Compare commits

..

28 Commits

Author SHA1 Message Date
Van Leemput Dayron
e665dea82a feat(activities): add autocomplete & what's new popup
All checks were successful
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m6s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 4m3s
Features:
- Add autocomplete support for Activity search with Google Places API.
- Add "What's New" popup system to showcase new features on app update.
- Implement logic to detect fresh installs vs updates.

Fixes:
- Switch API key handling to use Firebase config for Release mode support.
- Refactor map pins to be consistent (red pins).
- UI fixes on Create Trip page (overflow issues).

Refactor:
- Make WhatsNewDialog reusable by accepting features list as parameter.
2026-01-13 17:36:51 +01:00
Van Leemput Dayron
b511ec5df0 Update map autocompletion
All checks were successful
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m5s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 3m53s
2026-01-13 17:15:22 +01:00
Van Leemput Dayron
c0e53cd3f6 feat: enhance global search and map experience
All checks were successful
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m10s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 4m19s
- Global Activity Search:
  - Allow searching activities globally (not just in destination).
  - Add distance warning for activities > 50km away.
- Create Trip UI:
  - Fix destination suggestion list overflow.
  - Prevent suggestion list from reappearing after selection.
- Map:
  - Add generic text search support (e.g., "Restaurants") on 'Enter'.
  - Display multiple results for generic searches.
  - Resize markers (User 60.0, Places 50.0).
  - Standardize place markers to red pin.
2026-01-13 16:59:04 +01:00
Van Leemput Dayron
4fc7abc5b4 test 36
All checks were successful
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m16s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Successful in 3m54s
2026-01-11 19:37:53 +01:00
Van Leemput Dayron
e04bf6f405 test 35
Some checks failed
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 1m59s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Failing after 28s
2026-01-11 19:34:17 +01:00
Van Leemput Dayron
bed761401f test 34
Some checks failed
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 1m57s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Failing after 3m8s
2026-01-11 19:25:16 +01:00
Van Leemput Dayron
55463649b2 test 33
Some checks failed
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 1m55s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Failing after 3m14s
2026-01-11 19:15:48 +01:00
Van Leemput Dayron
62d2aa17be test 32
Some checks failed
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m16s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Failing after 3m2s
2026-01-11 19:08:23 +01:00
Van Leemput Dayron
acfb2259cc test 31
Some checks failed
Deploy TravelMate Final Fix / deploy-all (push) Failing after 1m12s
2026-01-11 00:12:56 +01:00
Van Leemput Dayron
d1d2194861 test 30
Some checks failed
Deploy TravelMate (Full Mobile) / deploy-android (push) Successful in 2m2s
Deploy TravelMate (Full Mobile) / deploy-ios (push) Failing after 3m28s
2026-01-11 00:04:59 +01:00
Van Leemput Dayron
b542f98a91 test 29
Some checks failed
Deploy TravelMate (Android & iOS) / deploy-android (push) Successful in 1m59s
Deploy TravelMate (Android & iOS) / deploy-ios (push) Failing after 3m27s
2026-01-10 23:57:25 +01:00
Van Leemput Dayron
f983b869ba test 28
Some checks failed
Deploy TravelMate (Android & iOS) / deploy-android (push) Successful in 2m19s
Deploy TravelMate (Android & iOS) / deploy-ios (push) Failing after 3m24s
2026-01-10 23:49:53 +01:00
Van Leemput Dayron
ead346bb1b test 27
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Has been cancelled
2026-01-10 23:44:33 +01:00
Van Leemput Dayron
8d27e771a7 test 26
All checks were successful
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m18s
2026-01-10 23:40:18 +01:00
Van Leemput Dayron
31fe3a4260 test 25
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m7s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m4s
2026-01-10 23:33:36 +01:00
Van Leemput Dayron
a2c6cd1d4f test 24
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m12s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m13s
2026-01-10 23:26:05 +01:00
Van Leemput Dayron
5fe9f371b2 test 23
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m0s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m7s
2026-01-10 23:19:44 +01:00
Van Leemput Dayron
b27fb7ed4c test 22
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m12s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m19s
2026-01-10 23:12:47 +01:00
Van Leemput Dayron
919ef611bc test 21
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m15s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m14s
2026-01-10 23:05:59 +01:00
Van Leemput Dayron
3eeed888b5 test 20
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m12s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m25s
2026-01-10 22:57:46 +01:00
Van Leemput Dayron
322f611522 test 19
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m21s
Deploy Flutter to Firebase iOS / deploy-ios (push) Failing after 3m21s
2026-01-10 22:50:49 +01:00
Van Leemput Dayron
a7d2634c5f test 18
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Has been cancelled
Deploy Flutter to Firebase iOS (Fixed) / deploy-ios (push) Failing after 2m59s
2026-01-10 22:39:47 +01:00
Van Leemput Dayron
ae125f1144 test 17
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m15s
Deploy Flutter to Firebase iOS (Fixed) / deploy-ios (push) Failing after 47s
2026-01-10 22:31:30 +01:00
Van Leemput Dayron
d66907f636 test 16
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m25s
Deploy Flutter to Firebase iOS (Final) / deploy-ios (push) Failing after 28s
2026-01-10 22:26:24 +01:00
Van Leemput Dayron
508d69a4f4 test 15
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m35s
Deploy Flutter to Firebase iOS (Final & Safe) / deploy-ios (push) Failing after 1m3s
2026-01-10 22:02:09 +01:00
Van Leemput Dayron
ee00415d23 Test 14
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 2m59s
Deploy Flutter to Firebase iOS (Final & Safe) / deploy-ios (push) Failing after 57s
2026-01-10 21:55:31 +01:00
Van Leemput Dayron
576b86fbbb Test 13
Some checks failed
Deploy Flutter to Firebase (Mac) / deploy-android (push) Successful in 3m12s
Deploy Flutter to Firebase iOS (Safe Mode) / deploy-ios (push) Failing after 1m1s
2026-01-10 21:46:59 +01:00
Van Leemput Dayron
918742293b test 12 2026-01-10 21:38:04 +01:00
70 changed files with 1042 additions and 7023 deletions

View File

@@ -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"

View File

@@ -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"

View 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
View File

@@ -51,3 +51,8 @@ app.*.map.json
firestore.rules firestore.rules
storage.rules storage.rules
/functions/node_modules /functions/node_modules
.idea
.vscode
.VSCodeCounter

View File

@@ -59,4 +59,4 @@ Travel Mate est une application mobile conçue pour simplifier l'organisation de
## 🚀 CI/CD & Déploiement ## 🚀 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**.

View File

@@ -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"]

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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"
}
}
}

View File

@@ -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
}
}
}

View File

@@ -1 +0,0 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}

View File

@@ -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

View File

@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -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.

View File

@@ -1 +0,0 @@
0aab77bc3ae598fb4e009b4ecda3aeffcfb640a524b7ecc7591b10a25aa5c92a

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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.

View File

@@ -1 +0,0 @@
564fc6ab625b9b4d1fd8dff0a4b24b86b698bd2655c9670266b6506278a91b74

View File

@@ -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

View File

@@ -1 +0,0 @@
f79f58c3a8e99961a1a88dcd08a90777e8913bfe44cb3f95b85212ac836b242e

Binary file not shown.

View File

@@ -1 +0,0 @@
{"GlobalPropertiesHash":"mgx0JdLooAvG36KewBSjBhnWYhsOqFG08xB+ZULwM34=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["eZ2qkZPO/MYAX3sbEVnqz208bqpLunsDjAZG7XY4wtU=","KNz8obfBglIfZZ8oXDHzHOqsrgOcLsm\u002BNZPmnNIlemg="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +0,0 @@
{"GlobalPropertiesHash":"q+hAwBrcPnVtSgGz/Kp6kP1qVOxpDSxk/3MpYyK9dDk=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["eZ2qkZPO/MYAX3sbEVnqz208bqpLunsDjAZG7XY4wtU=","KNz8obfBglIfZZ8oXDHzHOqsrgOcLsm\u002BNZPmnNIlemg="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +0,0 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}

View File

@@ -1 +0,0 @@
{"Version":1,"Hash":"Zp2AayENaHwwegD1S3er2B42GEf0rjybz+5iZ/TMps0=","Source":"TravelMate.Backend","BasePath":"/","Mode":"Root","ManifestType":"Build","ReferencedProjectsConfiguration":[],"DiscoveryPatterns":[],"Assets":[],"Endpoints":[]}

View File

@@ -1 +0,0 @@
Zp2AayENaHwwegD1S3er2B42GEf0rjybz+5iZ/TMps0=

View File

@@ -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"
}
}
}
}
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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": []
}

View File

@@ -1,16 +0,0 @@
{
"dateRanges": [
{
"startDate": "2023-01-01",
"endDate": "today"
}
],
"metrics": [
{
"name": "totalUsers"
},
{
"name": "eventCount"
}
]
}

View File

@@ -490,7 +490,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 456VVYXDFN;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -499,6 +503,8 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate; PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = myiphone;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@@ -673,7 +679,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 456VVYXDFN;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -682,6 +692,8 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate; PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = myiphone;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -696,7 +708,11 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 456VVYXDFN;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -705,6 +721,8 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate; PRODUCT_BUNDLE_IDENTIFIER = be.devdayronvl.TravelMate;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = myiphone;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";

View File

@@ -7,12 +7,15 @@ import '../../blocs/activity/activity_state.dart';
import '../../models/trip.dart'; import '../../models/trip.dart';
import '../../models/activity.dart'; import '../../models/activity.dart';
import '../../services/activity_cache_service.dart'; import '../../services/activity_cache_service.dart';
import '../../services/activity_places_service.dart';
import 'dart:async';
import '../loading/laoding_content.dart'; import '../loading/laoding_content.dart';
import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_bloc.dart';
import '../../blocs/user/user_state.dart'; import '../../blocs/user/user_state.dart';
import '../../services/error_service.dart'; import '../../services/error_service.dart';
import 'activity_detail_dialog.dart'; import 'activity_detail_dialog.dart';
import 'package:geolocator/geolocator.dart';
class ActivitiesPage extends StatefulWidget { class ActivitiesPage extends StatefulWidget {
final Trip trip; final Trip trip;
@@ -38,6 +41,14 @@ class _ActivitiesPageState extends State<ActivitiesPage>
List<Activity> _approvedActivities = []; List<Activity> _approvedActivities = [];
bool _isLoadingTripActivities = false; 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 = bool _autoReloadInProgress =
false; // Protection contre les rechargements en boucle false; // Protection contre les rechargements en boucle
int _lastAutoReloadTriggerCount = int _lastAutoReloadTriggerCount =
@@ -104,6 +115,9 @@ class _ActivitiesPageState extends State<ActivitiesPage>
void dispose() { void dispose() {
_tabController.dispose(); _tabController.dispose();
_searchController.dispose(); _searchController.dispose();
_searchFocusNode.dispose();
_debounceTimer?.cancel();
_hideSuggestions();
super.dispose(); super.dispose();
} }
@@ -277,42 +291,204 @@ class _ActivitiesPageState extends State<ActivitiesPage>
} }
Widget _buildSearchBar(ThemeData theme) { Widget _buildSearchBar(ThemeData theme) {
return Container( return CompositedTransformTarget(
padding: const EdgeInsets.all(16), link: _layerLink,
child: Container( child: Container(
decoration: BoxDecoration( padding: const EdgeInsets.all(16),
color: theme.colorScheme.surfaceContainerHighest.withValues( child: Column(
alpha: 0.3, children: [
), Container(
borderRadius: BorderRadius.circular(12), decoration: BoxDecoration(
), color: theme.colorScheme.surfaceContainerHighest.withValues(
child: TextField( alpha: 0.3,
controller: _searchController, ),
decoration: InputDecoration( borderRadius: BorderRadius.circular(12),
hintText: 'Rechercher restaurants, musées...', ),
hintStyle: TextStyle( child: TextField(
color: theme.colorScheme.onSurface.withValues(alpha: 0.6), 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) { Widget _buildCategoryTabs(ThemeData theme) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@@ -637,6 +813,23 @@ class _ActivitiesPageState extends State<ActivitiesPage>
activity.name.toLowerCase().trim(), 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( return GestureDetector(
onTap: () { onTap: () {
showDialog( showDialog(
@@ -709,6 +902,40 @@ class _ActivitiesPageState extends State<ActivitiesPage>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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( Row(
children: [ children: [
// Icône de catégorie // Icône de catégorie

View File

@@ -24,6 +24,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../services/place_image_service.dart'; import '../../services/place_image_service.dart';
import '../../services/trip_geocoding_service.dart'; import '../../services/trip_geocoding_service.dart';
import '../../services/logger_service.dart'; import '../../services/logger_service.dart';
import '../../firebase_options.dart';
/// Create trip content widget for trip creation and editing functionality. /// 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 /// Google Maps API key for location services
static String get _apiKey { static String get _apiKey {
if (Platform.isAndroid) { return DefaultFirebaseOptions.currentPlatform.apiKey;
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'] ?? '';
} }
/// Participant management /// Participant management
@@ -135,7 +127,11 @@ class _CreateTripContentState extends State<CreateTripContent> {
} }
} }
bool _isProgrammaticUpdate = false;
void _onLocationChanged() { void _onLocationChanged() {
if (_isProgrammaticUpdate) return;
final query = _locationController.text.trim(); final query = _locationController.text.trim();
if (query.length < 2) { if (query.length < 2) {
@@ -215,15 +211,18 @@ class _CreateTripContentState extends State<CreateTripContent> {
if (_placeSuggestions.isEmpty) return; 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( _suggestionsOverlay = OverlayEntry(
builder: (context) => Positioned( builder: (context) => Positioned(
width: width: width,
MediaQuery.of(context).size.width -
32, // Largeur du champ avec padding
child: CompositedTransformFollower( child: CompositedTransformFollower(
link: _layerLink, link: _layerLink,
showWhenUnlinked: false, showWhenUnlinked: false,
offset: const Offset(0, 60), // Position sous le champ targetAnchor: Alignment.bottomLeft,
child: Material( child: Material(
elevation: 4, elevation: 4,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -236,6 +235,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
), ),
child: ListView.builder( child: ListView.builder(
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: _placeSuggestions.length, itemCount: _placeSuggestions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final suggestion = _placeSuggestions[index]; final suggestion = _placeSuggestions[index];
@@ -256,7 +256,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
), ),
); );
Overlay.of(context).insert(_suggestionsOverlay!); overlay.insert(_suggestionsOverlay!);
} }
void _hideSuggestions() { void _hideSuggestions() {
@@ -265,7 +265,10 @@ class _CreateTripContentState extends State<CreateTripContent> {
} }
void _selectSuggestion(PlaceSuggestion suggestion) { void _selectSuggestion(PlaceSuggestion suggestion) {
_isProgrammaticUpdate = true;
_locationController.text = suggestion.description; _locationController.text = suggestion.description;
_isProgrammaticUpdate = false;
_hideSuggestions(); _hideSuggestions();
setState(() { setState(() {
_placeSuggestions = []; _placeSuggestions = [];

View File

@@ -6,6 +6,7 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../firebase_options.dart';
import '../../services/error_service.dart'; import '../../services/error_service.dart';
import '../../services/map_navigation_service.dart'; import '../../services/map_navigation_service.dart';
import '../../services/logger_service.dart'; import '../../services/logger_service.dart';
@@ -22,6 +23,7 @@ class MapContent extends StatefulWidget {
class _MapContentState extends State<MapContent> { class _MapContentState extends State<MapContent> {
GoogleMapController? _mapController; GoogleMapController? _mapController;
LatLng _initialPosition = const LatLng(48.8566, 2.3522); LatLng _initialPosition = const LatLng(48.8566, 2.3522);
LatLng? _currentMapCenter;
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
bool _isLoadingLocation = false; bool _isLoadingLocation = false;
bool _isSearching = false; bool _isSearching = false;
@@ -33,16 +35,7 @@ class _MapContentState extends State<MapContent> {
List<PlaceSuggestion> _suggestions = []; List<PlaceSuggestion> _suggestions = [];
static String get _apiKey { static String get _apiKey {
if (Platform.isAndroid) { return DefaultFirebaseOptions.currentPlatform.apiKey;
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'] ?? '';
} }
@override @override
@@ -321,20 +314,30 @@ class _MapContentState extends State<MapContent> {
} }
} }
// Créer une icône personnalisée à partir de l'icône Material // Créer une icône personnalisée
Future<BitmapDescriptor> _createCustomMarkerIcon() async { Future<BitmapDescriptor> _createMarkerIcon(
IconData iconData,
Color color, {
double size = 60.0,
}) async {
final pictureRecorder = ui.PictureRecorder(); final pictureRecorder = ui.PictureRecorder();
final canvas = Canvas(pictureRecorder); final canvas = Canvas(pictureRecorder);
const size = 80.0;
// Dessiner l'icône person_pin_circle en bleu
final iconPainter = TextPainter(textDirection: TextDirection.ltr); final iconPainter = TextPainter(textDirection: TextDirection.ltr);
iconPainter.text = TextSpan( iconPainter.text = TextSpan(
text: String.fromCharCode(Icons.person_pin_circle.codePoint), text: String.fromCharCode(iconData.codePoint),
style: TextStyle( style: TextStyle(
fontSize: 70, fontSize: size,
fontFamily: Icons.person_pin_circle.fontFamily, fontFamily: iconData.fontFamily,
color: Colors.blue[700], package: iconData.fontPackage,
color: color,
shadows: [
Shadow(
offset: const Offset(1, 1),
blurRadius: 2,
color: Colors.black.withValues(alpha: 0.3),
),
],
), ),
); );
iconPainter.layout(); iconPainter.layout();
@@ -364,8 +367,12 @@ class _MapContentState extends State<MapContent> {
), ),
); );
// Créer l'icône personnalisée // Créer l'icône personnalisée (plus petite: 60 au lieu de 80)
final icon = await _createCustomMarkerIcon(); final icon = await _createMarkerIcon(
Icons.person_pin_circle,
Colors.blue[700]!,
size: 60.0,
);
// Ajouter le marqueur avec l'icône // Ajouter le marqueur avec l'icône
setState(() { setState(() {
@@ -374,10 +381,7 @@ class _MapContentState extends State<MapContent> {
markerId: const MarkerId('user_location'), markerId: const MarkerId('user_location'),
position: position, position: position,
icon: icon, icon: icon,
anchor: const Offset( anchor: const Offset(0.5, 0.85), // Ancrer au bas de l'icône
0.5,
0.85,
), // Ancrer au bas de l'icône (le point du pin)
infoWindow: InfoWindow( infoWindow: InfoWindow(
title: 'Ma position', title: 'Ma position',
snippet: snippet:
@@ -401,10 +405,21 @@ class _MapContentState extends State<MapContent> {
}); });
try { 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( final url = Uri.parse(
'https://maps.googleapis.com/maps/api/place/autocomplete/json' 'https://maps.googleapis.com/maps/api/place/autocomplete/json'
'?input=${Uri.encodeComponent(query)}' '?input=${Uri.encodeComponent(query)}'
'&key=$_apiKey' '&key=$apiKey'
'&language=fr', '&language=fr',
); );
@@ -412,11 +427,21 @@ class _MapContentState extends State<MapContent> {
if (!mounted) return; if (!mounted) return;
LoggerService.info(
'MapContent: Response status code: ${response.statusCode}',
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
final data = json.decode(response.body); 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; final predictions = data['predictions'] as List;
LoggerService.info(
'MapContent: Found ${predictions.length} predictions',
);
setState(() { setState(() {
_suggestions = predictions _suggestions = predictions
@@ -430,13 +455,19 @@ class _MapContentState extends State<MapContent> {
_isSearching = false; _isSearching = false;
}); });
} else { } else {
LoggerService.error(
'MapContent: API Error: $status - ${data['error_message'] ?? "No error message"}',
);
setState(() { setState(() {
_suggestions = []; _suggestions = [];
_isSearching = false; _isSearching = false;
}); });
} }
} else {
LoggerService.error('MapContent: HTTP Error ${response.statusCode}');
} }
} catch (e) { } catch (e) {
LoggerService.error('MapContent: Exception during search: $e');
_showError('Erreur lors de la recherche de lieux: $e'); _showError('Erreur lors de la recherche de lieux: $e');
setState(() { setState(() {
_isSearching = false; _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 { Future<void> _selectPlace(PlaceSuggestion suggestion) async {
setState(() { setState(() {
_isSearching = true; _isSearching = true;
@@ -467,13 +620,21 @@ class _MapContentState extends State<MapContent> {
final data = json.decode(response.body); final data = json.decode(response.body);
if (data['status'] == 'OK') { if (data['status'] == 'OK') {
final location = data['result']['geometry']['location']; final result = data['result'];
final location = result['geometry']['location'];
final lat = location['lat']; final lat = location['lat'];
final lng = location['lng']; final lng = location['lng'];
final name = data['result']['name']; final name = result['name'];
final newPosition = LatLng(lat, lng); 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) // Ajouter un marqueur pour le lieu recherché (ne pas supprimer le marqueur de position)
setState(() { setState(() {
// Garder le marqueur de position utilisateur // Garder le marqueur de position utilisateur
@@ -484,6 +645,8 @@ class _MapContentState extends State<MapContent> {
Marker( Marker(
markerId: MarkerId(suggestion.placeId), markerId: MarkerId(suggestion.placeId),
position: newPosition, position: newPosition,
icon: markerIcon,
anchor: const Offset(0.5, 0.85),
infoWindow: InfoWindow(title: name), infoWindow: InfoWindow(title: name),
), ),
); );
@@ -533,6 +696,9 @@ class _MapContentState extends State<MapContent> {
onMapCreated: (GoogleMapController controller) { onMapCreated: (GoogleMapController controller) {
_mapController = controller; _mapController = controller;
}, },
onCameraMove: (CameraPosition position) {
_currentMapCenter = position.target;
},
markers: _markers, markers: _markers,
circles: _circles, circles: _circles,
myLocationEnabled: false, myLocationEnabled: false,
@@ -664,6 +830,10 @@ class _MapContentState extends State<MapContent> {
vertical: 14, vertical: 14,
), ),
), ),
textInputAction: TextInputAction.search,
onSubmitted: (value) {
_performTextSearch(value);
},
onChanged: (value) { onChanged: (value) {
// Ne pas rechercher si c'est juste le remplissage initial // Ne pas rechercher si c'est juste le remplissage initial
if (widget.initialSearchQuery != null && if (widget.initialSearchQuery != null &&

View 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,
});
}

View File

@@ -27,18 +27,6 @@ class DefaultFirebaseOptions {
return android; return android;
case TargetPlatform.iOS: case TargetPlatform.iOS:
return 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: default:
throw UnsupportedError( throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.', 'DefaultFirebaseOptions are not supported for this platform.',
@@ -63,15 +51,4 @@ class DefaultFirebaseOptions {
iosClientId: '521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com', iosClientId: '521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com',
iosBundleId: 'com.example.travelMate', 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',
);
} }

View File

@@ -14,6 +14,8 @@ import '../blocs/auth/auth_event.dart';
import '../services/error_service.dart'; import '../services/error_service.dart';
import '../services/notification_service.dart'; import '../services/notification_service.dart';
import '../services/map_navigation_service.dart'; import '../services/map_navigation_service.dart';
import '../services/whats_new_service.dart';
import '../components/whats_new_dialog.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({super.key}); 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) { Widget _buildPage(int index) {

View File

@@ -365,7 +365,7 @@ class ActivityPlacesService {
final encodedQuery = Uri.encodeComponent(query); final encodedQuery = Uri.encodeComponent(query);
final url = final url =
'https://maps.googleapis.com/maps/api/place/textsearch/json' 'https://maps.googleapis.com/maps/api/place/textsearch/json'
'?query=$encodedQuery in $destination' '?query=$encodedQuery'
'&location=${coordinates['lat']},${coordinates['lng']}' '&location=${coordinates['lat']},${coordinates['lng']}'
'&radius=$radius' '&radius=$radius'
'&key=$_apiKey' '&key=$_apiKey'
@@ -654,4 +654,72 @@ class ActivityPlacesService {
throw Exception('Erreur HTTP ${response.statusCode}'); 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;
}
}
} }

View 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');
}
}
}

View File

@@ -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 # 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 # 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. # 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: environment:
sdk: ^3.9.2 sdk: ^3.9.2