Compare commits
28 Commits
19c06c71f8
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e665dea82a | ||
|
|
b511ec5df0 | ||
|
|
c0e53cd3f6 | ||
|
|
4fc7abc5b4 | ||
|
|
e04bf6f405 | ||
|
|
bed761401f | ||
|
|
55463649b2 | ||
|
|
62d2aa17be | ||
|
|
acfb2259cc | ||
|
|
d1d2194861 | ||
|
|
b542f98a91 | ||
|
|
f983b869ba | ||
|
|
ead346bb1b | ||
|
|
8d27e771a7 | ||
|
|
31fe3a4260 | ||
|
|
a2c6cd1d4f | ||
|
|
5fe9f371b2 | ||
|
|
b27fb7ed4c | ||
|
|
919ef611bc | ||
|
|
3eeed888b5 | ||
|
|
322f611522 | ||
|
|
a7d2634c5f | ||
|
|
ae125f1144 | ||
|
|
d66907f636 | ||
|
|
508d69a4f4 | ||
|
|
ee00415d23 | ||
|
|
576b86fbbb | ||
|
|
918742293b |
@@ -1,88 +0,0 @@
|
||||
name: Deploy Flutter to Firebase (Mac)
|
||||
on:
|
||||
push:
|
||||
branches: release
|
||||
|
||||
jobs:
|
||||
deploy-android:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Vérifier l'installation Flutter
|
||||
run: flutter doctor -v
|
||||
|
||||
- name: Installer les dépendances Flutter
|
||||
run: flutter pub get
|
||||
|
||||
- name: Créer les fichiers secrets
|
||||
run: |
|
||||
echo "${{ secrets.ENV_FILE }}" > .env
|
||||
printf '%s' '${{ secrets.FIREBASE_CREDENTIALS }}' > ./android/firebase_credentials.json
|
||||
|
||||
- name: Lancer Fastlane (Force APK)
|
||||
working-directory: ./android
|
||||
env:
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
RAW_PROPERTIES: ${{ secrets.ANDROID_KEY_PROPERTIES }}
|
||||
FIREBASE_ANDROID_APP_ID: ${{ secrets.FIREBASE_ANDROID_APP_ID }}
|
||||
run: |
|
||||
# 1. Config Ruby
|
||||
export PATH="/opt/homebrew/opt/ruby/bin:$PATH"
|
||||
export GEM_HOME=$PWD/vendor/bundle
|
||||
export GEM_PATH=$PWD/vendor/bundle
|
||||
export PATH=$GEM_HOME/bin:$PATH
|
||||
|
||||
# 2. Keystore & Properties
|
||||
echo "$ANDROID_KEYSTORE_BASE64" | base64 -D > keystore.jks
|
||||
KEYSTORE_PATH=$(pwd)/keystore.jks
|
||||
|
||||
echo "$RAW_PROPERTIES" > temp_props.txt
|
||||
STORE_PASS=$(grep "storePassword" temp_props.txt | cut -d'=' -f2 | tr -d '\r')
|
||||
KEY_PASS=$(grep "keyPassword" temp_props.txt | cut -d'=' -f2 | tr -d '\r')
|
||||
KEY_ALIAS=$(grep "keyAlias" temp_props.txt | cut -d'=' -f2 | tr -d '\r')
|
||||
|
||||
echo "storePassword=$STORE_PASS" > key.properties
|
||||
echo "keyPassword=$KEY_PASS" >> key.properties
|
||||
echo "keyAlias=$KEY_ALIAS" >> key.properties
|
||||
echo "storeFile=$KEYSTORE_PATH" >> key.properties
|
||||
|
||||
# 3. Installation Dépendances
|
||||
rm -rf vendor Gemfile.lock .bundle
|
||||
echo "source 'https://rubygems.org'" > Gemfile
|
||||
echo "gem 'fastlane', '>= 2.210.0'" >> Gemfile
|
||||
echo "gem 'fastlane-plugin-firebase_app_distribution'" >> Gemfile
|
||||
# Patchs Ruby 3.4
|
||||
echo "gem 'abbrev'" >> Gemfile
|
||||
echo "gem 'ostruct'" >> Gemfile
|
||||
echo "gem 'mutex_m'" >> Gemfile
|
||||
echo "gem 'base64'" >> Gemfile
|
||||
echo "gem 'csv'" >> Gemfile
|
||||
echo "gem 'bigdecimal'" >> Gemfile
|
||||
echo "gem 'drb'" >> Gemfile
|
||||
echo "gem 'nkf'" >> Gemfile
|
||||
|
||||
echo "⬇️ Installation..."
|
||||
gem install bundler -v 2.7.2 --force --no-document
|
||||
bundle _2.7.2_ update --jobs 4
|
||||
|
||||
# 4. CONSTRUCTION ET ENVOI MANUEL EN APK 🗝️
|
||||
# On remonte à la racine pour lancer Flutter
|
||||
cd ..
|
||||
|
||||
echo "🚧 Construction de l'APK (Format compatible sans Play Store)..."
|
||||
# C'est cette commande qui change tout : APK au lieu de AppBundle
|
||||
flutter build apk --release
|
||||
|
||||
# On redescend dans le dossier android pour lancer fastlane
|
||||
cd android
|
||||
|
||||
echo "🚀 Envoi de l'APK vers Firebase..."
|
||||
# On pointe vers le fichier APK généré
|
||||
bundle _2.7.2_ exec fastlane run firebase_app_distribution \
|
||||
app:"$FIREBASE_ANDROID_APP_ID" \
|
||||
android_artifact_path:"../build/app/outputs/flutter-apk/app-release.apk" \
|
||||
service_credentials_file:"firebase_credentials.json" \
|
||||
release_notes:"Build APK via Act - Test"
|
||||
@@ -1,107 +0,0 @@
|
||||
name: Deploy Flutter to Firebase iOS
|
||||
on:
|
||||
push:
|
||||
branches: release
|
||||
|
||||
jobs:
|
||||
deploy-ios:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Vérifier et Configurer Xcode
|
||||
run: |
|
||||
# 1. Lister les versions installées pour le debug (visible dans les logs)
|
||||
ls /Applications | grep Xcode
|
||||
|
||||
# 2. Vérifier la version actuelle par défaut
|
||||
xcodebuild -version
|
||||
|
||||
# 3. (Optionnel) Ne décommenter que si la version par défaut n'est pas la 26.2
|
||||
# sudo xcode-select -s /Applications/Xcode.app
|
||||
|
||||
- name: Installer les dépendances Flutter & Cocoapods
|
||||
run: |
|
||||
flutter pub get
|
||||
cd ios
|
||||
pod install --repo-update
|
||||
cd ..
|
||||
|
||||
- name: Créer les fichiers secrets
|
||||
run: |
|
||||
echo "${{ secrets.ENV_FILE }}" > .env
|
||||
printf '%s' '${{ secrets.FIREBASE_CREDENTIALS }}' > ./ios/firebase_credentials.json
|
||||
|
||||
- name: Préparer le Code Signing (Version Xcode 26.2)
|
||||
env:
|
||||
P12_BASE: ${{ secrets.IOS_P12_BASE64 }}
|
||||
P12_PASS: ${{ secrets.IOS_P12_PASSWORD }}
|
||||
PROV_BASE: ${{ secrets.IOS_PROVISION_BASE64 }}
|
||||
run: |
|
||||
# 1. Nettoyage et création
|
||||
security delete-keychain build.keychain || true
|
||||
security create-keychain -p "" build.keychain
|
||||
|
||||
# 2. AJOUTER AU CHEMIN DE RECHERCHE (Crucial pour Xcode 26.2)
|
||||
# Cela permet à Xcode de fouiller dans ce keychain pour signer l'IPA
|
||||
security list-keychains -d user -s build.keychain $(security list-keychains -d user | xargs)
|
||||
|
||||
# 3. Paramétrage et Déverrouillage
|
||||
security unlock-keychain -p "" build.keychain
|
||||
security set-keychain-settings -t 3600 -u build.keychain
|
||||
|
||||
# 4. Importation du certificat Apple Distribution
|
||||
echo "$P12_BASE" | base64 -D -o cert.p12
|
||||
# On autorise explicitement /usr/bin/codesign à accéder à la clé
|
||||
security import cert.p12 -k build.keychain -P "$P12_PASS" -T /usr/bin/codesign -T /usr/bin/productsign
|
||||
|
||||
# 5. Configuration de la partition pour éviter les popups bloquants
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
|
||||
|
||||
# 6. Installation du Profil (Bundle ID: be.devdayronvl.TravelMate)
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
# Xcode 26.2 nécessite que le nom du fichier corresponde à l'UUID ou soit standard
|
||||
echo "$PROV_BASE" | base64 -D -o ~/Library/MobileDevice/Provisioning\ Profiles/distribution.mobileprovision
|
||||
|
||||
- name: Lancer Fastlane & Build IPA
|
||||
working-directory: ./ios
|
||||
env:
|
||||
FIREBASE_IOS_APP_ID: ${{ secrets.FIREBASE_IOS_APP_ID }}
|
||||
run: |
|
||||
# 1. Nettoyage et PATH
|
||||
unset GEM_HOME
|
||||
unset GEM_PATH
|
||||
export PATH="/opt/homebrew/opt/ruby/bin:/opt/homebrew/bin:$PATH"
|
||||
|
||||
# 2. Installer la version de Bundler compatible avec Fastlane
|
||||
gem install bundler -v 2.7.2 --no-document
|
||||
|
||||
# 3. Création du Gemfile
|
||||
rm -f Gemfile Gemfile.lock
|
||||
echo "source 'https://rubygems.org'" > Gemfile
|
||||
echo "gem 'fastlane', '>= 2.210.0'" >> Gemfile
|
||||
echo "gem 'fastlane-plugin-firebase_app_distribution'" >> Gemfile
|
||||
# Indispensable pour Ruby 3.4 (Xcode 26.2 compatible)
|
||||
echo "gem 'base64'" >> Gemfile
|
||||
echo "gem 'bigdecimal'" >> Gemfile
|
||||
echo "gem 'mutex_m'" >> Gemfile
|
||||
|
||||
# 4. Installation avec la version FORCÉE de Bundler
|
||||
bundle _2.7.2_ config set path 'vendor/bundle'
|
||||
bundle _2.7.2_ install
|
||||
|
||||
# 5. Construction de l'IPA
|
||||
# Xcode 26.2 compile plus vite grâce à son nouveau moteur
|
||||
cd ..
|
||||
flutter build ipa --release --export-method ad-hoc
|
||||
|
||||
cd ios
|
||||
|
||||
# 6. Envoi vers Firebase
|
||||
echo "🚀 Envoi de l'IPA..."
|
||||
bundle _2.7.2_ exec fastlane run firebase_app_distribution \
|
||||
app:"$FIREBASE_IOS_APP_ID" \
|
||||
ipa_path:"../build/ios/ipa/*.ipa" \
|
||||
service_credentials_file:"firebase_credentials.json"
|
||||
226
.gitea/workflows/deploy.yaml
Normal file
226
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,226 @@
|
||||
name: Deploy TravelMate (Full Mobile)
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
|
||||
jobs:
|
||||
# --- JOB 1 : ANDROID (Génération APK) ---
|
||||
deploy-android:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Configuration Flutter & Secrets
|
||||
run: |
|
||||
flutter pub get
|
||||
echo "${{ secrets.ENV_FILE }}" > .env
|
||||
printf '%s' '${{ secrets.FIREBASE_CREDENTIALS }}' > ./android/firebase_credentials.json
|
||||
|
||||
- name: Build & Deploy Android
|
||||
working-directory: ./android
|
||||
env:
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
RAW_PROPERTIES: ${{ secrets.ANDROID_KEY_PROPERTIES }}
|
||||
FIREBASE_ANDROID_APP_ID: ${{ secrets.FIREBASE_ANDROID_APP_ID }}
|
||||
run: |
|
||||
# 1. Keystore
|
||||
echo "$ANDROID_KEYSTORE_BASE64" | base64 -D > keystore.jks
|
||||
echo "$RAW_PROPERTIES" > temp_props.txt
|
||||
echo "storePassword=$(grep 'storePassword' temp_props.txt | cut -d'=' -f2 | tr -d '\r')" > key.properties
|
||||
echo "keyPassword=$(grep 'keyPassword' temp_props.txt | cut -d'=' -f2 | tr -d '\r')" >> key.properties
|
||||
echo "keyAlias=$(grep 'keyAlias' temp_props.txt | cut -d'=' -f2 | tr -d '\r')" >> key.properties
|
||||
echo "storeFile=$(pwd)/keystore.jks" >> key.properties
|
||||
|
||||
# 2. Gemfile (Correctifs Ruby 3.4 inclus)
|
||||
echo "source 'https://rubygems.org'" > Gemfile
|
||||
echo "gem 'fastlane', '>= 2.230.0'" >> Gemfile
|
||||
echo "gem 'fastlane-plugin-firebase_app_distribution'" >> Gemfile
|
||||
for g in abbrev ostruct mutex_m base64 csv bigdecimal drb nkf reline logger; do echo "gem '$g'" >> Gemfile; done
|
||||
|
||||
gem install bundler -v 2.7.2 --no-document
|
||||
bundle _2.7.2_ update
|
||||
|
||||
# 3. Build & Envoi
|
||||
cd .. && flutter build apk --release && cd android
|
||||
bundle _2.7.2_ exec fastlane run firebase_app_distribution \
|
||||
app:"$FIREBASE_ANDROID_APP_ID" \
|
||||
android_artifact_path:"../build/app/outputs/flutter-apk/app-release.apk" \
|
||||
service_credentials_file:"firebase_credentials.json"
|
||||
|
||||
# --- JOB 2 : IOS (Génération IPA) ---
|
||||
deploy-ios:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Dépendances iOS & Secrets
|
||||
run: |
|
||||
flutter pub get
|
||||
echo "${{ secrets.ENV_FILE }}" > .env
|
||||
printf '%s' '${{ secrets.FIREBASE_CREDENTIALS }}' > ./ios/firebase_credentials.json
|
||||
cd ios && (pod install --repo-update || pod update)
|
||||
|
||||
- name: Préparer le Code Signing
|
||||
env:
|
||||
P12_BASE: ${{ secrets.IOS_P12_BASE64 }}
|
||||
P12_PASS: ${{ secrets.IOS_P12_PASSWORD }}
|
||||
PROV_BASE: ${{ secrets.IOS_PROVISION_BASE64 }}
|
||||
TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
|
||||
run: |
|
||||
# Créer et configurer le keychain
|
||||
security delete-keychain build.keychain || true
|
||||
security create-keychain -p "" build.keychain
|
||||
security unlock-keychain -p "" build.keychain
|
||||
security list-keychains -d user -s build.keychain $(security list-keychains -d user | xargs)
|
||||
security set-keychain-settings -lut 21600 build.keychain
|
||||
|
||||
# Importer le certificat
|
||||
echo "$P12_BASE" | base64 -D -o cert.p12
|
||||
security import cert.p12 -k build.keychain -P "$P12_PASS" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
|
||||
|
||||
# Installer le profil de provisioning
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
PROFILE_PATH=~/Library/MobileDevice/Provisioning\ Profiles/distribution.mobileprovision
|
||||
echo "$PROV_BASE" | base64 -D -o "$PROFILE_PATH"
|
||||
|
||||
# Extraire l'UUID du profil
|
||||
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(/usr/bin/security cms -D -i "$PROFILE_PATH"))
|
||||
echo "Profile UUID: $PROFILE_UUID"
|
||||
|
||||
# Copier avec l'UUID correct
|
||||
cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_UUID.mobileprovision
|
||||
|
||||
- name: Configurer le projet Xcode
|
||||
env:
|
||||
TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
|
||||
BUNDLE_ID: ${{ secrets.IOS_BUNDLE_ID }}
|
||||
run: |
|
||||
cd ios
|
||||
|
||||
# Extraire le nom du profil de provisioning
|
||||
PROV_NAME=$(/usr/libexec/PlistBuddy -c "Print Name" /dev/stdin <<< $(/usr/bin/security cms -D -i ~/Library/MobileDevice/Provisioning\ Profiles/distribution.mobileprovision))
|
||||
echo "📝 Provisioning Profile Name: $PROV_NAME"
|
||||
|
||||
# Obtenir l'UUID du profil
|
||||
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(/usr/bin/security cms -D -i ~/Library/MobileDevice/Provisioning\ Profiles/distribution.mobileprovision))
|
||||
echo "🔑 Profile UUID: $PROFILE_UUID"
|
||||
|
||||
# Sauvegarder les variables pour la prochaine étape
|
||||
echo "$PROV_NAME" > /tmp/prov_name.txt
|
||||
echo "$TEAM_ID" > /tmp/team_id.txt
|
||||
echo "$BUNDLE_ID" > /tmp/bundle_id.txt
|
||||
|
||||
echo "✅ Configuration des paramètres de signing prête"
|
||||
|
||||
- name: Créer exportOptions.plist
|
||||
env:
|
||||
TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
|
||||
BUNDLE_ID: ${{ secrets.IOS_BUNDLE_ID }}
|
||||
run: |
|
||||
# Extraire le nom du profil
|
||||
PROV_NAME=$(/usr/libexec/PlistBuddy -c "Print Name" /dev/stdin <<< $(/usr/bin/security cms -D -i ~/Library/MobileDevice/Provisioning\ Profiles/distribution.mobileprovision))
|
||||
|
||||
cat > ios/exportOptions.plist <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>ad-hoc</string>
|
||||
<key>teamID</key>
|
||||
<string>$TEAM_ID</string>
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>$BUNDLE_ID</key>
|
||||
<string>$PROV_NAME</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
- name: Build IPA avec Flutter
|
||||
run: |
|
||||
# Récupérer les variables sauvegardées
|
||||
PROV_NAME=$(cat /tmp/prov_name.txt)
|
||||
TEAM_ID=$(cat /tmp/team_id.txt)
|
||||
BUNDLE_ID=$(cat /tmp/bundle_id.txt)
|
||||
|
||||
echo "📝 Provisioning Profile: $PROV_NAME"
|
||||
echo "🔑 Team ID: $TEAM_ID"
|
||||
echo "📦 Bundle ID: $BUNDLE_ID"
|
||||
|
||||
# Clean
|
||||
flutter clean
|
||||
flutter pub get
|
||||
|
||||
echo "🔨 Build de l'IPA avec Flutter..."
|
||||
|
||||
# Flutter build ipa gère automatiquement le signing des Pods
|
||||
# On utilise SEULEMENT --export-options-plist (pas --export-method)
|
||||
flutter build ipa \
|
||||
--release \
|
||||
--export-options-plist=ios/exportOptions.plist
|
||||
|
||||
echo "✅ Build terminé"
|
||||
echo "📂 Recherche de l'IPA..."
|
||||
|
||||
# L'IPA devrait être dans build/ios/ipa/
|
||||
find build/ios -name "*.ipa" -type f
|
||||
|
||||
IPA_FILE=$(find build/ios/ipa -name "*.ipa" | head -n 1)
|
||||
|
||||
if [ -z "$IPA_FILE" ]; then
|
||||
echo "❌ ERREUR: Aucun fichier IPA trouvé !"
|
||||
echo "📂 Contenu de build/ios/ :"
|
||||
ls -R build/ios/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ IPA trouvée : $IPA_FILE"
|
||||
echo "📊 Taille : $(du -h "$IPA_FILE" | cut -f1)"
|
||||
|
||||
- name: Vérification et Upload Firebase
|
||||
env:
|
||||
FIREBASE_IOS_APP_ID: ${{ secrets.FIREBASE_IOS_APP_ID }}
|
||||
run: |
|
||||
cd ios
|
||||
|
||||
# Configuration Fastlane
|
||||
echo "source 'https://rubygems.org'" > Gemfile
|
||||
echo "gem 'fastlane', '>= 2.230.0'" >> Gemfile
|
||||
echo "gem 'fastlane-plugin-firebase_app_distribution'" >> Gemfile
|
||||
for g in abbrev ostruct mutex_m base64 csv bigdecimal drb nkf reline logger; do echo "gem '$g'" >> Gemfile; done
|
||||
|
||||
gem install bundler -v 2.7.2 --no-document
|
||||
bundle _2.7.2_ update
|
||||
|
||||
# Recherche de l'IPA
|
||||
IPA_FILE=$(find ../build/ios/ipa -name "*.ipa" | head -n 1)
|
||||
|
||||
if [ -z "$IPA_FILE" ]; then
|
||||
echo "❌ ERREUR : Aucun fichier IPA trouvé !"
|
||||
echo "📂 Structure complète du dossier build :"
|
||||
ls -R ../build/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ IPA trouvée : $IPA_FILE"
|
||||
echo "📊 Taille : $(du -h "$IPA_FILE" | cut -f1)"
|
||||
|
||||
# Upload vers Firebase
|
||||
bundle _2.7.2_ exec fastlane run firebase_app_distribution \
|
||||
app:"$FIREBASE_IOS_APP_ID" \
|
||||
ipa_path:"$IPA_FILE" \
|
||||
service_credentials_file:"firebase_credentials.json"
|
||||
|
||||
- name: Nettoyage Final
|
||||
if: always()
|
||||
run: |
|
||||
security list-keychains -d user -s login.keychain
|
||||
security delete-keychain build.keychain || true
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -51,3 +51,8 @@ app.*.map.json
|
||||
firestore.rules
|
||||
storage.rules
|
||||
/functions/node_modules
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
.VSCodeCounter
|
||||
|
||||
|
||||
@@ -59,4 +59,4 @@ Travel Mate est une application mobile conçue pour simplifier l'organisation de
|
||||
|
||||
## 🚀 CI/CD & Déploiement
|
||||
|
||||
Les versions de test interne Android sont automatiquement distribuées via **Firebase App Distribution**.
|
||||
Les versions de test interne Android et IOS sont automatiquement distribuées via **Firebase App Distribution**.
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["TravelMate.Backend.csproj", "./"]
|
||||
RUN dotnet restore "TravelMate.Backend.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/."
|
||||
RUN dotnet build "TravelMate.Backend.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "TravelMate.Backend.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
EXPOSE 8080
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
ENTRYPOINT ["dotnet", "TravelMate.Backend.dll"]
|
||||
@@ -1,356 +0,0 @@
|
||||
using DotNetEnv;
|
||||
using Google.Apis.AnalyticsData.v1beta;
|
||||
using Google.Apis.AnalyticsData.v1beta.Data;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
Env.Load();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
// Register Google Analytics Data Client
|
||||
builder.Services.AddSingleton<AnalyticsDataService>(sp =>
|
||||
{
|
||||
GoogleCredential credential;
|
||||
try
|
||||
{
|
||||
// Tries to find credentials from GOOGLE_APPLICATION_CREDENTIALS environment variable
|
||||
// or default cloud environment credentials.
|
||||
credential = GoogleCredential.GetApplicationDefault();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Fallback for local development if env var not set (User might need to run 'gcloud auth application-default login')
|
||||
// Or we warn them. For now, we assume it's set up or will fail at runtime.
|
||||
throw new InvalidOperationException("Could not load Google Credentials. Set GOOGLE_APPLICATION_CREDENTIALS or run 'gcloud auth application-default login'.");
|
||||
}
|
||||
|
||||
if (credential.IsCreateScopedRequired)
|
||||
{
|
||||
credential = credential.CreateScoped(AnalyticsDataService.Scope.AnalyticsReadonly);
|
||||
}
|
||||
|
||||
var service = new AnalyticsDataService(new BaseClientService.Initializer
|
||||
{
|
||||
HttpClientInitializer = credential,
|
||||
ApplicationName = "TravelMate Backend",
|
||||
});
|
||||
|
||||
return service;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
string? ga4PropertyId = Environment.GetEnvironmentVariable("GA4_PROPERTY_ID");
|
||||
if (string.IsNullOrEmpty(ga4PropertyId))
|
||||
{
|
||||
Console.WriteLine("Warning: GA4_PROPERTY_ID not set in .env");
|
||||
// Default or error? Let's just hold it and fail in the endpoint if needed, or fail startup.
|
||||
// For now, let's allow it to start but endpoint will likely fail.
|
||||
}
|
||||
|
||||
app.MapGet("/api/metrics/daily", async (
|
||||
[FromServices] AnalyticsDataService service,
|
||||
[FromQuery] string from,
|
||||
[FromQuery] string to) =>
|
||||
{
|
||||
// Basic validation
|
||||
// Basic validation: Check if it's a valid date OR a valid GA4 keyword
|
||||
bool IsValidDate(string date)
|
||||
{
|
||||
return DateTime.TryParse(date, out _) ||
|
||||
date == "today" ||
|
||||
date == "yesterday" ||
|
||||
date.EndsWith("daysAgo");
|
||||
}
|
||||
|
||||
if (!IsValidDate(from) || !IsValidDate(to))
|
||||
{
|
||||
return Results.BadRequest(new { error = "Invalid date format. Use YYYY-MM-DD, 'today', 'yesterday', or 'NdaysAgo'." });
|
||||
}
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange>
|
||||
{
|
||||
new DateRange { StartDate = from, EndDate = to }
|
||||
},
|
||||
Dimensions = new List<Dimension>
|
||||
{
|
||||
new Dimension { Name = "date" }
|
||||
},
|
||||
Metrics = new List<Metric>
|
||||
{
|
||||
new Metric { Name = "activeUsers" } // equivalent to Daily Active Users in this context often
|
||||
// Or strictly "activeUsers"
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
|
||||
var result = response.Rows?.Select(row => new
|
||||
{
|
||||
date = FormatDate(row.DimensionValues[0].Value), // GA4 returns date as YYYYMMDD string usually
|
||||
dailyActiveUsers = int.Parse(row.MetricValues[0].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex}");
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetDailyMetrics")
|
||||
.WithOpenApi();
|
||||
|
||||
app.MapGet("/api/metrics/general", async (
|
||||
[FromServices] AnalyticsDataService service) =>
|
||||
{
|
||||
// "Global" means all time ideally, or a very long range.
|
||||
// GA4 retention might limit this for user-level data, but aggregated metrics usually go back further.
|
||||
var startDate = "2023-01-01"; // Arbitrary start date for the app
|
||||
var endDate = "today";
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange>
|
||||
{
|
||||
new DateRange { StartDate = startDate, EndDate = endDate }
|
||||
},
|
||||
Metrics = new List<Metric>
|
||||
{
|
||||
new Metric { Name = "totalUsers" }, // Proxy for "nb de comptes"
|
||||
new Metric { Name = "eventCount" } // Proxy for "nb de requètes"
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
|
||||
var totalUsers = 0;
|
||||
var eventCount = 0;
|
||||
|
||||
if (response.Rows != null && response.Rows.Count > 0)
|
||||
{
|
||||
var row = response.Rows[0];
|
||||
totalUsers = int.Parse(row.MetricValues[0].Value);
|
||||
eventCount = int.Parse(row.MetricValues[1].Value);
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
totalAccounts = totalUsers,
|
||||
totalRequests = eventCount
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex}");
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
|
||||
})
|
||||
.WithName("GetGeneralMetrics")
|
||||
.WithOpenApi();
|
||||
|
||||
// 3. Tech Overview (OS & App Version)
|
||||
// GET /api/metrics/tech?from=x&to=y
|
||||
app.MapGet("/api/metrics/tech", async (
|
||||
[FromServices] AnalyticsDataService service,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] string? to) =>
|
||||
{
|
||||
var startDate = from ?? "30daysAgo";
|
||||
var endDate = to ?? "today";
|
||||
|
||||
// Request for OS
|
||||
var requestOS = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||
Dimensions = new List<Dimension> { new Dimension { Name = "operatingSystem" } },
|
||||
Metrics = new List<Metric> { new Metric { Name = "activeUsers" } }
|
||||
};
|
||||
|
||||
// Request for App Version
|
||||
var requestVersion = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||
Dimensions = new List<Dimension> { new Dimension { Name = "appVersion" } },
|
||||
Metrics = new List<Metric> { new Metric { Name = "activeUsers" } } // use active users to see version adoption
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var responseOS = await service.Properties.RunReport(requestOS, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
var responseVersion = await service.Properties.RunReport(requestVersion, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
|
||||
var osData = responseOS.Rows?.Select(row => new {
|
||||
os = row.DimensionValues[0].Value,
|
||||
users = int.Parse(row.MetricValues[0].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
var versionData = responseVersion.Rows?.Select(row => new {
|
||||
version = row.DimensionValues[0].Value,
|
||||
users = int.Parse(row.MetricValues[0].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
return Results.Ok(new { operatingSystems = osData, appVersions = versionData });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetTechOverview")
|
||||
.WithOpenApi();
|
||||
|
||||
// 4. Top Events
|
||||
// GET /api/metrics/events?from=x&to=y
|
||||
app.MapGet("/api/metrics/events", async (
|
||||
[FromServices] AnalyticsDataService service,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] string? to) =>
|
||||
{
|
||||
var startDate = from ?? "30daysAgo";
|
||||
var endDate = to ?? "today";
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||
Dimensions = new List<Dimension> { new Dimension { Name = "eventName" } },
|
||||
Metrics = new List<Metric> { new Metric { Name = "eventCount" }, new Metric { Name = "activeUsers" } },
|
||||
OrderBys = new List<OrderBy> { new OrderBy { Desc = true, Metric = new MetricOrderBy { MetricName = "eventCount" } } },
|
||||
Limit = 20
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
var result = response.Rows?.Select(row => new {
|
||||
eventName = row.DimensionValues[0].Value,
|
||||
count = int.Parse(row.MetricValues[0].Value),
|
||||
users = int.Parse(row.MetricValues[1].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetTopEvents")
|
||||
.WithOpenApi();
|
||||
|
||||
// 5. Top Screens
|
||||
// GET /api/metrics/screens?from=x&to=y
|
||||
app.MapGet("/api/metrics/screens", async (
|
||||
[FromServices] AnalyticsDataService service,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] string? to) =>
|
||||
{
|
||||
var startDate = from ?? "30daysAgo";
|
||||
var endDate = to ?? "today";
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||
// GA4 uses 'unifiedScreenName' or 'pagePath' depending on setup, usually 'unifiedScreenName' for apps
|
||||
Dimensions = new List<Dimension> { new Dimension { Name = "unifiedScreenName" } },
|
||||
Metrics = new List<Metric> { new Metric { Name = "screenPageViews" }, new Metric { Name = "activeUsers" } },
|
||||
OrderBys = new List<OrderBy> { new OrderBy { Desc = true, Metric = new MetricOrderBy { MetricName = "screenPageViews" } } },
|
||||
Limit = 20
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
var result = response.Rows?.Select(row => new {
|
||||
screenName = row.DimensionValues[0].Value,
|
||||
views = int.Parse(row.MetricValues[0].Value),
|
||||
users = int.Parse(row.MetricValues[1].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetTopScreens")
|
||||
.WithOpenApi();
|
||||
|
||||
// 6. User Retention (New vs Returning)
|
||||
// GET /api/metrics/retention?from=x&to=y
|
||||
app.MapGet("/api/metrics/retention", async (
|
||||
[FromServices] AnalyticsDataService service,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] string? to) =>
|
||||
{
|
||||
var startDate = from ?? "30daysAgo";
|
||||
var endDate = to ?? "today";
|
||||
|
||||
var request = new RunReportRequest
|
||||
{
|
||||
Property = $"properties/{ga4PropertyId}",
|
||||
DateRanges = new List<DateRange> { new DateRange { StartDate = startDate, EndDate = endDate } },
|
||||
Dimensions = new List<Dimension> { new Dimension { Name = "newVsReturning" } },
|
||||
Metrics = new List<Metric> { new Metric { Name = "activeUsers" }, new Metric { Name = "sessions" } }
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await service.Properties.RunReport(request, $"properties/{ga4PropertyId}").ExecuteAsync();
|
||||
var result = response.Rows?.Select(row => new {
|
||||
type = row.DimensionValues[0].Value,
|
||||
users = int.Parse(row.MetricValues[0].Value),
|
||||
sessions = int.Parse(row.MetricValues[1].Value)
|
||||
}) ?? Enumerable.Empty<object>();
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Problem($"Analytics API Error: {ex.Message}");
|
||||
}
|
||||
})
|
||||
.WithName("GetUserRetention")
|
||||
.WithOpenApi();
|
||||
|
||||
app.Run();
|
||||
|
||||
// Helper to format GA4 YYYYMMDD to YYYY-MM-DD
|
||||
static string FormatDate(string gaDate)
|
||||
{
|
||||
if (gaDate.Length == 8)
|
||||
{
|
||||
return $"{gaDate.Substring(0, 4)}-{gaDate.Substring(4, 2)}-{gaDate.Substring(6, 2)}";
|
||||
}
|
||||
return gaDate;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetEnv" Version="3.1.1" />
|
||||
<PackageReference Include="Google.Apis.AnalyticsData.v1beta" Version="1.68.0.3608" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,291 +0,0 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v8.0",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v8.0": {
|
||||
"TravelMate.Backend/1.0.0": {
|
||||
"dependencies": {
|
||||
"DotNetEnv": "3.1.1",
|
||||
"Google.Apis.AnalyticsData.v1beta": "1.68.0.3608",
|
||||
"Microsoft.AspNetCore.OpenApi": "8.0.1",
|
||||
"Swashbuckle.AspNetCore": "6.5.0"
|
||||
},
|
||||
"runtime": {
|
||||
"TravelMate.Backend.dll": {}
|
||||
}
|
||||
},
|
||||
"DotNetEnv/3.1.1": {
|
||||
"dependencies": {
|
||||
"Sprache": "2.3.1"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard1.3/DotNetEnv.dll": {
|
||||
"assemblyVersion": "3.1.1.0",
|
||||
"fileVersion": "3.1.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Google.Apis/1.68.0": {
|
||||
"dependencies": {
|
||||
"Google.Apis.Core": "1.68.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/Google.Apis.dll": {
|
||||
"assemblyVersion": "1.68.0.0",
|
||||
"fileVersion": "1.68.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Google.Apis.AnalyticsData.v1beta/1.68.0.3608": {
|
||||
"dependencies": {
|
||||
"Google.Apis": "1.68.0",
|
||||
"Google.Apis.Auth": "1.68.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/Google.Apis.AnalyticsData.v1beta.dll": {
|
||||
"assemblyVersion": "1.68.0.3608",
|
||||
"fileVersion": "1.68.0.3608"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Google.Apis.Auth/1.68.0": {
|
||||
"dependencies": {
|
||||
"Google.Apis": "1.68.0",
|
||||
"Google.Apis.Core": "1.68.0",
|
||||
"System.Management": "7.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/Google.Apis.Auth.dll": {
|
||||
"assemblyVersion": "1.68.0.0",
|
||||
"fileVersion": "1.68.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Google.Apis.Core/1.68.0": {
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "13.0.3"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/Google.Apis.Core.dll": {
|
||||
"assemblyVersion": "1.68.0.0",
|
||||
"fileVersion": "1.68.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi/8.0.1": {
|
||||
"dependencies": {
|
||||
"Microsoft.OpenApi": "1.4.3"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.AspNetCore.OpenApi.dll": {
|
||||
"assemblyVersion": "8.0.1.0",
|
||||
"fileVersion": "8.0.123.58008"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.OpenApi/1.4.3": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
||||
"assemblyVersion": "1.4.3.0",
|
||||
"fileVersion": "1.4.3.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json/13.0.3": {
|
||||
"runtime": {
|
||||
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||
"assemblyVersion": "13.0.0.0",
|
||||
"fileVersion": "13.0.3.27908"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Sprache/2.3.1": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.1/Sprache.dll": {
|
||||
"assemblyVersion": "2.3.1.0",
|
||||
"fileVersion": "2.3.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore/6.5.0": {
|
||||
"dependencies": {
|
||||
"Swashbuckle.AspNetCore.Swagger": "6.5.0",
|
||||
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0",
|
||||
"Swashbuckle.AspNetCore.SwaggerUI": "6.5.0"
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore.Swagger/6.5.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.OpenApi": "1.4.3"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net7.0/Swashbuckle.AspNetCore.Swagger.dll": {
|
||||
"assemblyVersion": "6.5.0.0",
|
||||
"fileVersion": "6.5.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerGen/6.5.0": {
|
||||
"dependencies": {
|
||||
"Swashbuckle.AspNetCore.Swagger": "6.5.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
|
||||
"assemblyVersion": "6.5.0.0",
|
||||
"fileVersion": "6.5.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerUI/6.5.0": {
|
||||
"runtime": {
|
||||
"lib/net7.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
|
||||
"assemblyVersion": "6.5.0.0",
|
||||
"fileVersion": "6.5.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.CodeDom/7.0.0": {
|
||||
"runtime": {
|
||||
"lib/net7.0/System.CodeDom.dll": {
|
||||
"assemblyVersion": "7.0.0.0",
|
||||
"fileVersion": "7.0.22.51805"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Management/7.0.2": {
|
||||
"dependencies": {
|
||||
"System.CodeDom": "7.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net7.0/System.Management.dll": {
|
||||
"assemblyVersion": "7.0.0.2",
|
||||
"fileVersion": "7.0.723.27404"
|
||||
}
|
||||
},
|
||||
"runtimeTargets": {
|
||||
"runtimes/win/lib/net7.0/System.Management.dll": {
|
||||
"rid": "win",
|
||||
"assetType": "runtime",
|
||||
"assemblyVersion": "7.0.0.2",
|
||||
"fileVersion": "7.0.723.27404"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"TravelMate.Backend/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
},
|
||||
"DotNetEnv/3.1.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-o4SqUVCq0pqHF/HYsZk6k22XGIVmvsDVo+Dy7l0ubq9uQ45JkXswrMRJmYvhGLXWFYF0M5OupMonytB+0zvpGQ==",
|
||||
"path": "dotnetenv/3.1.1",
|
||||
"hashPath": "dotnetenv.3.1.1.nupkg.sha512"
|
||||
},
|
||||
"Google.Apis/1.68.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-s2MymhdpH+ybZNBeZ2J5uFgFHApBp+QXf9FjZSdM1lk/vx5VqIknJwnaWiuAzXxPrLEkesX0Q+UsiWn39yZ9zw==",
|
||||
"path": "google.apis/1.68.0",
|
||||
"hashPath": "google.apis.1.68.0.nupkg.sha512"
|
||||
},
|
||||
"Google.Apis.AnalyticsData.v1beta/1.68.0.3608": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-y9BdpAyCIKmRSFPUZEwyD2CtqXgzC8L0gqCqfoeDIn2bBu3RBp+uiLfCe/ns20AuArfnyIJgR4unmvap+9CpHQ==",
|
||||
"path": "google.apis.analyticsdata.v1beta/1.68.0.3608",
|
||||
"hashPath": "google.apis.analyticsdata.v1beta.1.68.0.3608.nupkg.sha512"
|
||||
},
|
||||
"Google.Apis.Auth/1.68.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-hFx8Qz5bZ4w0hpnn4tSmZaaFpjAMsgVElZ+ZgVLUZ2r9i+AKcoVgwiNfv1pruNS5cCvpXqhKECbruBCfRezPHA==",
|
||||
"path": "google.apis.auth/1.68.0",
|
||||
"hashPath": "google.apis.auth.1.68.0.nupkg.sha512"
|
||||
},
|
||||
"Google.Apis.Core/1.68.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-pAqwa6pfu53UXCR2b7A/PAPXeuVg6L1OFw38WckN27NU2+mf+KTjoEg2YGv/f0UyKxzz7DxF1urOTKg/6dTP9g==",
|
||||
"path": "google.apis.core/1.68.0",
|
||||
"hashPath": "google.apis.core.1.68.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi/8.0.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-JF0U31SByntKEhZNC8ch3CD87BLZb5AzSujOBppLdvnjYu3W2XZvlsol5hUJfxiaHOp720cUiL6wzG8Bv0fI2Q==",
|
||||
"path": "microsoft.aspnetcore.openapi/8.0.1",
|
||||
"hashPath": "microsoft.aspnetcore.openapi.8.0.1.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.OpenApi/1.4.3": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-rURwggB+QZYcSVbDr7HSdhw/FELvMlriW10OeOzjPT7pstefMo7IThhtNtDudxbXhW+lj0NfX72Ka5EDsG8x6w==",
|
||||
"path": "microsoft.openapi/1.4.3",
|
||||
"hashPath": "microsoft.openapi.1.4.3.nupkg.sha512"
|
||||
},
|
||||
"Newtonsoft.Json/13.0.3": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
|
||||
"path": "newtonsoft.json/13.0.3",
|
||||
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
|
||||
},
|
||||
"Sprache/2.3.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-Q+mXeiTxiUYG3lKYF6TS82/SyB4F2613Q1yXTMwg4jWGHEEVC3yrzHtNcI4B3qnDI0+eJsezGJ0V+cToUytHWw==",
|
||||
"path": "sprache/2.3.1",
|
||||
"hashPath": "sprache.2.3.1.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore/6.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-FK05XokgjgwlCI6wCT+D4/abtQkL1X1/B9Oas6uIwHFmYrIO9WUD5aLC9IzMs9GnHfUXOtXZ2S43gN1mhs5+aA==",
|
||||
"path": "swashbuckle.aspnetcore/6.5.0",
|
||||
"hashPath": "swashbuckle.aspnetcore.6.5.0.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore.Swagger/6.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-XWmCmqyFmoItXKFsQSwQbEAsjDKcxlNf1l+/Ki42hcb6LjKL8m5Db69OTvz5vLonMSRntYO1XLqz0OP+n3vKnA==",
|
||||
"path": "swashbuckle.aspnetcore.swagger/6.5.0",
|
||||
"hashPath": "swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerGen/6.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-Y/qW8Qdg9OEs7V013tt+94OdPxbRdbhcEbw4NiwGvf4YBcfhL/y7qp/Mjv/cENsQ2L3NqJ2AOu94weBy/h4KvA==",
|
||||
"path": "swashbuckle.aspnetcore.swaggergen/6.5.0",
|
||||
"hashPath": "swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerUI/6.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-OvbvxX+wL8skxTBttcBsVxdh73Fag4xwqEU2edh4JMn7Ws/xJHnY/JB1e9RoCb6XpDxUF3hD9A0Z1lEUx40Pfw==",
|
||||
"path": "swashbuckle.aspnetcore.swaggerui/6.5.0",
|
||||
"hashPath": "swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512"
|
||||
},
|
||||
"System.CodeDom/7.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-GLltyqEsE5/3IE+zYRP5sNa1l44qKl9v+bfdMcwg+M9qnQf47wK3H0SUR/T+3N4JEQXF3vV4CSuuo0rsg+nq2A==",
|
||||
"path": "system.codedom/7.0.0",
|
||||
"hashPath": "system.codedom.7.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Management/7.0.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-/qEUN91mP/MUQmJnM5y5BdT7ZoPuVrtxnFlbJ8a3kBJGhe2wCzBfnPFtK2wTtEEcf3DMGR9J00GZZfg6HRI6yA==",
|
||||
"path": "system.management/7.0.2",
|
||||
"hashPath": "system.management.7.0.2.nupkg.sha512"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.AspNetCore.App",
|
||||
"version": "8.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.GC.Server": true,
|
||||
"System.Globalization.Invariant": true,
|
||||
"System.Globalization.PredefinedCulturesOnly": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"Version":1,"ManifestType":"Build","Endpoints":[]}
|
||||
Binary file not shown.
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
PROJECT_ID="travelmate-a47f5"
|
||||
SERVICE_NAME="travelmate-backend"
|
||||
REGION="europe-west1" # Vous pourrez changer ceci si besoin
|
||||
|
||||
echo "🚀 Déploiement de $SERVICE_NAME sur $PROJECT_ID..."
|
||||
|
||||
# Vérification de gcloud
|
||||
if ! command -v gcloud &> /dev/null
|
||||
then
|
||||
echo "❌ gcloud n'est pas installé."
|
||||
echo "👉 Installez le Google Cloud SDK : https://cloud.google.com/sdk/docs/install"
|
||||
echo "Puis lancez 'gcloud auth login' et 'gcloud auth application-default login'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build sur Cloud Build (plus simple que Docker local et push)
|
||||
echo "📦 Construction de l'image sur Google Cloud Build..."
|
||||
gcloud builds submit --tag gcr.io/$PROJECT_ID/$SERVICE_NAME --project $PROJECT_ID .
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Échec du build."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Déploiement sur Cloud Run
|
||||
echo "🚀 Déploiement sur Cloud Run..."
|
||||
gcloud run deploy $SERVICE_NAME \
|
||||
--image gcr.io/$PROJECT_ID/$SERVICE_NAME \
|
||||
--platform managed \
|
||||
--region $REGION \
|
||||
--project $PROJECT_ID \
|
||||
--allow-unauthenticated \
|
||||
--set-env-vars GA4_PROPERTY_ID=507003174
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Déploiement terminé avec succès !"
|
||||
echo "ℹ️ Récupérez l'URL ci-dessus pour votre MauiProgram.cs"
|
||||
else
|
||||
echo "❌ Échec du déploiement."
|
||||
fi
|
||||
@@ -1,4 +0,0 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -1,22 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("TravelMate.Backend")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+63fc18ea740090c0a2233c46af179b2dbe32a9b8")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("TravelMate.Backend")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("TravelMate.Backend")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Généré par la classe MSBuild WriteCodeFragment.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
0aab77bc3ae598fb4e009b4ecda3aeffcfb640a524b7ecc7591b10a25aa5c92a
|
||||
@@ -1,23 +0,0 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v8.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb = true
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization = true
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = TravelMate.Backend
|
||||
build_property.RootNamespace = TravelMate.Backend
|
||||
build_property.ProjectDir = /Users/dayronvanleemput/Documents/Coding/travel_mate/backend/
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.RazorLangVersion = 8.0
|
||||
build_property.SupportLocalizedComponentNames =
|
||||
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
||||
build_property.MSBuildProjectDirectory = /Users/dayronvanleemput/Documents/Coding/travel_mate/backend
|
||||
build_property._RazorSourceGeneratorDebug =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -1,17 +0,0 @@
|
||||
// <auto-generated/>
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.Http;
|
||||
global using Microsoft.AspNetCore.Routing;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Net.Http.Json;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
@@ -1,17 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Microsoft.AspNetCore.OpenApi")]
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Swashbuckle.AspNetCore.SwaggerGen")]
|
||||
|
||||
// Généré par la classe MSBuild WriteCodeFragment.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
564fc6ab625b9b4d1fd8dff0a4b24b86b698bd2655c9670266b6506278a91b74
|
||||
@@ -1,44 +0,0 @@
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.csproj.AssemblyReference.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rpswa.dswa.cache.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.GeneratedMSBuildEditorConfig.editorconfig
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.AssemblyInfoInputs.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.AssemblyInfo.cs
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.csproj.CoreCompileInputs.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.MvcApplicationPartsAssemblyInfo.cs
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.MvcApplicationPartsAssemblyInfo.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.staticwebassets.endpoints.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.deps.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.runtimeconfig.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/TravelMate.Backend.pdb
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.AnalyticsData.v1beta.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.Auth.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Google.Apis.Core.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Microsoft.AspNetCore.OpenApi.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Microsoft.OpenApi.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Newtonsoft.Json.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Swashbuckle.AspNetCore.Swagger.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/System.CodeDom.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/System.Management.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/runtimes/win/lib/net7.0/System.Management.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rjimswa.dswa.cache.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rjsmrazor.dswa.cache.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/rjsmcshtml.dswa.cache.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/scopedcss/bundle/TravelMate.Backend.styles.css
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.build.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.build.json.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.development.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/staticwebassets.build.endpoints.json
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/swae.build.ex.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMa.030FF4D7.Up2Date
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/refint/TravelMate.Backend.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.pdb
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/TravelMate.Backend.genruntimeconfig.cache
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/Debug/net8.0/ref/TravelMate.Backend.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/DotNetEnv.dll
|
||||
/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/bin/Debug/net8.0/Sprache.dll
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
f79f58c3a8e99961a1a88dcd08a90777e8913bfe44cb3f95b85212ac836b242e
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"GlobalPropertiesHash":"mgx0JdLooAvG36KewBSjBhnWYhsOqFG08xB+ZULwM34=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["eZ2qkZPO/MYAX3sbEVnqz208bqpLunsDjAZG7XY4wtU=","KNz8obfBglIfZZ8oXDHzHOqsrgOcLsm\u002BNZPmnNIlemg="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +0,0 @@
|
||||
{"GlobalPropertiesHash":"q+hAwBrcPnVtSgGz/Kp6kP1qVOxpDSxk/3MpYyK9dDk=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["eZ2qkZPO/MYAX3sbEVnqz208bqpLunsDjAZG7XY4wtU=","KNz8obfBglIfZZ8oXDHzHOqsrgOcLsm\u002BNZPmnNIlemg="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +0,0 @@
|
||||
{"Version":1,"ManifestType":"Build","Endpoints":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"Version":1,"Hash":"Zp2AayENaHwwegD1S3er2B42GEf0rjybz+5iZ/TMps0=","Source":"TravelMate.Backend","BasePath":"/","Mode":"Root","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]}
|
||||
@@ -1 +0,0 @@
|
||||
Zp2AayENaHwwegD1S3er2B42GEf0rjybz+5iZ/TMps0=
|
||||
@@ -1,103 +0,0 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj",
|
||||
"projectName": "TravelMate.Backend",
|
||||
"projectPath": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj",
|
||||
"packagesPath": "/Users/dayronvanleemput/.nuget/packages/",
|
||||
"outputPath": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/Users/dayronvanleemput/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"/usr/local/share/dotnet/library-packs": {},
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"DotNetEnv": {
|
||||
"target": "Package",
|
||||
"version": "[3.1.1, )"
|
||||
},
|
||||
"Google.Apis.AnalyticsData.v1beta": {
|
||||
"target": "Package",
|
||||
"version": "[1.68.0.3608, )"
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.1, )"
|
||||
},
|
||||
"Swashbuckle.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[6.5.0, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"downloadDependencies": [
|
||||
{
|
||||
"name": "Microsoft.AspNetCore.App.Ref",
|
||||
"version": "[8.0.22, 8.0.22]"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.NETCore.App.Host.osx-arm64",
|
||||
"version": "[8.0.22, 8.0.22]"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.NETCore.App.Ref",
|
||||
"version": "[8.0.22, 8.0.22]"
|
||||
}
|
||||
],
|
||||
"frameworkReferences": {
|
||||
"Microsoft.AspNetCore.App": {
|
||||
"privateAssets": "none"
|
||||
},
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "/usr/local/share/dotnet/sdk/10.0.100/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/dayronvanleemput/.nuget/packages/</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/dayronvanleemput/.nuget/packages/</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="/Users/dayronvanleemput/.nuget/packages/" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)swashbuckle.aspnetcore/6.5.0/build/Swashbuckle.AspNetCore.props" Condition="Exists('$(NuGetPackageRoot)swashbuckle.aspnetcore/6.5.0/build/Swashbuckle.AspNetCore.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server/6.0.5/build/Microsoft.Extensions.ApiDescription.Server.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "6JHw9LAEeo0=",
|
||||
"success": true,
|
||||
"projectFilePath": "/Users/dayronvanleemput/Documents/Coding/travel_mate/backend/TravelMate.Backend.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"/Users/dayronvanleemput/.nuget/packages/dotnetenv/3.1.1/dotnetenv.3.1.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/google.apis/1.68.0/google.apis.1.68.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/google.apis.analyticsdata.v1beta/1.68.0.3608/google.apis.analyticsdata.v1beta.1.68.0.3608.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/google.apis.auth/1.68.0/google.apis.auth.1.68.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/google.apis.core/1.68.0/google.apis.core.1.68.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.aspnetcore.openapi/8.0.1/microsoft.aspnetcore.openapi.8.0.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.configuration/1.1.2/microsoft.extensions.configuration.1.1.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.configuration.abstractions/1.1.2/microsoft.extensions.configuration.abstractions.1.1.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.extensions.primitives/1.1.1/microsoft.extensions.primitives.1.1.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.platforms/1.1.1/microsoft.netcore.platforms.1.1.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.targets/1.1.3/microsoft.netcore.targets.1.1.3.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.openapi/1.4.3/microsoft.openapi.1.4.3.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.win32.primitives/4.3.0/microsoft.win32.primitives.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/netstandard.library/1.6.1/netstandard.library.1.6.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system/4.3.0/runtime.native.system.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.io.compression/4.3.0/runtime.native.system.io.compression.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.net.http/4.3.0/runtime.native.system.net.http.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.security.cryptography.apple/4.3.0/runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.native.system.security.cryptography.openssl/4.3.2/runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.2/runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/sprache/2.3.1/sprache.2.3.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore/6.5.0/swashbuckle.aspnetcore.6.5.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore.swagger/6.5.0/swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore.swaggergen/6.5.0/swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/swashbuckle.aspnetcore.swaggerui/6.5.0/swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.appcontext/4.3.0/system.appcontext.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.buffers/4.3.0/system.buffers.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.codedom/7.0.0/system.codedom.7.0.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.collections/4.3.0/system.collections.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.collections.concurrent/4.3.0/system.collections.concurrent.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.console/4.3.0/system.console.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.debug/4.3.0/system.diagnostics.debug.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.diagnosticsource/4.3.0/system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.tools/4.3.0/system.diagnostics.tools.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.diagnostics.tracing/4.3.0/system.diagnostics.tracing.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.globalization/4.3.0/system.globalization.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.globalization.calendars/4.3.0/system.globalization.calendars.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.globalization.extensions/4.3.0/system.globalization.extensions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.io/4.3.0/system.io.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.io.compression/4.3.0/system.io.compression.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.io.compression.zipfile/4.3.0/system.io.compression.zipfile.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.io.filesystem/4.3.0/system.io.filesystem.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.io.filesystem.primitives/4.3.0/system.io.filesystem.primitives.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.linq/4.3.0/system.linq.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.linq.expressions/4.3.0/system.linq.expressions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.management/7.0.2/system.management.7.0.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.net.http/4.3.4/system.net.http.4.3.4.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.net.primitives/4.3.0/system.net.primitives.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.net.sockets/4.3.0/system.net.sockets.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.objectmodel/4.3.0/system.objectmodel.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.private.uri/4.3.2/system.private.uri.4.3.2.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection/4.3.0/system.reflection.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection.emit/4.3.0/system.reflection.emit.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection.emit.ilgeneration/4.3.0/system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection.emit.lightweight/4.3.0/system.reflection.emit.lightweight.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection.extensions/4.3.0/system.reflection.extensions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection.primitives/4.3.0/system.reflection.primitives.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.reflection.typeextensions/4.3.0/system.reflection.typeextensions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.resources.resourcemanager/4.3.0/system.resources.resourcemanager.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime/4.3.1/system.runtime.4.3.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime.compilerservices.unsafe/4.3.0/system.runtime.compilerservices.unsafe.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime.extensions/4.3.0/system.runtime.extensions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime.handles/4.3.0/system.runtime.handles.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime.interopservices/4.3.0/system.runtime.interopservices.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime.interopservices.runtimeinformation/4.3.0/system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.runtime.numerics/4.3.0/system.runtime.numerics.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.algorithms/4.3.0/system.security.cryptography.algorithms.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.cng/4.3.0/system.security.cryptography.cng.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.csp/4.3.0/system.security.cryptography.csp.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.encoding/4.3.0/system.security.cryptography.encoding.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.openssl/4.3.0/system.security.cryptography.openssl.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.primitives/4.3.0/system.security.cryptography.primitives.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.security.cryptography.x509certificates/4.3.0/system.security.cryptography.x509certificates.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.text.encoding/4.3.0/system.text.encoding.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.text.encoding.extensions/4.3.0/system.text.encoding.extensions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.text.regularexpressions/4.3.1/system.text.regularexpressions.4.3.1.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.threading/4.3.0/system.threading.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.threading.tasks/4.3.0/system.threading.tasks.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.threading.tasks.extensions/4.3.0/system.threading.tasks.extensions.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.threading.timer/4.3.0/system.threading.timer.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.xml.readerwriter/4.3.0/system.xml.readerwriter.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/system.xml.xdocument/4.3.0/system.xml.xdocument.4.3.0.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.aspnetcore.app.ref/8.0.22/microsoft.aspnetcore.app.ref.8.0.22.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.app.host.osx-arm64/8.0.22/microsoft.netcore.app.host.osx-arm64.8.0.22.nupkg.sha512",
|
||||
"/Users/dayronvanleemput/.nuget/packages/microsoft.netcore.app.ref/8.0.22/microsoft.netcore.app.ref.8.0.22.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"dateRanges": [
|
||||
{
|
||||
"startDate": "2023-01-01",
|
||||
"endDate": "today"
|
||||
}
|
||||
],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "totalUsers"
|
||||
},
|
||||
{
|
||||
"name": "eventCount"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -490,7 +490,11 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 456VVYXDFN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -499,6 +503,8 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = myiphone;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -673,7 +679,11 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 456VVYXDFN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -682,6 +692,8 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = myiphone;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -696,7 +708,11 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 456VVYXDFN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -705,6 +721,8 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = myiphone;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -7,12 +7,15 @@ import '../../blocs/activity/activity_state.dart';
|
||||
import '../../models/trip.dart';
|
||||
import '../../models/activity.dart';
|
||||
import '../../services/activity_cache_service.dart';
|
||||
import '../../services/activity_places_service.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../loading/laoding_content.dart';
|
||||
import '../../blocs/user/user_bloc.dart';
|
||||
import '../../blocs/user/user_state.dart';
|
||||
import '../../services/error_service.dart';
|
||||
import 'activity_detail_dialog.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
class ActivitiesPage extends StatefulWidget {
|
||||
final Trip trip;
|
||||
@@ -38,6 +41,14 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
List<Activity> _approvedActivities = [];
|
||||
bool _isLoadingTripActivities = false;
|
||||
|
||||
// Autocomplete variables
|
||||
List<Map<String, String>> _suggestions = [];
|
||||
final LayerLink _layerLink = LayerLink();
|
||||
OverlayEntry? _overlayEntry;
|
||||
final FocusNode _searchFocusNode = FocusNode();
|
||||
Timer? _debounceTimer;
|
||||
bool _isLoadingSuggestions = false;
|
||||
|
||||
bool _autoReloadInProgress =
|
||||
false; // Protection contre les rechargements en boucle
|
||||
int _lastAutoReloadTriggerCount =
|
||||
@@ -104,6 +115,9 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
_searchController.dispose();
|
||||
_searchFocusNode.dispose();
|
||||
_debounceTimer?.cancel();
|
||||
_hideSuggestions();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -277,42 +291,204 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
}
|
||||
|
||||
Widget _buildSearchBar(ThemeData theme) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Rechercher restaurants, musées...',
|
||||
hintStyle: TextStyle(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
focusNode: _searchFocusNode,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Rechercher restaurants, musées...',
|
||||
hintStyle: TextStyle(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Icons.search,
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
suffixIcon: _isLoadingSuggestions
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
onChanged: _onSearchChanged,
|
||||
onSubmitted: (value) {
|
||||
_hideSuggestions();
|
||||
if (value.isNotEmpty) {
|
||||
_performSearch(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Icons.search,
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
if (value.isNotEmpty) {
|
||||
_performSearch(value);
|
||||
}
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSearchChanged(String value) {
|
||||
if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel();
|
||||
|
||||
if (value.isEmpty) {
|
||||
_hideSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 300), () async {
|
||||
setState(() {
|
||||
_isLoadingSuggestions = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final suggestions = await ActivityPlacesService().fetchSuggestions(
|
||||
query: value,
|
||||
lat: widget.trip.latitude,
|
||||
lng: widget.trip.longitude,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_suggestions = suggestions;
|
||||
_isLoadingSuggestions = false;
|
||||
});
|
||||
|
||||
if (_suggestions.isNotEmpty) {
|
||||
_showSuggestions();
|
||||
} else {
|
||||
_hideSuggestions();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoadingSuggestions = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _showSuggestions() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
void _hideSuggestions() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
}
|
||||
|
||||
OverlayEntry _createOverlayEntry() {
|
||||
final renderBox = context.findRenderObject() as RenderBox;
|
||||
final theme = Theme.of(context);
|
||||
final size = renderBox.size;
|
||||
final width = size.width - 32; // padding 16 * 2
|
||||
|
||||
return OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
width: width,
|
||||
child: CompositedTransformFollower(
|
||||
link: _layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: const Offset(16, 80), // Adjust vertical offset
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: theme.colorScheme.surface,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 250),
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: _suggestions.length,
|
||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = _suggestions[index];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.location_on_outlined),
|
||||
title: Text(
|
||||
suggestion['description'] ?? '',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onTap: () {
|
||||
_selectSuggestion(suggestion);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectSuggestion(Map<String, String> suggestion) async {
|
||||
_hideSuggestions();
|
||||
_searchController.text = suggestion['description']!;
|
||||
_searchFocusNode.unfocus();
|
||||
|
||||
// Passer à l'onglet "Suggestion" si ce n'est pas déjà fait
|
||||
if (_tabController.index != 2) {
|
||||
_tabController.animateTo(2);
|
||||
}
|
||||
|
||||
// Charger l'activité spécifique via le Bloc ou Service
|
||||
// Ici on va utiliser le Bloc pour ajouter l'activité aux résultats de recherche
|
||||
// Pour ça, il faudrait idéalement un événement "LoadSingleActivity" dans le Bloc
|
||||
// Mais pour faire simple et rapide, on peut faire une recherche "exacte" ou hack:
|
||||
// On charge l'activité manuellement et on l'ajoute comme si c'était un résultat de recherche.
|
||||
|
||||
setState(() {
|
||||
_isLoadingSuggestions = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final activity = await ActivityPlacesService().getActivityByPlaceId(
|
||||
placeId: suggestion['placeId']!,
|
||||
tripId: widget.trip.id!,
|
||||
);
|
||||
|
||||
if (mounted && activity != null) {
|
||||
// Injecter ce résultat unique dans le Bloc
|
||||
context.read<ActivityBloc>().add(
|
||||
RestoreCachedSearchResults(searchResults: [activity]),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ErrorService().showError(message: "Impossible de charger l'activité");
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoadingSuggestions = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCategoryTabs(ThemeData theme) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -637,6 +813,23 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
activity.name.toLowerCase().trim(),
|
||||
);
|
||||
|
||||
// Calculer la distance si c'est une suggestion Google et que le voyage a des coordonnées
|
||||
double? distanceInKm;
|
||||
if (isGoogleSuggestion &&
|
||||
widget.trip.hasCoordinates &&
|
||||
activity.latitude != null &&
|
||||
activity.longitude != null) {
|
||||
final distanceInMeters = Geolocator.distanceBetween(
|
||||
widget.trip.latitude!,
|
||||
widget.trip.longitude!,
|
||||
activity.latitude!,
|
||||
activity.longitude!,
|
||||
);
|
||||
distanceInKm = distanceInMeters / 1000;
|
||||
}
|
||||
|
||||
final isFar = distanceInKm != null && distanceInKm > 50;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
@@ -709,6 +902,40 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isFar)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.error.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: theme.colorScheme.error,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Activité éloignée : ${distanceInKm!.toStringAsFixed(0)} km du lieu du voyage',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onErrorContainer,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
// Icône de catégorie
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import '../../services/place_image_service.dart';
|
||||
import '../../services/trip_geocoding_service.dart';
|
||||
import '../../services/logger_service.dart';
|
||||
import '../../firebase_options.dart';
|
||||
|
||||
/// Create trip content widget for trip creation and editing functionality.
|
||||
///
|
||||
@@ -86,16 +87,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
||||
|
||||
/// Google Maps API key for location services
|
||||
static String get _apiKey {
|
||||
if (Platform.isAndroid) {
|
||||
return dotenv.env['GOOGLE_MAPS_API_KEY_ANDROID'] ??
|
||||
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
|
||||
'';
|
||||
} else if (Platform.isIOS) {
|
||||
return dotenv.env['GOOGLE_MAPS_API_KEY_IOS'] ??
|
||||
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
|
||||
'';
|
||||
}
|
||||
return dotenv.env['GOOGLE_MAPS_API_KEY'] ?? '';
|
||||
return DefaultFirebaseOptions.currentPlatform.apiKey;
|
||||
}
|
||||
|
||||
/// Participant management
|
||||
@@ -135,7 +127,11 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
||||
}
|
||||
}
|
||||
|
||||
bool _isProgrammaticUpdate = false;
|
||||
|
||||
void _onLocationChanged() {
|
||||
if (_isProgrammaticUpdate) return;
|
||||
|
||||
final query = _locationController.text.trim();
|
||||
|
||||
if (query.length < 2) {
|
||||
@@ -215,15 +211,18 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
||||
|
||||
if (_placeSuggestions.isEmpty) return;
|
||||
|
||||
final overlay = Overlay.of(context);
|
||||
|
||||
// Calculer la largeur correcte en fonction du padding parent (16 margin + 24 padding = 40 de chaque côté)
|
||||
final width = MediaQuery.of(context).size.width - 80;
|
||||
|
||||
_suggestionsOverlay = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
width:
|
||||
MediaQuery.of(context).size.width -
|
||||
32, // Largeur du champ avec padding
|
||||
width: width,
|
||||
child: CompositedTransformFollower(
|
||||
link: _layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: const Offset(0, 60), // Position sous le champ
|
||||
targetAnchor: Alignment.bottomLeft,
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
@@ -236,6 +235,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
||||
),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: _placeSuggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = _placeSuggestions[index];
|
||||
@@ -256,7 +256,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_suggestionsOverlay!);
|
||||
overlay.insert(_suggestionsOverlay!);
|
||||
}
|
||||
|
||||
void _hideSuggestions() {
|
||||
@@ -265,7 +265,10 @@ class _CreateTripContentState extends State<CreateTripContent> {
|
||||
}
|
||||
|
||||
void _selectSuggestion(PlaceSuggestion suggestion) {
|
||||
_isProgrammaticUpdate = true;
|
||||
_locationController.text = suggestion.description;
|
||||
_isProgrammaticUpdate = false;
|
||||
|
||||
_hideSuggestions();
|
||||
setState(() {
|
||||
_placeSuggestions = [];
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import '../../firebase_options.dart';
|
||||
import '../../services/error_service.dart';
|
||||
import '../../services/map_navigation_service.dart';
|
||||
import '../../services/logger_service.dart';
|
||||
@@ -22,6 +23,7 @@ class MapContent extends StatefulWidget {
|
||||
class _MapContentState extends State<MapContent> {
|
||||
GoogleMapController? _mapController;
|
||||
LatLng _initialPosition = const LatLng(48.8566, 2.3522);
|
||||
LatLng? _currentMapCenter;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
bool _isLoadingLocation = false;
|
||||
bool _isSearching = false;
|
||||
@@ -33,16 +35,7 @@ class _MapContentState extends State<MapContent> {
|
||||
List<PlaceSuggestion> _suggestions = [];
|
||||
|
||||
static String get _apiKey {
|
||||
if (Platform.isAndroid) {
|
||||
return dotenv.env['GOOGLE_MAPS_API_KEY_ANDROID'] ??
|
||||
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
|
||||
'';
|
||||
} else if (Platform.isIOS) {
|
||||
return dotenv.env['GOOGLE_MAPS_API_KEY_IOS'] ??
|
||||
dotenv.env['GOOGLE_MAPS_API_KEY'] ??
|
||||
'';
|
||||
}
|
||||
return dotenv.env['GOOGLE_MAPS_API_KEY'] ?? '';
|
||||
return DefaultFirebaseOptions.currentPlatform.apiKey;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -321,20 +314,30 @@ class _MapContentState extends State<MapContent> {
|
||||
}
|
||||
}
|
||||
|
||||
// Créer une icône personnalisée à partir de l'icône Material
|
||||
Future<BitmapDescriptor> _createCustomMarkerIcon() async {
|
||||
// Créer une icône personnalisée
|
||||
Future<BitmapDescriptor> _createMarkerIcon(
|
||||
IconData iconData,
|
||||
Color color, {
|
||||
double size = 60.0,
|
||||
}) async {
|
||||
final pictureRecorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(pictureRecorder);
|
||||
const size = 80.0;
|
||||
|
||||
// Dessiner l'icône person_pin_circle en bleu
|
||||
final iconPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||
iconPainter.text = TextSpan(
|
||||
text: String.fromCharCode(Icons.person_pin_circle.codePoint),
|
||||
text: String.fromCharCode(iconData.codePoint),
|
||||
style: TextStyle(
|
||||
fontSize: 70,
|
||||
fontFamily: Icons.person_pin_circle.fontFamily,
|
||||
color: Colors.blue[700],
|
||||
fontSize: size,
|
||||
fontFamily: iconData.fontFamily,
|
||||
package: iconData.fontPackage,
|
||||
color: color,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: const Offset(1, 1),
|
||||
blurRadius: 2,
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
iconPainter.layout();
|
||||
@@ -364,8 +367,12 @@ class _MapContentState extends State<MapContent> {
|
||||
),
|
||||
);
|
||||
|
||||
// Créer l'icône personnalisée
|
||||
final icon = await _createCustomMarkerIcon();
|
||||
// Créer l'icône personnalisée (plus petite: 60 au lieu de 80)
|
||||
final icon = await _createMarkerIcon(
|
||||
Icons.person_pin_circle,
|
||||
Colors.blue[700]!,
|
||||
size: 60.0,
|
||||
);
|
||||
|
||||
// Ajouter le marqueur avec l'icône
|
||||
setState(() {
|
||||
@@ -374,10 +381,7 @@ class _MapContentState extends State<MapContent> {
|
||||
markerId: const MarkerId('user_location'),
|
||||
position: position,
|
||||
icon: icon,
|
||||
anchor: const Offset(
|
||||
0.5,
|
||||
0.85,
|
||||
), // Ancrer au bas de l'icône (le point du pin)
|
||||
anchor: const Offset(0.5, 0.85), // Ancrer au bas de l'icône
|
||||
infoWindow: InfoWindow(
|
||||
title: 'Ma position',
|
||||
snippet:
|
||||
@@ -401,10 +405,21 @@ class _MapContentState extends State<MapContent> {
|
||||
});
|
||||
|
||||
try {
|
||||
final apiKey = _apiKey;
|
||||
LoggerService.info('MapContent: Searching places with query "$query"');
|
||||
if (apiKey.isEmpty) {
|
||||
LoggerService.error('MapContent: API Key is empty!');
|
||||
} else {
|
||||
// Log first few chars to verify correct key is loaded without leaking full key
|
||||
LoggerService.info(
|
||||
'MapContent: Using API Key starting with ${apiKey.substring(0, 5)}...',
|
||||
);
|
||||
}
|
||||
|
||||
final url = Uri.parse(
|
||||
'https://maps.googleapis.com/maps/api/place/autocomplete/json'
|
||||
'?input=${Uri.encodeComponent(query)}'
|
||||
'&key=$_apiKey'
|
||||
'&key=$apiKey'
|
||||
'&language=fr',
|
||||
);
|
||||
|
||||
@@ -412,11 +427,21 @@ class _MapContentState extends State<MapContent> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
LoggerService.info(
|
||||
'MapContent: Response status code: ${response.statusCode}',
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
final status = data['status'];
|
||||
|
||||
if (data['status'] == 'OK') {
|
||||
LoggerService.info('MapContent: API Status: $status');
|
||||
|
||||
if (status == 'OK') {
|
||||
final predictions = data['predictions'] as List;
|
||||
LoggerService.info(
|
||||
'MapContent: Found ${predictions.length} predictions',
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_suggestions = predictions
|
||||
@@ -430,13 +455,19 @@ class _MapContentState extends State<MapContent> {
|
||||
_isSearching = false;
|
||||
});
|
||||
} else {
|
||||
LoggerService.error(
|
||||
'MapContent: API Error: $status - ${data['error_message'] ?? "No error message"}',
|
||||
);
|
||||
setState(() {
|
||||
_suggestions = [];
|
||||
_isSearching = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
LoggerService.error('MapContent: HTTP Error ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
LoggerService.error('MapContent: Exception during search: $e');
|
||||
_showError('Erreur lors de la recherche de lieux: $e');
|
||||
setState(() {
|
||||
_isSearching = false;
|
||||
@@ -444,6 +475,128 @@ class _MapContentState extends State<MapContent> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _performTextSearch(String query) async {
|
||||
if (query.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
_isSearching = true;
|
||||
_suggestions = []; // Hide suggestions
|
||||
});
|
||||
|
||||
try {
|
||||
final apiKey = _apiKey;
|
||||
// Utiliser le centre actuel de la carte ou la position initiale
|
||||
final center = _currentMapCenter ?? _initialPosition;
|
||||
|
||||
final url = Uri.parse(
|
||||
'https://maps.googleapis.com/maps/api/place/textsearch/json'
|
||||
'?query=${Uri.encodeComponent(query)}'
|
||||
'&location=${center.latitude},${center.longitude}'
|
||||
'&radius=5000' // Rechercher dans un rayon de 5km autour du centre
|
||||
'&key=$apiKey'
|
||||
'&language=fr',
|
||||
);
|
||||
|
||||
final response = await http.get(url);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
|
||||
if (data['status'] == 'OK') {
|
||||
final results = data['results'] as List;
|
||||
final List<Marker> newMarkers = [];
|
||||
|
||||
// Garder le marqueur de position utilisateur
|
||||
final userMarker = _markers
|
||||
.where((m) => m.markerId.value == 'user_location')
|
||||
.toList();
|
||||
|
||||
double minLat = center.latitude;
|
||||
double maxLat = center.latitude;
|
||||
double minLng = center.longitude;
|
||||
double maxLng = center.longitude;
|
||||
|
||||
// Créer le marqueur rouge standard personnalisé
|
||||
final markerIcon = await _createMarkerIcon(
|
||||
Icons.location_on,
|
||||
Colors.red,
|
||||
size: 50.0,
|
||||
);
|
||||
|
||||
for (final place in results) {
|
||||
final geometry = place['geometry']['location'];
|
||||
final lat = geometry['lat'];
|
||||
final lng = geometry['lng'];
|
||||
final name = place['name'];
|
||||
final placeId = place['place_id'];
|
||||
|
||||
final position = LatLng(lat, lng);
|
||||
|
||||
// Mettre à jour les bornes
|
||||
if (lat < minLat) minLat = lat;
|
||||
if (lat > maxLat) maxLat = lat;
|
||||
if (lng < minLng) minLng = lng;
|
||||
if (lng > maxLng) maxLng = lng;
|
||||
|
||||
newMarkers.add(
|
||||
Marker(
|
||||
markerId: MarkerId(placeId),
|
||||
position: position,
|
||||
icon: markerIcon,
|
||||
anchor: const Offset(0.5, 0.85), // Standard anchor for pins
|
||||
infoWindow: InfoWindow(
|
||||
title: name,
|
||||
snippet: place['formatted_address'] ?? 'Lieu trouvé',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_markers.clear();
|
||||
if (userMarker.isNotEmpty) {
|
||||
_markers.add(userMarker.first);
|
||||
}
|
||||
_markers.addAll(newMarkers);
|
||||
_isSearching = false;
|
||||
});
|
||||
|
||||
// Ajuster la caméra pour montrer tous les résultats
|
||||
if (newMarkers.isNotEmpty) {
|
||||
_mapController?.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(
|
||||
LatLngBounds(
|
||||
southwest: LatLng(minLat, minLng),
|
||||
northeast: LatLng(maxLat, maxLng),
|
||||
),
|
||||
50.0, // padding
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
FocusScope.of(context).unfocus();
|
||||
} else {
|
||||
_showError('Aucun résultat trouvé pour "$query"');
|
||||
setState(() {
|
||||
_isSearching = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_showError('Erreur lors de la recherche: ${response.statusCode}');
|
||||
setState(() {
|
||||
_isSearching = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
_showError('Erreur lors de la recherche: $e');
|
||||
setState(() {
|
||||
_isSearching = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectPlace(PlaceSuggestion suggestion) async {
|
||||
setState(() {
|
||||
_isSearching = true;
|
||||
@@ -467,13 +620,21 @@ class _MapContentState extends State<MapContent> {
|
||||
final data = json.decode(response.body);
|
||||
|
||||
if (data['status'] == 'OK') {
|
||||
final location = data['result']['geometry']['location'];
|
||||
final result = data['result'];
|
||||
final location = result['geometry']['location'];
|
||||
final lat = location['lat'];
|
||||
final lng = location['lng'];
|
||||
final name = data['result']['name'];
|
||||
final name = result['name'];
|
||||
|
||||
final newPosition = LatLng(lat, lng);
|
||||
|
||||
// Utiliser le marqueur rouge standard personnalisé
|
||||
final markerIcon = await _createMarkerIcon(
|
||||
Icons.location_on,
|
||||
Colors.red,
|
||||
size: 50.0,
|
||||
);
|
||||
|
||||
// Ajouter un marqueur pour le lieu recherché (ne pas supprimer le marqueur de position)
|
||||
setState(() {
|
||||
// Garder le marqueur de position utilisateur
|
||||
@@ -484,6 +645,8 @@ class _MapContentState extends State<MapContent> {
|
||||
Marker(
|
||||
markerId: MarkerId(suggestion.placeId),
|
||||
position: newPosition,
|
||||
icon: markerIcon,
|
||||
anchor: const Offset(0.5, 0.85),
|
||||
infoWindow: InfoWindow(title: name),
|
||||
),
|
||||
);
|
||||
@@ -533,6 +696,9 @@ class _MapContentState extends State<MapContent> {
|
||||
onMapCreated: (GoogleMapController controller) {
|
||||
_mapController = controller;
|
||||
},
|
||||
onCameraMove: (CameraPosition position) {
|
||||
_currentMapCenter = position.target;
|
||||
},
|
||||
markers: _markers,
|
||||
circles: _circles,
|
||||
myLocationEnabled: false,
|
||||
@@ -664,6 +830,10 @@ class _MapContentState extends State<MapContent> {
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: (value) {
|
||||
_performTextSearch(value);
|
||||
},
|
||||
onChanged: (value) {
|
||||
// Ne pas rechercher si c'est juste le remplissage initial
|
||||
if (widget.initialSearchQuery != null &&
|
||||
|
||||
140
lib/components/whats_new_dialog.dart
Normal file
140
lib/components/whats_new_dialog.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../services/whats_new_service.dart';
|
||||
|
||||
class WhatsNewDialog extends StatelessWidget {
|
||||
final VoidCallback onDismiss;
|
||||
final List<WhatsNewItem> features;
|
||||
|
||||
const WhatsNewDialog({
|
||||
super.key,
|
||||
required this.onDismiss,
|
||||
required this.features,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.auto_awesome,
|
||||
color: theme.colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Quoi de neuf ?',
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Features List
|
||||
Flexible(
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: features.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 16),
|
||||
itemBuilder: (context, index) {
|
||||
final feature = features[index];
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
feature.icon,
|
||||
color: theme.colorScheme.primary,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
feature.title,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
feature.description,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// Marquer comme vu via le service
|
||||
WhatsNewService().markCurrentVersionAsSeen();
|
||||
onDismiss();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'C\'est parti !',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WhatsNewItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
|
||||
const WhatsNewItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
});
|
||||
}
|
||||
@@ -27,18 +27,6 @@ class DefaultFirebaseOptions {
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
return windows;
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
@@ -63,15 +51,4 @@ class DefaultFirebaseOptions {
|
||||
iosClientId: '521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com',
|
||||
iosBundleId: 'com.example.travelMate',
|
||||
);
|
||||
|
||||
static const FirebaseOptions windows = FirebaseOptions(
|
||||
apiKey: 'AIzaSyC4t-WOvp22zns9b9t58urznsNAhSHRAag',
|
||||
appId: '1:521527250907:web:53ff98bcdb8c218f7da1fe',
|
||||
messagingSenderId: '521527250907',
|
||||
projectId: 'travelmate-a47f5',
|
||||
authDomain: 'travelmate-a47f5.firebaseapp.com',
|
||||
storageBucket: 'travelmate-a47f5.firebasestorage.app',
|
||||
measurementId: 'G-J246Y7J61M',
|
||||
);
|
||||
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import '../blocs/auth/auth_event.dart';
|
||||
import '../services/error_service.dart';
|
||||
import '../services/notification_service.dart';
|
||||
import '../services/map_navigation_service.dart';
|
||||
import '../services/whats_new_service.dart';
|
||||
import '../components/whats_new_dialog.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
@@ -57,6 +59,45 @@ class _HomePageState extends State<HomePage> {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Vérifier les nouveautés
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _checkAndShowWhatsNew();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkAndShowWhatsNew() async {
|
||||
final service = WhatsNewService();
|
||||
if (await service.shouldShowWhatsNew()) {
|
||||
if (!mounted) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => WhatsNewDialog(
|
||||
onDismiss: () => Navigator.pop(context),
|
||||
features: const [
|
||||
WhatsNewItem(
|
||||
icon: Icons.map_outlined,
|
||||
title: 'Recherche globale',
|
||||
description:
|
||||
'Recherchez des restaurants, musées et plus encore directement depuis la carte.',
|
||||
),
|
||||
WhatsNewItem(
|
||||
icon: Icons.search,
|
||||
title: 'Autocomplétion améliorée',
|
||||
description:
|
||||
'Découvrez des suggestions intelligentes lors de la recherche de lieux et d\'activités.',
|
||||
),
|
||||
WhatsNewItem(
|
||||
icon: Icons.warning_amber_rounded,
|
||||
title: 'Alertes de distance',
|
||||
description:
|
||||
'Soyez averti si une activité est trop éloignée de votre lieu de séjour.',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPage(int index) {
|
||||
|
||||
@@ -365,7 +365,7 @@ class ActivityPlacesService {
|
||||
final encodedQuery = Uri.encodeComponent(query);
|
||||
final url =
|
||||
'https://maps.googleapis.com/maps/api/place/textsearch/json'
|
||||
'?query=$encodedQuery in $destination'
|
||||
'?query=$encodedQuery'
|
||||
'&location=${coordinates['lat']},${coordinates['lng']}'
|
||||
'&radius=$radius'
|
||||
'&key=$_apiKey'
|
||||
@@ -654,4 +654,72 @@ class ActivityPlacesService {
|
||||
throw Exception('Erreur HTTP ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère des suggestions d'autocomplétion
|
||||
Future<List<Map<String, String>>> fetchSuggestions({
|
||||
required String query,
|
||||
double? lat,
|
||||
double? lng,
|
||||
}) async {
|
||||
if (query.isEmpty) return [];
|
||||
|
||||
try {
|
||||
String url =
|
||||
'https://maps.googleapis.com/maps/api/place/autocomplete/json'
|
||||
'?input=${Uri.encodeComponent(query)}'
|
||||
'&key=$_apiKey'
|
||||
'&language=fr';
|
||||
|
||||
if (lat != null && lng != null) {
|
||||
url += '&location=$lat,$lng&radius=50000'; // 50km bias
|
||||
}
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
if (data['status'] == 'OK') {
|
||||
return (data['predictions'] as List).map<Map<String, String>>((p) {
|
||||
return {
|
||||
'description': p['description'] as String,
|
||||
'placeId': p['place_id'] as String,
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
LoggerService.error('ActivityPlacesService: Erreur autocomplete: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Récupère une activité via son Place ID
|
||||
Future<Activity?> getActivityByPlaceId({
|
||||
required String placeId,
|
||||
required String tripId,
|
||||
}) async {
|
||||
try {
|
||||
final details = await _getPlaceDetails(placeId);
|
||||
if (details == null) return null;
|
||||
|
||||
// Créer une map simulant la structure "place" attendue par _convertPlaceToActivity
|
||||
// Note: _getPlaceDetails retourne "result", qui est déjà ce qu'on veut,
|
||||
// mais _convertPlaceToActivity attend le format "search result" qui a geometry au premier niveau.
|
||||
// Heureusement _getPlaceDetails retourne une structure compatible pour geometry/photos etc.
|
||||
|
||||
// On doit s'assurer d'avoir les types pour déterminer la catégorie
|
||||
final types = List<String>.from(details['types'] ?? []);
|
||||
final category = _determineCategoryFromTypes(types);
|
||||
|
||||
return await _convertPlaceToActivity(
|
||||
details, // details a la structure nécessaire (geometry, name, etc)
|
||||
tripId,
|
||||
category,
|
||||
);
|
||||
} catch (e) {
|
||||
LoggerService.error('ActivityPlacesService: Erreur get details: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
68
lib/services/whats_new_service.dart
Normal file
68
lib/services/whats_new_service.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../services/logger_service.dart';
|
||||
|
||||
class WhatsNewService {
|
||||
static const String _lastVersionKey = 'last_known_version';
|
||||
|
||||
/// Vérifie si le popup "Nouveautés" doit être affiché.
|
||||
///
|
||||
/// Retourne true si:
|
||||
/// - Ce n'est PAS une nouvelle installation
|
||||
/// - ET la version actuelle est plus récente que la version stockée
|
||||
Future<bool> shouldShowWhatsNew() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
final currentVersion = packageInfo.version;
|
||||
final lastVersion = prefs.getString(_lastVersionKey);
|
||||
|
||||
LoggerService.info(
|
||||
'WhatsNewService: Current=$currentVersion, Last=$lastVersion',
|
||||
);
|
||||
|
||||
// Cas 1: Première installation (lastVersion est null)
|
||||
if (lastVersion == null) {
|
||||
// On sauvegarde la version actuelle pour ne pas afficher le popup
|
||||
// la prochaine fois, et on retourne false maintenant.
|
||||
await prefs.setString(_lastVersionKey, currentVersion);
|
||||
LoggerService.info(
|
||||
'WhatsNewService: Fresh install detected. Marking version $currentVersion as read.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cas 2: Mise à jour (lastVersion != currentVersion)
|
||||
if (lastVersion != currentVersion) {
|
||||
// C'est une mise à jour, on doit afficher le popup.
|
||||
// On NE met PAS à jour la version ici, on attend que l'utilisateur ait vu le popup.
|
||||
LoggerService.info(
|
||||
'WhatsNewService: Update detected ($lastVersion -> $currentVersion). Showing popup.',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cas 3: Même version
|
||||
return false;
|
||||
} catch (e) {
|
||||
LoggerService.error('WhatsNewService: Error checking version: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Marque la version actuelle comme "Vue".
|
||||
/// À appeler quand l'utilisateur ferme le popup.
|
||||
Future<void> markCurrentVersionAsSeen() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
await prefs.setString(_lastVersionKey, packageInfo.version);
|
||||
LoggerService.info(
|
||||
'WhatsNewService: Version ${packageInfo.version} marked as seen.',
|
||||
);
|
||||
} catch (e) {
|
||||
LoggerService.error('WhatsNewService: Error marking seen: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2026.1.1+1
|
||||
version: 2026.1.3+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
Reference in New Issue
Block a user