diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d3352..eaf91e2 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,3 @@ -# Default ignored files -/shelf/ -/workspace.xml +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/CESIZen.iml b/.idea/CESIZen.iml index 39c0579..329019f 100644 --- a/.idea/CESIZen.iml +++ b/.idea/CESIZen.iml @@ -1,52 +1,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml index e2f2258..2ebaaff 100644 --- a/.idea/caches/deviceStreaming.xml +++ b/.idea/caches/deviceStreaming.xml @@ -1781,4 +1781,4 @@ - \ No newline at end of file + diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index ddc03ec..29a1b1d 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -1,11 +1,11 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml index c61ea33..b1f3d01 100644 --- a/.idea/markdown.xml +++ b/.idea/markdown.xml @@ -1,8 +1,8 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 56b3355..9a308bc 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Launch_All__Mobile___Wear_.xml b/.idea/runConfigurations/Launch_All__Mobile___Wear_.xml index 017afb3..5dae445 100644 --- a/.idea/runConfigurations/Launch_All__Mobile___Wear_.xml +++ b/.idea/runConfigurations/Launch_All__Mobile___Wear_.xml @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/.idea/runConfigurations/Mobile_App.xml b/.idea/runConfigurations/Mobile_App.xml index 3ed23f4..50d40f8 100644 --- a/.idea/runConfigurations/Mobile_App.xml +++ b/.idea/runConfigurations/Mobile_App.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/.idea/runConfigurations/Wear_App.xml b/.idea/runConfigurations/Wear_App.xml index 31ac647..de53a32 100644 --- a/.idea/runConfigurations/Wear_App.xml +++ b/.idea/runConfigurations/Wear_App.xml @@ -1,68 +1,68 @@ - - - - - + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..5906f1b 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 1460b7b..69814a4 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,85 @@ -# 🌿 CESIZen - Écosystème de Bien-être Unifié - -CESIZen est une solution complète de gestion du stress et du bien-être, composée d'une plateforme web, d'une application mobile et d'une application pour montre connectée (Wear OS). - -## 🚀 Structure du Projet - -- **/WEB** : API et interface d'administration/diagnostic (Laravel 10, DSFR, Alpine.js). -- **/mobile** : Application compagnon pour le suivi et les exercices (Flutter). -- **/Wear** : Application de montre pour le tracking rapide de l'humeur (Android Wear OS, Kotlin/Compose). - ---- - -## 🛠 Installation & Configuration - -### 1. Backend & Web (Laravel) -Le serveur central gère l'authentification (Sanctum) et la persistance des données. - -```bash -cd WEB -composer install -npm install -cp .env.example .env # Configurez votre base de données et l'URL du serveur -php artisan key:generate -php artisan migrate --seed -php artisan serve -``` -*Note : Assurez-vous que l'IP de votre serveur est accessible par votre téléphone/émulateur pour la synchronisation.* - -### 2. Application Mobile (Flutter) -L'application mobile se connecte à l'API Laravel. - -```bash -cd mobile -flutter pub get -# Modifiez l'URL de l'API dans lib/features/auth/services/auth_service.dart si nécessaire -flutter run -``` - -### 3. Application Montre (Wear OS) -La montre communique avec le téléphone via le **Google Wearable Data Layer**. - -1. Ouvrez le dossier `/Wear` dans **Android Studio**. -2. Compilez et installez l'APK sur une montre (ou émulateur) Wear OS. -3. Assurez-vous que le téléphone appairé possède également l'application mobile CESIZen installée. - ---- - -## 📱 Utilisation & Fonctionnalités - -### 🖥️ Plateforme Web -- **Tableau de bord** : Visualisation des statistiques de stress. -- **Détente interactive** : Module de respiration guidée (méthode DSFR) avec 3 modes (Sommeil, Cohérence, Anxiété). -- **Diagnostics** : Historique détaillé des relevés émotionnels. - -### 🤳 Application Mobile -- **Authentification** : Connexion sécurisée avec persistance de session. -- **Tracker** : Enregistrement quotidien de l'état émotionnel. -- **Exercices** : Respiration guidée avec animation synchronisée du logo CESIZen. -- **Sync Montre** : Envoi automatique de la date et réception des humeurs capturées sur la montre. - -### ⌚ Application Wear OS -- **Quick Mood** : Sélectionnez votre humeur en un clic parmi 5 niveaux. -- **Smart Reset** : L'humeur est automatiquement réinitialisée à minuit chaque jour grâce à la synchronisation bidirectionnelle avec le mobile. -- **Indicateur visuel** : Confirmation visuelle immédiate de l'enregistrement. - ---- - -## 🔗 Synchronisation Bidirectionnelle -- **Mobile -> Montre** : Envoie la `date du jour` pour synchroniser le cycle quotidien. -- **Montre -> Mobile** : Envoie l'émotion sélectionnée via le `Data Layer` (`/daily_mood`). -- **Mobile -> Web** : Synchronise les données finales vers la base de données Laravel pour analyse. - ---- - -## 🎨 Identité Visuelle -L'ensemble de l'écosystème utilise la charte graphique **CesiZen** : -- **Couleur Primaire** : Bleu Marine (`#000080`) -- **Couleur Accent** : Orange (`#FFCC80`) -- **Typographie** : Marianne (standard DSFR) -- **Logo** : Point central de l'animation de respiration. - ---- - -## 📄 Licence -Projet réalisé dans le cadre de la formation CDA CESI. +# 🌿 CESIZen - Écosystème de Bien-être Unifié + +CESIZen est une solution complète de gestion du stress et du bien-être, composée d'une plateforme web, d'une application mobile et d'une application pour montre connectée (Wear OS). + +## 🚀 Structure du Projet + +- **/WEB** : API et interface d'administration/diagnostic (Laravel 10, DSFR, Alpine.js). +- **/mobile** : Application compagnon pour le suivi et les exercices (Flutter). +- **/Wear** : Application de montre pour le tracking rapide de l'humeur (Android Wear OS, Kotlin/Compose). + +--- + +## 🛠 Installation & Configuration + +### 1. Backend & Web (Laravel) +Le serveur central gère l'authentification (Sanctum) et la persistance des données. + +```bash +cd WEB +composer install +npm install +cp .env.example .env # Configurez votre base de données et l'URL du serveur +php artisan key:generate +php artisan migrate --seed +php artisan serve +``` +*Note : Assurez-vous que l'IP de votre serveur est accessible par votre téléphone/émulateur pour la synchronisation.* + +### 2. Application Mobile (Flutter) +L'application mobile se connecte à l'API Laravel. + +```bash +cd mobile +flutter pub get +# Modifiez l'URL de l'API dans lib/features/auth/services/auth_service.dart si nécessaire +flutter run +``` + +### 3. Application Montre (Wear OS) +La montre communique avec le téléphone via le **Google Wearable Data Layer**. + +1. Ouvrez le dossier `/Wear` dans **Android Studio**. +2. Compilez et installez l'APK sur une montre (ou émulateur) Wear OS. +3. Assurez-vous que le téléphone appairé possède également l'application mobile CESIZen installée. + +--- + +## 📱 Utilisation & Fonctionnalités + +### 🖥️ Plateforme Web +- **Tableau de bord** : Visualisation des statistiques de stress. +- **Détente interactive** : Module de respiration guidée (méthode DSFR) avec 3 modes (Sommeil, Cohérence, Anxiété). +- **Diagnostics** : Historique détaillé des relevés émotionnels. + +### 🤳 Application Mobile +- **Authentification** : Connexion sécurisée avec persistance de session. +- **Tracker** : Enregistrement quotidien de l'état émotionnel. +- **Exercices** : Respiration guidée avec animation synchronisée du logo CESIZen. +- **Sync Montre** : Envoi automatique de la date et réception des humeurs capturées sur la montre. + +### ⌚ Application Wear OS +- **Quick Mood** : Sélectionnez votre humeur en un clic parmi 5 niveaux. +- **Smart Reset** : L'humeur est automatiquement réinitialisée à minuit chaque jour grâce à la synchronisation bidirectionnelle avec le mobile. +- **Indicateur visuel** : Confirmation visuelle immédiate de l'enregistrement. + +--- + +## 🔗 Synchronisation Bidirectionnelle +- **Mobile -> Montre** : Envoie la `date du jour` pour synchroniser le cycle quotidien. +- **Montre -> Mobile** : Envoie l'émotion sélectionnée via le `Data Layer` (`/daily_mood`). +- **Mobile -> Web** : Synchronise les données finales vers la base de données Laravel pour analyse. + +--- + +## 🎨 Identité Visuelle +L'ensemble de l'écosystème utilise la charte graphique **CesiZen** : +- **Couleur Primaire** : Bleu Marine (`#000080`) +- **Couleur Accent** : Orange (`#FFCC80`) +- **Typographie** : Marianne (standard DSFR) +- **Logo** : Point central de l'animation de respiration. + +--- + +## 📄 Licence +Projet réalisé dans le cadre de la formation CDA CESI. diff --git a/WEB/tests/Unit/DiagnosticTest.php b/WEB/tests/Unit/DiagnosticTest.php deleted file mode 100644 index 3fc999f..0000000 --- a/WEB/tests/Unit/DiagnosticTest.php +++ /dev/null @@ -1,41 +0,0 @@ -create(); - - // Créer des événements de stress - $event1 = StressEvent::create(['event_name' => 'Event 1', 'points' => 100]); - $event2 = StressEvent::create(['event_name' => 'Event 2', 'points' => 60]); - - // Simuler une requête de diagnostic - $response = $this->actingAs($user) - ->post(route('diagnostics.store'), [ - 'events' => [$event1->id, $event2->id] - ]); - - // Vérifier la redirection et le score en session - $response->assertStatus(302); - $response->assertSessionHas('score', 160); - - // Vérifier l'enregistrement en base de données - $this->assertDatabaseHas('resultat_diags', [ - 'id_user' => $user->id, - 'score_total' => 160, - 'niveau_stress' => 'Modéré' - ]); - } -} diff --git a/Wear/.idea/.gitignore b/Wear/.idea/.gitignore index 26d3352..eaf91e2 100644 --- a/Wear/.idea/.gitignore +++ b/Wear/.idea/.gitignore @@ -1,3 +1,3 @@ -# Default ignored files -/shelf/ -/workspace.xml +# Default ignored files +/shelf/ +/workspace.xml diff --git a/Wear/.idea/AndroidProjectSystem.xml b/Wear/.idea/AndroidProjectSystem.xml index 4a53bee..d58d49b 100644 --- a/Wear/.idea/AndroidProjectSystem.xml +++ b/Wear/.idea/AndroidProjectSystem.xml @@ -1,6 +1,6 @@ - - - - + + + + \ No newline at end of file diff --git a/Wear/.idea/compiler.xml b/Wear/.idea/compiler.xml index b86273d..8b3f102 100644 --- a/Wear/.idea/compiler.xml +++ b/Wear/.idea/compiler.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/Wear/.idea/deploymentTargetSelector.xml b/Wear/.idea/deploymentTargetSelector.xml index b257aa1..0d827f5 100644 --- a/Wear/.idea/deploymentTargetSelector.xml +++ b/Wear/.idea/deploymentTargetSelector.xml @@ -1,18 +1,18 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/Wear/.idea/gradle.xml b/Wear/.idea/gradle.xml index 02c4aa5..d33973f 100644 --- a/Wear/.idea/gradle.xml +++ b/Wear/.idea/gradle.xml @@ -1,18 +1,18 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/Wear/.idea/runConfigurations.xml b/Wear/.idea/runConfigurations.xml index 16660f1..5bd6771 100644 --- a/Wear/.idea/runConfigurations.xml +++ b/Wear/.idea/runConfigurations.xml @@ -1,17 +1,17 @@ - - - - - + + + + + \ No newline at end of file diff --git a/Wear/.idea/vcs.xml b/Wear/.idea/vcs.xml index 6c0b863..2e3f692 100644 --- a/Wear/.idea/vcs.xml +++ b/Wear/.idea/vcs.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/Wear/app/src/main/AndroidManifest.xml b/Wear/app/src/main/AndroidManifest.xml index 779396d..4423059 100644 --- a/Wear/app/src/main/AndroidManifest.xml +++ b/Wear/app/src/main/AndroidManifest.xml @@ -1,50 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Wear/app/src/main/java/com/cesizen/app/MainActivity.kt b/Wear/app/src/main/java/com/cesizen/app/MainActivity.kt index 0059909..517fcee 100644 --- a/Wear/app/src/main/java/com/cesizen/app/MainActivity.kt +++ b/Wear/app/src/main/java/com/cesizen/app/MainActivity.kt @@ -1,282 +1,282 @@ -package com.cesizen.app - -import android.content.Context -import android.os.Bundle -import android.util.Log -import android.widget.Toast -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.wear.compose.foundation.lazy.TransformingLazyColumn -import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState -import androidx.wear.compose.material3.* -import com.cesizen.app.presentation.theme.MyApplicationTheme -import com.google.android.gms.tasks.Tasks -import com.google.android.gms.wearable.* -import org.json.JSONObject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest - -class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener { - - - private var userName by mutableStateOf(null) - private var currentMood by mutableStateOf(null) - private var isRefreshing by mutableStateOf(false) - private val TAG = "CESIZen_Watch" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - Wearable.getDataClient(this).addListener(this) - refreshData() - - val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - val request = NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // Priorité Wi-Fi - .addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH) // Secours Bluetooth - .build() - - connectivityManager.requestNetwork(request, object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - // Le système a trouvé soit du Wi-Fi, soit le pont Bluetooth du téléphone - connectivityManager.bindProcessToNetwork(network) - } - }) - - setContent { - WearApp( - userName = userName, - currentMood = currentMood, - isRefreshing = isRefreshing, - onRefresh = { refreshData() }, - onMoodSelected = { mood -> sendMoodToPhone(mood) } - ) - } - } - - private fun refreshData() { - if (isRefreshing) return - isRefreshing = true - - val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) - - Wearable.getDataClient(this).dataItems.addOnSuccessListener { items -> - for (item in items) { - if (item.uri.path == "/auth_status") { - val dataMap = DataMapItem.fromDataItem(item).dataMap - userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null - } else if (item.uri.path == "/daily_mood") { - val dataMap = DataMapItem.fromDataItem(item).dataMap - val moodDate = dataMap.getString("date") - - if (moodDate == today) { - currentMood = dataMap.getString("mood") - } else { - currentMood = null - } - } - } - isRefreshing = false - } - - CoroutineScope(Dispatchers.IO).launch { - try { - val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes) - for (node in nodes) { - val payload = JSONObject().apply { put("command", "get_sync_data") }.toString().toByteArray() - Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload) - } - } catch (e: Exception) { - Log.e(TAG, "Error sync nodes", e) - } - } - } - - private fun sendMoodToPhone(mood: String) { - currentMood = mood - if (mood.isEmpty()) { - // Reset mood locally to show the selection again - currentMood = null - } - - CoroutineScope(Dispatchers.IO).launch { - try { - val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes) - for (node in nodes) { - val payload = JSONObject().apply { - put("command", "save_mood") - put("mood", mood) - }.toString().toByteArray() - Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload) - } - } catch (e: Exception) { - Log.e(TAG, "Error sending mood", e) - } - } - } - - override fun onDataChanged(dataEvents: DataEventBuffer) { - val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) - - for (event in dataEvents) { - val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap - if (event.dataItem.uri.path == "/auth_status") { - userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null - } else if (event.dataItem.uri.path == "/daily_mood") { - val moodDate = dataMap.getString("date") - if (moodDate == today) { - currentMood = dataMap.getString("mood") - } else { - currentMood = null - } - } - } - } -} - -@Composable -fun WearApp(userName: String?, currentMood: String?, isRefreshing: Boolean, onRefresh: () -> Unit, onMoodSelected: (String) -> Unit) { - val cesiBlue = Color(0xFF000091) - - MyApplicationTheme { - AppScaffold { - val listState = rememberTransformingLazyColumnState() - ScreenScaffold(scrollState = listState) { contentPadding -> - TransformingLazyColumn( - modifier = Modifier - .fillMaxSize() - .background(Brush.verticalGradient(listOf(Color.Black, Color(0xFF000022)))), - contentPadding = contentPadding, - state = listState, - horizontalAlignment = Alignment.CenterHorizontally - ) { - // Header - item { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(bottom = 12.dp) - ) { - Text( - "CESIZen", - color = cesiBlue, - style = MaterialTheme.typography.labelMedium, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.height(4.dp)) - } - } - - if (userName == null) { - item { - TitleCard( - onClick = onRefresh, - title = { Text("Connexion requise", textAlign = TextAlign.Center) }, - subtitle = { Text("Ouvrez l'app sur votre téléphone", textAlign = TextAlign.Center, fontSize = 12.sp) }, - modifier = Modifier.padding(horizontal = 8.dp) - ) - } - } else { - // User Status - item { - val displayName = userName.split(" ").firstOrNull() ?: "Zen" - Text( - "Bonjour $displayName", - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center - ) - } - - if (currentMood.isNullOrEmpty()) { - item { - Text( - "Comment allez-vous ?", - style = MaterialTheme.typography.bodySmall, - color = Color.LightGray, - modifier = Modifier.padding(vertical = 8.dp) - ) - } - - val moods = listOf( - MoodItem("Très bien", "😊", Color(0xFF4CAF50)), - MoodItem("Bien", "🙂", Color(0xFF8BC34A)), - MoodItem("Neutre", "😐", Color(0xFFFFC107)), - MoodItem("Pas top", "🙁", Color(0xFFFF9800)), - MoodItem("Stressé", "😫", Color(0xFFF44336)) - ) - - moods.forEach { mood -> - item { - FilledTonalButton( - onClick = { onMoodSelected(mood.label) }, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 2.dp, horizontal = 16.dp), - colors = ButtonDefaults.filledTonalButtonColors( - containerColor = mood.color.copy(alpha = 0.2f), - contentColor = Color.White - ) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text(mood.emoji, fontSize = 20.sp) - Spacer(modifier = Modifier.width(12.dp)) - Text(mood.label, style = MaterialTheme.typography.labelLarge) - } - } - } - } - } else { - item { - Card( - onClick = { onMoodSelected("") }, - modifier = Modifier.padding(16.dp), - colors = CardDefaults.cardColors(containerColor = cesiBlue.copy(alpha = 0.3f)) - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Humeur enregistrée", style = MaterialTheme.typography.labelSmall) - Spacer(modifier = Modifier.height(8.dp)) - Text(currentMood, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(8.dp)) - Text("Tapoter pour changer", fontSize = 10.sp, color = Color.LightGray) - } - } - } - } - } - - // Bottom Spacer - item { - Spacer(modifier = Modifier.height(20.dp)) - } - } - } - } - } -} - -data class MoodItem(val label: String, val emoji: String, val color: Color) +package com.cesizen.app + +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.wear.compose.foundation.lazy.TransformingLazyColumn +import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState +import androidx.wear.compose.material3.* +import com.cesizen.app.presentation.theme.MyApplicationTheme +import com.google.android.gms.tasks.Tasks +import com.google.android.gms.wearable.* +import org.json.JSONObject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest + +class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener { + + + private var userName by mutableStateOf(null) + private var currentMood by mutableStateOf(null) + private var isRefreshing by mutableStateOf(false) + private val TAG = "CESIZen_Watch" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Wearable.getDataClient(this).addListener(this) + refreshData() + + val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + val request = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // Priorité Wi-Fi + .addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH) // Secours Bluetooth + .build() + + connectivityManager.requestNetwork(request, object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + // Le système a trouvé soit du Wi-Fi, soit le pont Bluetooth du téléphone + connectivityManager.bindProcessToNetwork(network) + } + }) + + setContent { + WearApp( + userName = userName, + currentMood = currentMood, + isRefreshing = isRefreshing, + onRefresh = { refreshData() }, + onMoodSelected = { mood -> sendMoodToPhone(mood) } + ) + } + } + + private fun refreshData() { + if (isRefreshing) return + isRefreshing = true + + val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) + + Wearable.getDataClient(this).dataItems.addOnSuccessListener { items -> + for (item in items) { + if (item.uri.path == "/auth_status") { + val dataMap = DataMapItem.fromDataItem(item).dataMap + userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null + } else if (item.uri.path == "/daily_mood") { + val dataMap = DataMapItem.fromDataItem(item).dataMap + val moodDate = dataMap.getString("date") + + if (moodDate == today) { + currentMood = dataMap.getString("mood") + } else { + currentMood = null + } + } + } + isRefreshing = false + } + + CoroutineScope(Dispatchers.IO).launch { + try { + val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes) + for (node in nodes) { + val payload = JSONObject().apply { put("command", "get_sync_data") }.toString().toByteArray() + Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload) + } + } catch (e: Exception) { + Log.e(TAG, "Error sync nodes", e) + } + } + } + + private fun sendMoodToPhone(mood: String) { + currentMood = mood + if (mood.isEmpty()) { + // Reset mood locally to show the selection again + currentMood = null + } + + CoroutineScope(Dispatchers.IO).launch { + try { + val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes) + for (node in nodes) { + val payload = JSONObject().apply { + put("command", "save_mood") + put("mood", mood) + }.toString().toByteArray() + Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload) + } + } catch (e: Exception) { + Log.e(TAG, "Error sending mood", e) + } + } + } + + override fun onDataChanged(dataEvents: DataEventBuffer) { + val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) + + for (event in dataEvents) { + val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap + if (event.dataItem.uri.path == "/auth_status") { + userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null + } else if (event.dataItem.uri.path == "/daily_mood") { + val moodDate = dataMap.getString("date") + if (moodDate == today) { + currentMood = dataMap.getString("mood") + } else { + currentMood = null + } + } + } + } +} + +@Composable +fun WearApp(userName: String?, currentMood: String?, isRefreshing: Boolean, onRefresh: () -> Unit, onMoodSelected: (String) -> Unit) { + val cesiBlue = Color(0xFF000091) + + MyApplicationTheme { + AppScaffold { + val listState = rememberTransformingLazyColumnState() + ScreenScaffold(scrollState = listState) { contentPadding -> + TransformingLazyColumn( + modifier = Modifier + .fillMaxSize() + .background(Brush.verticalGradient(listOf(Color.Black, Color(0xFF000022)))), + contentPadding = contentPadding, + state = listState, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Header + item { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(bottom = 12.dp) + ) { + Text( + "CESIZen", + color = cesiBlue, + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(4.dp)) + } + } + + if (userName == null) { + item { + TitleCard( + onClick = onRefresh, + title = { Text("Connexion requise", textAlign = TextAlign.Center) }, + subtitle = { Text("Ouvrez l'app sur votre téléphone", textAlign = TextAlign.Center, fontSize = 12.sp) }, + modifier = Modifier.padding(horizontal = 8.dp) + ) + } + } else { + // User Status + item { + val displayName = userName.split(" ").firstOrNull() ?: "Zen" + Text( + "Bonjour $displayName", + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center + ) + } + + if (currentMood.isNullOrEmpty()) { + item { + Text( + "Comment allez-vous ?", + style = MaterialTheme.typography.bodySmall, + color = Color.LightGray, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + + val moods = listOf( + MoodItem("Très bien", "😊", Color(0xFF4CAF50)), + MoodItem("Bien", "🙂", Color(0xFF8BC34A)), + MoodItem("Neutre", "😐", Color(0xFFFFC107)), + MoodItem("Pas top", "🙁", Color(0xFFFF9800)), + MoodItem("Stressé", "😫", Color(0xFFF44336)) + ) + + moods.forEach { mood -> + item { + FilledTonalButton( + onClick = { onMoodSelected(mood.label) }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 2.dp, horizontal = 16.dp), + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = mood.color.copy(alpha = 0.2f), + contentColor = Color.White + ) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text(mood.emoji, fontSize = 20.sp) + Spacer(modifier = Modifier.width(12.dp)) + Text(mood.label, style = MaterialTheme.typography.labelLarge) + } + } + } + } + } else { + item { + Card( + onClick = { onMoodSelected("") }, + modifier = Modifier.padding(16.dp), + colors = CardDefaults.cardColors(containerColor = cesiBlue.copy(alpha = 0.3f)) + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Humeur enregistrée", style = MaterialTheme.typography.labelSmall) + Spacer(modifier = Modifier.height(8.dp)) + Text(currentMood, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(8.dp)) + Text("Tapoter pour changer", fontSize = 10.sp, color = Color.LightGray) + } + } + } + } + } + + // Bottom Spacer + item { + Spacer(modifier = Modifier.height(20.dp)) + } + } + } + } + } +} + +data class MoodItem(val label: String, val emoji: String, val color: Color) diff --git a/Wear/app/src/main/java/com/cesizen/app/presentation/theme/Theme.kt b/Wear/app/src/main/java/com/cesizen/app/presentation/theme/Theme.kt index e178509..7930c08 100644 --- a/Wear/app/src/main/java/com/cesizen/app/presentation/theme/Theme.kt +++ b/Wear/app/src/main/java/com/cesizen/app/presentation/theme/Theme.kt @@ -1,13 +1,13 @@ -package com.cesizen.app.presentation.theme - -import androidx.compose.runtime.Composable -import androidx.wear.compose.material3.MaterialTheme - -@Composable -fun MyApplicationTheme( - content: @Composable () -> Unit -) { - MaterialTheme( - content = content - ) -} +package com.cesizen.app.presentation.theme + +import androidx.compose.runtime.Composable +import androidx.wear.compose.material3.MaterialTheme + +@Composable +fun MyApplicationTheme( + content: @Composable () -> Unit +) { + MaterialTheme( + content = content + ) +} diff --git a/Wear/app/src/main/res/xml/wearable_app_values.xml b/Wear/app/src/main/res/xml/wearable_app_values.xml index 73fe710..dacdaae 100644 --- a/Wear/app/src/main/res/xml/wearable_app_values.xml +++ b/Wear/app/src/main/res/xml/wearable_app_values.xml @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/Wear/gradle/wrapper/gradle-wrapper.properties b/Wear/gradle/wrapper/gradle-wrapper.properties index e1474f4..39408b2 100644 --- a/Wear/gradle/wrapper/gradle-wrapper.properties +++ b/Wear/gradle/wrapper/gradle-wrapper.properties @@ -1,9 +1,9 @@ -#Tue Apr 21 22:42:57 CEST 2026 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +#Tue Apr 21 22:42:57 CEST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Wear/gradlew.bat b/Wear/gradlew.bat index db3a6ac..5eed7ee 100644 --- a/Wear/gradlew.bat +++ b/Wear/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mobile/.gitignore b/mobile/.gitignore index 3820a95..6f0d006 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -1,45 +1,45 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ -.swiftpm/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ -/coverage/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/mobile/README.md b/mobile/README.md index f89dc56..b04aa21 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -1,84 +1,84 @@ -# CESIZen - Application Mobile (Flutter) - -CESIZen est une application de gestion de la santé mentale développée avec Flutter. Elle permet aux utilisateurs de suivre leur état émotionnel, de réaliser des diagnostics de stress et de pratiquer des exercices de relaxation. - -## 🚀 Fonctionnalités - -- **Authentification Sécurisée** : Connexion et inscription via une API Laravel (JWT). -- **Espace Santé Personnel** : Modification du profil (Nom, Email, Mot de passe). -- **Diagnostic de Stress** : Basé sur l'échelle de **Holmes et Rahe** (données dynamiques via API). -- **Tracker d'Émotions** : Journal de bord complet (CRUD) pour suivre son humeur. -- **Cohérence Cardiaque** : Exercice de respiration guidé (5s inspiration / 5s expiration). -- **Catalogue Détente** : Activités de relaxation (Méditation, Musique) filtrables par catégorie. - ---- - -## 🛠️ Installation et Lancement - -### 1. Prérequis -- [Flutter SDK](https://docs.flutter.dev/get-started/install) (dernière version stable recommandée). -- [Android Studio](https://developer.android.com/studio) ou [VS Code](https://code.visualstudio.com/). -- Un émulateur Android ou un appareil physique avec le débogage USB activé. - -### 2. Récupération du projet -```bash -git clone -cd CESIZen/mobile -``` - -### 3. Installation des dépendances -```bash -flutter pub get -``` - ---- - -## 🌐 Configuration de l'API - -L'application communique avec un backend Laravel. - -### URL de l'API -La configuration se trouve dans `lib/core/network/dio_client.dart`. -- **URL actuelle** : `https://apicesizen.sam-coffre.duckdns.org/api` - -### Compatibilité et Sécurité (SSL Bypass) -Pour faciliter le développement sur des serveurs avec des certificats auto-signés ou en cours de configuration, le client `Dio` est configuré pour **ignorer les erreurs de certificat SSL**. - -Dans `lib/core/network/dio_client.dart` : -```dart -// Le code inclut un SecurityContext qui accepte tous les certificats -(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { - final client = HttpClient(); - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; - return client; -}; -``` - -**Note importante :** Sur Android, assurez-vous que `android:usesCleartextTraffic="true"` est bien présent dans le `AndroidManifest.xml` si vous repassez en HTTP simple. - ---- - -## 🏃 Lancer l'application - -Pour lancer l'application en mode debug : - -```bash -flutter run -``` - -Pour générer un APK de test : -```bash -flutter build apk --debug -``` - -## 📂 Structure du Projet - -- `lib/core/` : Configuration réseau (Dio) et thèmes. -- `lib/features/auth/` : Gestion de l'authentification et du profil. -- `lib/features/diagnostics/` : Logique du questionnaire Holmes & Rahe. -- `lib/features/tracker/` : Journal des émotions (CRUD). -- `lib/features/exercises/` : Exercice de cohérence cardiaque. -- `lib/features/relaxation/` : Catalogue d'activités de détente. - ---- -© 2024 CESIZen - Projet CDA +# CESIZen - Application Mobile (Flutter) + +CESIZen est une application de gestion de la santé mentale développée avec Flutter. Elle permet aux utilisateurs de suivre leur état émotionnel, de réaliser des diagnostics de stress et de pratiquer des exercices de relaxation. + +## 🚀 Fonctionnalités + +- **Authentification Sécurisée** : Connexion et inscription via une API Laravel (JWT). +- **Espace Santé Personnel** : Modification du profil (Nom, Email, Mot de passe). +- **Diagnostic de Stress** : Basé sur l'échelle de **Holmes et Rahe** (données dynamiques via API). +- **Tracker d'Émotions** : Journal de bord complet (CRUD) pour suivre son humeur. +- **Cohérence Cardiaque** : Exercice de respiration guidé (5s inspiration / 5s expiration). +- **Catalogue Détente** : Activités de relaxation (Méditation, Musique) filtrables par catégorie. + +--- + +## 🛠️ Installation et Lancement + +### 1. Prérequis +- [Flutter SDK](https://docs.flutter.dev/get-started/install) (dernière version stable recommandée). +- [Android Studio](https://developer.android.com/studio) ou [VS Code](https://code.visualstudio.com/). +- Un émulateur Android ou un appareil physique avec le débogage USB activé. + +### 2. Récupération du projet +```bash +git clone +cd CESIZen/mobile +``` + +### 3. Installation des dépendances +```bash +flutter pub get +``` + +--- + +## 🌐 Configuration de l'API + +L'application communique avec un backend Laravel. + +### URL de l'API +La configuration se trouve dans `lib/core/network/dio_client.dart`. +- **URL actuelle** : `https://apicesizen.sam-coffre.duckdns.org/api` + +### Compatibilité et Sécurité (SSL Bypass) +Pour faciliter le développement sur des serveurs avec des certificats auto-signés ou en cours de configuration, le client `Dio` est configuré pour **ignorer les erreurs de certificat SSL**. + +Dans `lib/core/network/dio_client.dart` : +```dart +// Le code inclut un SecurityContext qui accepte tous les certificats +(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { + final client = HttpClient(); + client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; + return client; +}; +``` + +**Note importante :** Sur Android, assurez-vous que `android:usesCleartextTraffic="true"` est bien présent dans le `AndroidManifest.xml` si vous repassez en HTTP simple. + +--- + +## 🏃 Lancer l'application + +Pour lancer l'application en mode debug : + +```bash +flutter run +``` + +Pour générer un APK de test : +```bash +flutter build apk --debug +``` + +## 📂 Structure du Projet + +- `lib/core/` : Configuration réseau (Dio) et thèmes. +- `lib/features/auth/` : Gestion de l'authentification et du profil. +- `lib/features/diagnostics/` : Logique du questionnaire Holmes & Rahe. +- `lib/features/tracker/` : Journal des émotions (CRUD). +- `lib/features/exercises/` : Exercice de cohérence cardiaque. +- `lib/features/relaxation/` : Catalogue d'activités de détente. + +--- +© 2024 CESIZen - Projet CDA diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 0d29021..d4e0f0c 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -1,28 +1,28 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobile/android/.gitignore b/mobile/android/.gitignore index be3943c..c908258 100644 --- a/mobile/android/.gitignore +++ b/mobile/android/.gitignore @@ -1,14 +1,14 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/mobile/android/app/build.gradle.kts b/mobile/android/app/build.gradle.kts index 9e1521d..c53ad53 100644 --- a/mobile/android/app/build.gradle.kts +++ b/mobile/android/app/build.gradle.kts @@ -1,47 +1,47 @@ -plugins { - id("com.android.application") - id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "com.cesizen.app" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - - defaultConfig { - applicationId = "com.cesizen.app" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - } - } -} - -dependencies { - implementation("com.google.android.gms:play-services-wearable:18.0.0") -} - -flutter { - source = "../.." -} +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.cesizen.app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + applicationId = "com.cesizen.app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +dependencies { + implementation("com.google.android.gms:play-services-wearable:18.0.0") +} + +flutter { + source = "../.." +} diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml index 399f698..8ffe024 100644 --- a/mobile/android/app/src/debug/AndroidManifest.xml +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 031d178..04b8883 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -1,55 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/com/cesizen/app/MainActivity.kt b/mobile/android/app/src/main/kotlin/com/cesizen/app/MainActivity.kt index ff6b776..0077594 100644 --- a/mobile/android/app/src/main/kotlin/com/cesizen/app/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/com/cesizen/app/MainActivity.kt @@ -1,108 +1,108 @@ -package com.cesizen.app - -import android.util.Log -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodChannel -import com.google.android.gms.wearable.Wearable -import com.google.android.gms.wearable.PutDataMapRequest -import com.google.android.gms.wearable.CapabilityClient -import com.google.android.gms.tasks.Tasks -import org.json.JSONObject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -class MainActivity : FlutterActivity() { - private val CHANNEL = "com.cesizen/wearable" - private val TAG = "CESIZen_Mobile_Native" - - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> - when (call.method) { - "sendAuthStatus" -> { - val status = call.argument("status") - val userName = call.argument("userName") - updateWearDataLayer(status, userName) - result.success(true) - } - "syncMood" -> { - val mood = call.argument("mood") - updateMoodDataLayer(mood) - result.success(true) - } - "isWatchNearby" -> { - CoroutineScope(Dispatchers.IO).launch { - try { - val capabilityInfo = Tasks.await( - Wearable.getCapabilityClient(this@MainActivity) - .getCapability("cesizen_wear_app", CapabilityClient.FILTER_ALL) - ) - val isNearby = capabilityInfo.nodes.isNotEmpty() - runOnUiThread { result.success(isNearby) } - } catch (e: Exception) { - runOnUiThread { result.success(false) } - } - } - } - else -> { - result.notImplemented() - } - } - } - - // Écouter les messages de la montre - Wearable.getMessageClient(this).addListener { messageEvent -> - Log.d(TAG, "Message received from watch: ${messageEvent.path}") - if (messageEvent.path == "/wearable_communication") { - try { - val dataString = String(messageEvent.data) - Log.d(TAG, "Payload: $dataString") - val json = JSONObject(dataString) - val command = json.optString("command") - - runOnUiThread { - val messenger = flutterEngine.dartExecutor.binaryMessenger - when (command) { - "get_sync_data" -> { - Log.d(TAG, "Command: get_sync_data -> calling requestStatusUpdate") - MethodChannel(messenger, CHANNEL).invokeMethod("requestStatusUpdate", null) - } - "save_mood" -> { - val mood = json.optString("mood") - Log.d(TAG, "Command: save_mood ($mood) -> calling saveMoodFromWear") - MethodChannel(messenger, CHANNEL).invokeMethod("saveMoodFromWear", mapOf("mood" to mood)) - updateMoodDataLayer(mood) - } - } - } - } catch (e: Exception) { - Log.e(TAG, "Error processing message", e) - } - } - } - } - - private fun updateWearDataLayer(status: String?, userName: String?) { - val request = PutDataMapRequest.create("/auth_status") - request.dataMap.putString("status", status ?: "unauthenticated") - request.dataMap.putString("userName", userName ?: "") - request.dataMap.putLong("timestamp", System.currentTimeMillis()) - val putDataRequest = request.asPutDataRequest().setUrgent() - Wearable.getDataClient(this).putDataItem(putDataRequest) - } - - private fun updateMoodDataLayer(mood: String?) { - val request = PutDataMapRequest.create("/daily_mood") - val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) - - request.dataMap.putString("mood", mood ?: "") - request.dataMap.putString("date", today) - request.dataMap.putLong("timestamp", System.currentTimeMillis()) - - val putDataRequest = request.asPutDataRequest().setUrgent() - Wearable.getDataClient(this).putDataItem(putDataRequest) - } -} +package com.cesizen.app + +import android.util.Log +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import com.google.android.gms.wearable.Wearable +import com.google.android.gms.wearable.PutDataMapRequest +import com.google.android.gms.wearable.CapabilityClient +import com.google.android.gms.tasks.Tasks +import org.json.JSONObject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class MainActivity : FlutterActivity() { + private val CHANNEL = "com.cesizen/wearable" + private val TAG = "CESIZen_Mobile_Native" + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> + when (call.method) { + "sendAuthStatus" -> { + val status = call.argument("status") + val userName = call.argument("userName") + updateWearDataLayer(status, userName) + result.success(true) + } + "syncMood" -> { + val mood = call.argument("mood") + updateMoodDataLayer(mood) + result.success(true) + } + "isWatchNearby" -> { + CoroutineScope(Dispatchers.IO).launch { + try { + val capabilityInfo = Tasks.await( + Wearable.getCapabilityClient(this@MainActivity) + .getCapability("cesizen_wear_app", CapabilityClient.FILTER_ALL) + ) + val isNearby = capabilityInfo.nodes.isNotEmpty() + runOnUiThread { result.success(isNearby) } + } catch (e: Exception) { + runOnUiThread { result.success(false) } + } + } + } + else -> { + result.notImplemented() + } + } + } + + // Écouter les messages de la montre + Wearable.getMessageClient(this).addListener { messageEvent -> + Log.d(TAG, "Message received from watch: ${messageEvent.path}") + if (messageEvent.path == "/wearable_communication") { + try { + val dataString = String(messageEvent.data) + Log.d(TAG, "Payload: $dataString") + val json = JSONObject(dataString) + val command = json.optString("command") + + runOnUiThread { + val messenger = flutterEngine.dartExecutor.binaryMessenger + when (command) { + "get_sync_data" -> { + Log.d(TAG, "Command: get_sync_data -> calling requestStatusUpdate") + MethodChannel(messenger, CHANNEL).invokeMethod("requestStatusUpdate", null) + } + "save_mood" -> { + val mood = json.optString("mood") + Log.d(TAG, "Command: save_mood ($mood) -> calling saveMoodFromWear") + MethodChannel(messenger, CHANNEL).invokeMethod("saveMoodFromWear", mapOf("mood" to mood)) + updateMoodDataLayer(mood) + } + } + } + } catch (e: Exception) { + Log.e(TAG, "Error processing message", e) + } + } + } + } + + private fun updateWearDataLayer(status: String?, userName: String?) { + val request = PutDataMapRequest.create("/auth_status") + request.dataMap.putString("status", status ?: "unauthenticated") + request.dataMap.putString("userName", userName ?: "") + request.dataMap.putLong("timestamp", System.currentTimeMillis()) + val putDataRequest = request.asPutDataRequest().setUrgent() + Wearable.getDataClient(this).putDataItem(putDataRequest) + } + + private fun updateMoodDataLayer(mood: String?) { + val request = PutDataMapRequest.create("/daily_mood") + val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) + + request.dataMap.putString("mood", mood ?: "") + request.dataMap.putString("date", today) + request.dataMap.putLong("timestamp", System.currentTimeMillis()) + + val putDataRequest = request.asPutDataRequest().setUrgent() + Wearable.getDataClient(this).putDataItem(putDataRequest) + } +} diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..1cb7aa2 100644 --- a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/android/app/src/main/res/drawable/launch_background.xml index 304732f..8403758 100644 --- a/mobile/android/app/src/main/res/drawable/launch_background.xml +++ b/mobile/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/android/app/src/main/res/values-night/styles.xml index 06952be..360a160 100644 --- a/mobile/android/app/src/main/res/values-night/styles.xml +++ b/mobile/android/app/src/main/res/values-night/styles.xml @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/android/app/src/main/res/values/styles.xml index cb1ef88..5fac679 100644 --- a/mobile/android/app/src/main/res/values/styles.xml +++ b/mobile/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + diff --git a/mobile/android/app/src/main/res/xml/wearable_app_values.xml b/mobile/android/app/src/main/res/xml/wearable_app_values.xml index 7d4a277..8ba8406 100644 --- a/mobile/android/app/src/main/res/xml/wearable_app_values.xml +++ b/mobile/android/app/src/main/res/xml/wearable_app_values.xml @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml index 399f698..8ffe024 100644 --- a/mobile/android/app/src/profile/AndroidManifest.xml +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/mobile/android/build.gradle.kts b/mobile/android/build.gradle.kts index dbee657..1f88145 100644 --- a/mobile/android/build.gradle.kts +++ b/mobile/android/build.gradle.kts @@ -1,24 +1,24 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = - rootProject.layout.buildDirectory - .dir("../../build") - .get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties index fbee1d8..21dbfa5 100644 --- a/mobile/android/gradle.properties +++ b/mobile/android/gradle.properties @@ -1,2 +1,2 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties index e4ef43f..db3f453 100644 --- a/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/mobile/android/settings.gradle.kts b/mobile/android/settings.gradle.kts index ca7fe06..4dcef4b 100644 --- a/mobile/android/settings.gradle.kts +++ b/mobile/android/settings.gradle.kts @@ -1,26 +1,26 @@ -pluginManagement { - val flutterSdkPath = - run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.11.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false -} - -include(":app") +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore index 7a7f987..ad322bc 100644 --- a/mobile/ios/.gitignore +++ b/mobile/ios/.gitignore @@ -1,34 +1,34 @@ -**/dgph -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobile/ios/Flutter/AppFrameworkInfo.plist b/mobile/ios/Flutter/AppFrameworkInfo.plist index 391a902..256cf28 100644 --- a/mobile/ios/Flutter/AppFrameworkInfo.plist +++ b/mobile/ios/Flutter/AppFrameworkInfo.plist @@ -1,24 +1,24 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - - + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/mobile/ios/Flutter/Debug.xcconfig b/mobile/ios/Flutter/Debug.xcconfig index 592ceee..0b2d479 100644 --- a/mobile/ios/Flutter/Debug.xcconfig +++ b/mobile/ios/Flutter/Debug.xcconfig @@ -1 +1 @@ -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/mobile/ios/Flutter/Release.xcconfig b/mobile/ios/Flutter/Release.xcconfig index 592ceee..0b2d479 100644 --- a/mobile/ios/Flutter/Release.xcconfig +++ b/mobile/ios/Flutter/Release.xcconfig @@ -1 +1 @@ -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index f619b68..8a83d55 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -1,620 +1,620 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 919434a..c4b79bd 100644 --- a/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d9810..fc6bf80 100644 --- a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index f9b0d7c..af0309c 100644 --- a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d4..bbabc4e 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,101 +1,101 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..59c6d39 100644 --- a/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d9810..fc6bf80 100644 --- a/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index f9b0d7c..af0309c 100644 --- a/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index c30b367..ed1c097 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -1,16 +1,16 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { - GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) - } -} +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..1950fd8 100644 --- a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,122 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index 0bedcf2..d08a4de 100644 --- a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +1,23 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md index 89c2725..65a94b5 100644 --- a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -1,5 +1,5 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard index f2e259c..497371e 100644 --- a/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner/Base.lproj/Main.storyboard b/mobile/ios/Runner/Base.lproj/Main.storyboard index f3c2851..bbb83ca 100644 --- a/mobile/ios/Runner/Base.lproj/Main.storyboard +++ b/mobile/ios/Runner/Base.lproj/Main.storyboard @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index a821fdb..1c396fb 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -1,70 +1,70 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Mobile - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - mobile - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneClassName - UIWindowScene - UISceneConfigurationName - flutter - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Mobile + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + mobile + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/mobile/ios/Runner/Runner-Bridging-Header.h b/mobile/ios/Runner/Runner-Bridging-Header.h index 308a2a5..fae207f 100644 --- a/mobile/ios/Runner/Runner-Bridging-Header.h +++ b/mobile/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" +#import "GeneratedPluginRegistrant.h" diff --git a/mobile/ios/Runner/SceneDelegate.swift b/mobile/ios/Runner/SceneDelegate.swift index b9ce8ea..b79be9b 100644 --- a/mobile/ios/Runner/SceneDelegate.swift +++ b/mobile/ios/Runner/SceneDelegate.swift @@ -1,6 +1,6 @@ -import Flutter -import UIKit - -class SceneDelegate: FlutterSceneDelegate { - -} +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/mobile/ios/RunnerTests/RunnerTests.swift b/mobile/ios/RunnerTests/RunnerTests.swift index 86a7c3b..4d206de 100644 --- a/mobile/ios/RunnerTests/RunnerTests.swift +++ b/mobile/ios/RunnerTests/RunnerTests.swift @@ -1,12 +1,12 @@ -import Flutter -import UIKit -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile/lib/core/network/dio_client.dart b/mobile/lib/core/network/dio_client.dart index f81872c..7169dab 100644 --- a/mobile/lib/core/network/dio_client.dart +++ b/mobile/lib/core/network/dio_client.dart @@ -1,44 +1,44 @@ -import 'dart:io'; -import 'package:dio/dio.dart'; -import 'package:dio/io.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -class DioClient { - final Dio _dio = Dio(); - final FlutterSecureStorage _storage = const FlutterSecureStorage(); - - DioClient() { - // On enlève le slash final ici - _dio.options.baseUrl = "https://apicesizen.sam-coffre.duckdns.org/api"; - _dio.options.connectTimeout = const Duration(seconds: 10); - _dio.options.receiveTimeout = const Duration(seconds: 10); - _dio.options.headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }; - - // Bypass SSL pour éviter l'erreur "unknown" sur certains certificats DuckDNS/Let's Encrypt - _dio.httpClientAdapter = IOHttpClientAdapter( - createHttpClient: () { - final client = HttpClient(); - client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; - return client; - }, - ); - - _dio.interceptors.add(InterceptorsWrapper( - onRequest: (options, handler) async { - String? token = await _storage.read(key: 'token'); - if (token != null) { - options.headers['Authorization'] = 'Bearer $token'; - } - return handler.next(options); - }, - onError: (DioException e, handler) { - return handler.next(e); - }, - )); - } - - Dio get dio => _dio; -} +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class DioClient { + final Dio _dio = Dio(); + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + DioClient() { + // On enlève le slash final ici + _dio.options.baseUrl = "https://apicesizen.sam-coffre.duckdns.org/api"; + _dio.options.connectTimeout = const Duration(seconds: 10); + _dio.options.receiveTimeout = const Duration(seconds: 10); + _dio.options.headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }; + + // Bypass SSL pour éviter l'erreur "unknown" sur certains certificats DuckDNS/Let's Encrypt + _dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + final client = HttpClient(); + client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; + return client; + }, + ); + + _dio.interceptors.add(InterceptorsWrapper( + onRequest: (options, handler) async { + String? token = await _storage.read(key: 'token'); + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + return handler.next(options); + }, + onError: (DioException e, handler) { + return handler.next(e); + }, + )); + } + + Dio get dio => _dio; +} diff --git a/mobile/lib/features/auth/models/user.dart b/mobile/lib/features/auth/models/user.dart index f769f07..fb08dcc 100644 --- a/mobile/lib/features/auth/models/user.dart +++ b/mobile/lib/features/auth/models/user.dart @@ -1,27 +1,27 @@ -class User { - final int id; - final String name; - final String email; - - User({ - required this.id, - required this.name, - required this.email, - }); - - factory User.fromJson(Map json) { - return User( - id: json['id'], - name: json['name'], - email: json['email'], - ); - } - - Map toJson() { - return { - 'id': id, - 'name': name, - 'email': email, - }; - } -} +class User { + final int id; + final String name; + final String email; + + User({ + required this.id, + required this.name, + required this.email, + }); + + factory User.fromJson(Map json) { + return User( + id: json['id'], + name: json['name'], + email: json['email'], + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'email': email, + }; + } +} diff --git a/mobile/lib/features/auth/providers/auth_provider.dart b/mobile/lib/features/auth/providers/auth_provider.dart index 445ab99..832b688 100644 --- a/mobile/lib/features/auth/providers/auth_provider.dart +++ b/mobile/lib/features/auth/providers/auth_provider.dart @@ -1,127 +1,127 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import '../models/user.dart'; -import '../services/auth_service.dart'; - -class AuthProvider with ChangeNotifier { - final AuthService _authService = AuthService(); - static const _platform = MethodChannel('com.cesizen/wearable'); - - User? _user; - bool _isLoading = false; - String? _errorMessage; - - AuthProvider() { - // Écouter les demandes de mise à jour venant de la couche native (montre -> téléphone) - _platform.setMethodCallHandler((call) async { - if (call.method == "requestStatusUpdate") { - _syncWithWear(); - } - }); - - // Tenter de restaurer la session au lancement - _checkInitialAuth(); - } - - Future _checkInitialAuth() async { - _isLoading = true; - notifyListeners(); - - final result = await _authService.getProfile(); - if (result['status'] == 'success') { - _user = result['user']; - _syncWithWear(); - } - - _isLoading = false; - notifyListeners(); - } - - User? get user => _user; - bool get isLoading => _isLoading; - String? get errorMessage => _errorMessage; - bool get isAuthenticated => _user != null; - - void _syncWithWear() { - try { - _platform.invokeMethod('sendAuthStatus', { - 'status': isAuthenticated ? 'authenticated' : 'unauthenticated', - 'userName': _user?.name ?? '', - }); - } catch (e) { - // Échec silencieux si pas d'appareil Wear OS à proximité - debugPrint("Wear OS sync failed: $e"); - } - } - - Future login(String email, String password) async { - _isLoading = true; - _errorMessage = null; - notifyListeners(); - - final result = await _authService.login(email, password); - - _isLoading = false; - if (result['status'] == 'success') { - _user = result['user']; - _syncWithWear(); - notifyListeners(); - return true; - } else { - _errorMessage = result['message']; - notifyListeners(); - return false; - } - } - - Future register(String name, String email, String password, String passwordConfirmation) async { - _isLoading = true; - _errorMessage = null; - notifyListeners(); - - final result = await _authService.register(name, email, password, passwordConfirmation); - - _isLoading = false; - if (result['status'] == 'success') { - _user = result['user']; - _syncWithWear(); - notifyListeners(); - return true; - } else { - if (result['errors'] is Map) { - _errorMessage = (result['errors'] as Map).values.first[0]; - } else { - _errorMessage = result['errors'].toString(); - } - notifyListeners(); - return false; - } - } - - Future logout() async { - await _authService.logout(); - _user = null; - _syncWithWear(); - notifyListeners(); - } - - Future updateProfile(String name, String email, {String? password}) async { - _isLoading = true; - _errorMessage = null; - notifyListeners(); - - final result = await _authService.updateProfile(name, email, password: password); - - _isLoading = false; - if (result['status'] == 'success') { - _user = result['user']; - _syncWithWear(); - notifyListeners(); - return true; - } else { - _errorMessage = result['message']; - notifyListeners(); - return false; - } - } -} +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../models/user.dart'; +import '../services/auth_service.dart'; + +class AuthProvider with ChangeNotifier { + final AuthService _authService = AuthService(); + static const _platform = MethodChannel('com.cesizen/wearable'); + + User? _user; + bool _isLoading = false; + String? _errorMessage; + + AuthProvider() { + // Écouter les demandes de mise à jour venant de la couche native (montre -> téléphone) + _platform.setMethodCallHandler((call) async { + if (call.method == "requestStatusUpdate") { + _syncWithWear(); + } + }); + + // Tenter de restaurer la session au lancement + _checkInitialAuth(); + } + + Future _checkInitialAuth() async { + _isLoading = true; + notifyListeners(); + + final result = await _authService.getProfile(); + if (result['status'] == 'success') { + _user = result['user']; + _syncWithWear(); + } + + _isLoading = false; + notifyListeners(); + } + + User? get user => _user; + bool get isLoading => _isLoading; + String? get errorMessage => _errorMessage; + bool get isAuthenticated => _user != null; + + void _syncWithWear() { + try { + _platform.invokeMethod('sendAuthStatus', { + 'status': isAuthenticated ? 'authenticated' : 'unauthenticated', + 'userName': _user?.name ?? '', + }); + } catch (e) { + // Échec silencieux si pas d'appareil Wear OS à proximité + debugPrint("Wear OS sync failed: $e"); + } + } + + Future login(String email, String password) async { + _isLoading = true; + _errorMessage = null; + notifyListeners(); + + final result = await _authService.login(email, password); + + _isLoading = false; + if (result['status'] == 'success') { + _user = result['user']; + _syncWithWear(); + notifyListeners(); + return true; + } else { + _errorMessage = result['message']; + notifyListeners(); + return false; + } + } + + Future register(String name, String email, String password, String passwordConfirmation) async { + _isLoading = true; + _errorMessage = null; + notifyListeners(); + + final result = await _authService.register(name, email, password, passwordConfirmation); + + _isLoading = false; + if (result['status'] == 'success') { + _user = result['user']; + _syncWithWear(); + notifyListeners(); + return true; + } else { + if (result['errors'] is Map) { + _errorMessage = (result['errors'] as Map).values.first[0]; + } else { + _errorMessage = result['errors'].toString(); + } + notifyListeners(); + return false; + } + } + + Future logout() async { + await _authService.logout(); + _user = null; + _syncWithWear(); + notifyListeners(); + } + + Future updateProfile(String name, String email, {String? password}) async { + _isLoading = true; + _errorMessage = null; + notifyListeners(); + + final result = await _authService.updateProfile(name, email, password: password); + + _isLoading = false; + if (result['status'] == 'success') { + _user = result['user']; + _syncWithWear(); + notifyListeners(); + return true; + } else { + _errorMessage = result['message']; + notifyListeners(); + return false; + } + } +} diff --git a/mobile/lib/features/auth/screens/login_screen.dart b/mobile/lib/features/auth/screens/login_screen.dart index c3f2198..0287429 100644 --- a/mobile/lib/features/auth/screens/login_screen.dart +++ b/mobile/lib/features/auth/screens/login_screen.dart @@ -1,124 +1,124 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../providers/auth_provider.dart'; -import 'register_screen.dart'; - -class LoginScreen extends StatefulWidget { - const LoginScreen({super.key}); - - @override - State createState() => _LoginScreenState(); -} - -class _LoginScreenState extends State { - final _formKey = GlobalKey(); - final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - - @override - void dispose() { - _emailController.dispose(); - _passwordController.dispose(); - super.dispose(); - } - - void _login() async { - if (_formKey.currentState!.validate()) { - final authProvider = Provider.of(context, listen: false); - final success = await authProvider.login( - _emailController.text, - _passwordController.text, - ); - - if (success) { - // Rediriger vers l'accueil ou le dashboard - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Connexion réussie')), - ); - Navigator.of(context).pop(); // Retourne à l'écran précédent (HomeScreen) - } - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(authProvider.errorMessage ?? 'Échec de la connexion')), - ); - } - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Connexion CESIZen')), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Bienvenue sur CESIZen', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 32), - TextFormField( - controller: _emailController, - decoration: const InputDecoration( - labelText: 'Email', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.email), - ), - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value == null || value.isEmpty) return 'Veuillez entrer votre email'; - if (!value.contains('@')) return 'Email invalide'; - return null; - }, - ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - decoration: const InputDecoration( - labelText: 'Mot de passe', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.lock), - ), - obscureText: true, - validator: (value) { - if (value == null || value.isEmpty) return 'Veuillez entrer votre mot de passe'; - return null; - }, - ), - const SizedBox(height: 24), - Consumer( - builder: (context, auth, child) { - return auth.isLoading - ? const CircularProgressIndicator() - : ElevatedButton( - onPressed: _login, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(50), - ), - child: const Text('Se connecter'), - ); - }, - ), - TextButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const RegisterScreen()), - ); - }, - child: const Text("Pas encore de compte ? S'inscrire"), - ), - ], - ), - ), - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../providers/auth_provider.dart'; +import 'register_screen.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final _formKey = GlobalKey(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + void _login() async { + if (_formKey.currentState!.validate()) { + final authProvider = Provider.of(context, listen: false); + final success = await authProvider.login( + _emailController.text, + _passwordController.text, + ); + + if (success) { + // Rediriger vers l'accueil ou le dashboard + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Connexion réussie')), + ); + Navigator.of(context).pop(); // Retourne à l'écran précédent (HomeScreen) + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(authProvider.errorMessage ?? 'Échec de la connexion')), + ); + } + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Connexion CESIZen')), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Bienvenue sur CESIZen', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 32), + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + labelText: 'Email', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.email), + ), + keyboardType: TextInputType.emailAddress, + validator: (value) { + if (value == null || value.isEmpty) return 'Veuillez entrer votre email'; + if (!value.contains('@')) return 'Email invalide'; + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + decoration: const InputDecoration( + labelText: 'Mot de passe', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.lock), + ), + obscureText: true, + validator: (value) { + if (value == null || value.isEmpty) return 'Veuillez entrer votre mot de passe'; + return null; + }, + ), + const SizedBox(height: 24), + Consumer( + builder: (context, auth, child) { + return auth.isLoading + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: _login, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(50), + ), + child: const Text('Se connecter'), + ); + }, + ), + TextButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const RegisterScreen()), + ); + }, + child: const Text("Pas encore de compte ? S'inscrire"), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/auth/screens/profile_screen.dart b/mobile/lib/features/auth/screens/profile_screen.dart index d38c277..2233734 100644 --- a/mobile/lib/features/auth/screens/profile_screen.dart +++ b/mobile/lib/features/auth/screens/profile_screen.dart @@ -1,267 +1,267 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../providers/auth_provider.dart'; -import '../../tracker/providers/emotion_provider.dart'; -import 'package:intl/intl.dart'; - -class ProfileScreen extends StatefulWidget { - const ProfileScreen({super.key}); - - @override - State createState() => _ProfileScreenState(); -} - -class _ProfileScreenState extends State { - final _formKey = GlobalKey(); - late TextEditingController _nameController; - late TextEditingController _emailController; - late TextEditingController _passwordController; - late String _currentEmail; - bool _isEditing = false; - - @override - void initState() { - super.initState(); - final user = Provider.of(context, listen: false).user; - _currentEmail = user?.email ?? ''; - _nameController = TextEditingController(text: user?.name); - _emailController = TextEditingController(text: _currentEmail); - _passwordController = TextEditingController(); - - _emailController.addListener(() { - if (mounted) setState(() {}); - }); - - // Charger les émotions pour le journal - WidgetsBinding.instance.addPostFrameCallback((_) { - Provider.of(context, listen: false).loadEmotions(); - }); - } - - @override - void dispose() { - _nameController.dispose(); - _emailController.dispose(); - _passwordController.dispose(); - super.dispose(); - } - - void _submitUpdate() async { - if (_formKey.currentState!.validate()) { - final authProvider = Provider.of(context, listen: false); - - final String newName = _nameController.text.trim(); - final String newEmail = _emailController.text.trim(); - final String? newPassword = _passwordController.text.isNotEmpty ? _passwordController.text : null; - - final success = await authProvider.updateProfile( - newName, - newEmail, - password: newPassword, - ); - - if (mounted) { - if (success) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Profil mis à jour avec succès')), - ); - setState(() { - _isEditing = false; - _currentEmail = newEmail; - _passwordController.clear(); - }); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(authProvider.errorMessage ?? 'Erreur lors de la mise à jour')), - ); - } - } - } - } - - @override - Widget build(BuildContext context) { - final authProvider = Provider.of(context); - final emotionProvider = Provider.of(context); - final user = authProvider.user; - final bool emailChanged = _emailController.text.trim() != _currentEmail.trim(); - - return Scaffold( - appBar: AppBar( - title: const Text('Mon Espace Santé'), - backgroundColor: Colors.white, - elevation: 0, - actions: [ - IconButton( - icon: Icon(_isEditing ? Icons.close : Icons.edit), - onPressed: () { - setState(() { - _isEditing = !_isEditing; - if (!_isEditing) { - _nameController.text = user?.name ?? ''; - _emailController.text = user?.email ?? ''; - _passwordController.clear(); - } - }); - }, - ) - ], - ), - body: SingleChildScrollView( - child: Column( - children: [ - // Section Profil - Padding( - padding: const EdgeInsets.all(24.0), - child: Form( - key: _formKey, - child: Column( - children: [ - const CircleAvatar( - radius: 50, - backgroundColor: Color(0xFF000080), - child: Icon(Icons.person, size: 50, color: Colors.white), - ), - const SizedBox(height: 20), - TextFormField( - controller: _nameController, - enabled: _isEditing, - decoration: const InputDecoration( - labelText: 'Nom', - prefixIcon: Icon(Icons.person_outline), - border: OutlineInputBorder(), - ), - validator: (value) => (value == null || value.trim().isEmpty) ? 'Veuillez entrer un nom' : null, - ), - const SizedBox(height: 15), - TextFormField( - controller: _emailController, - enabled: _isEditing, - decoration: const InputDecoration( - labelText: 'Email', - prefixIcon: Icon(Icons.email_outlined), - border: OutlineInputBorder(), - ), - validator: (value) { - final email = value?.trim() ?? ''; - if (email.isEmpty) return 'Veuillez entrer un email'; - if (!email.contains('@')) return 'Email invalide'; - return null; - }, - ), - if (_isEditing) ...[ - const SizedBox(height: 15), - TextFormField( - controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - labelText: emailChanged ? 'Mot de passe requis' : 'Nouveau mot de passe', - prefixIcon: const Icon(Icons.lock_outline), - border: const OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: authProvider.isLoading ? null : _submitUpdate, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF000080), - foregroundColor: Colors.white, - minimumSize: const Size.fromHeight(50), - ), - child: const Text('ENREGISTRER'), - ), - ], - ], - ), - ), - ), - - // Section Journal Émotionnel (Le "Journal Santé") - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - color: Colors.grey.shade50, - child: const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Journal Émotionnel', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF000080)), - ), - Text('Historique de vos saisies (Mobile & Montre)', style: TextStyle(color: Colors.grey, fontSize: 12)), - ], - ), - ), - - if (emotionProvider.isLoading) - const Padding( - padding: EdgeInsets.all(20.0), - child: CircularProgressIndicator(), - ) - else if (emotionProvider.entries.isEmpty) - const Padding( - padding: EdgeInsets.all(40.0), - child: Column( - children: [ - Icon(Icons.history, size: 48, color: Colors.grey), - SizedBox(height: 10), - Text('Aucun historique pour le moment.', style: TextStyle(color: Colors.grey)), - ], - ), - ) - else - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: emotionProvider.entries.length, - separatorBuilder: (context, index) => const Divider(height: 1), - itemBuilder: (context, index) { - final entry = emotionProvider.entries[index]; - final dateStr = DateFormat('dd/MM/yyyy HH:mm').format(entry.createdAt); - return ListTile( - leading: _getMoodEmoji(entry.emotion), - title: Text(entry.emotion, style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text(entry.note ?? 'Saisie rapide'), - trailing: Text(dateStr, style: const TextStyle(fontSize: 11, color: Colors.grey)), - ); - }, - ), - - const SizedBox(height: 100), // Espace pour le scroll - ], - ), - ), - ); - } - - Widget _getMoodEmoji(String mood) { - String emoji = "😐"; - Color color = Colors.grey; - switch (mood) { - case 'Très bien': - emoji = "😊"; - color = const Color(0xFF4CAF50); - break; - case 'Bien': - emoji = "🙂"; - color = const Color(0xFF8BC34A); - break; - case 'Neutre': - emoji = "😐"; - color = const Color(0xFFFFC107); - break; - case 'Pas top': - emoji = "🙁"; - color = const Color(0xFFFF9800); - break; - case 'Stressé': - emoji = "😫"; - color = const Color(0xFFF44336); - break; - } - return CircleAvatar( - backgroundColor: color.withOpacity(0.1), - child: Text(emoji, style: const TextStyle(fontSize: 20)), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../providers/auth_provider.dart'; +import '../../tracker/providers/emotion_provider.dart'; +import 'package:intl/intl.dart'; + +class ProfileScreen extends StatefulWidget { + const ProfileScreen({super.key}); + + @override + State createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + final _formKey = GlobalKey(); + late TextEditingController _nameController; + late TextEditingController _emailController; + late TextEditingController _passwordController; + late String _currentEmail; + bool _isEditing = false; + + @override + void initState() { + super.initState(); + final user = Provider.of(context, listen: false).user; + _currentEmail = user?.email ?? ''; + _nameController = TextEditingController(text: user?.name); + _emailController = TextEditingController(text: _currentEmail); + _passwordController = TextEditingController(); + + _emailController.addListener(() { + if (mounted) setState(() {}); + }); + + // Charger les émotions pour le journal + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of(context, listen: false).loadEmotions(); + }); + } + + @override + void dispose() { + _nameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + void _submitUpdate() async { + if (_formKey.currentState!.validate()) { + final authProvider = Provider.of(context, listen: false); + + final String newName = _nameController.text.trim(); + final String newEmail = _emailController.text.trim(); + final String? newPassword = _passwordController.text.isNotEmpty ? _passwordController.text : null; + + final success = await authProvider.updateProfile( + newName, + newEmail, + password: newPassword, + ); + + if (mounted) { + if (success) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Profil mis à jour avec succès')), + ); + setState(() { + _isEditing = false; + _currentEmail = newEmail; + _passwordController.clear(); + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(authProvider.errorMessage ?? 'Erreur lors de la mise à jour')), + ); + } + } + } + } + + @override + Widget build(BuildContext context) { + final authProvider = Provider.of(context); + final emotionProvider = Provider.of(context); + final user = authProvider.user; + final bool emailChanged = _emailController.text.trim() != _currentEmail.trim(); + + return Scaffold( + appBar: AppBar( + title: const Text('Mon Espace Santé'), + backgroundColor: Colors.white, + elevation: 0, + actions: [ + IconButton( + icon: Icon(_isEditing ? Icons.close : Icons.edit), + onPressed: () { + setState(() { + _isEditing = !_isEditing; + if (!_isEditing) { + _nameController.text = user?.name ?? ''; + _emailController.text = user?.email ?? ''; + _passwordController.clear(); + } + }); + }, + ) + ], + ), + body: SingleChildScrollView( + child: Column( + children: [ + // Section Profil + Padding( + padding: const EdgeInsets.all(24.0), + child: Form( + key: _formKey, + child: Column( + children: [ + const CircleAvatar( + radius: 50, + backgroundColor: Color(0xFF000080), + child: Icon(Icons.person, size: 50, color: Colors.white), + ), + const SizedBox(height: 20), + TextFormField( + controller: _nameController, + enabled: _isEditing, + decoration: const InputDecoration( + labelText: 'Nom', + prefixIcon: Icon(Icons.person_outline), + border: OutlineInputBorder(), + ), + validator: (value) => (value == null || value.trim().isEmpty) ? 'Veuillez entrer un nom' : null, + ), + const SizedBox(height: 15), + TextFormField( + controller: _emailController, + enabled: _isEditing, + decoration: const InputDecoration( + labelText: 'Email', + prefixIcon: Icon(Icons.email_outlined), + border: OutlineInputBorder(), + ), + validator: (value) { + final email = value?.trim() ?? ''; + if (email.isEmpty) return 'Veuillez entrer un email'; + if (!email.contains('@')) return 'Email invalide'; + return null; + }, + ), + if (_isEditing) ...[ + const SizedBox(height: 15), + TextFormField( + controller: _passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: emailChanged ? 'Mot de passe requis' : 'Nouveau mot de passe', + prefixIcon: const Icon(Icons.lock_outline), + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: authProvider.isLoading ? null : _submitUpdate, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF000080), + foregroundColor: Colors.white, + minimumSize: const Size.fromHeight(50), + ), + child: const Text('ENREGISTRER'), + ), + ], + ], + ), + ), + ), + + // Section Journal Émotionnel (Le "Journal Santé") + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + color: Colors.grey.shade50, + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Journal Émotionnel', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Color(0xFF000080)), + ), + Text('Historique de vos saisies (Mobile & Montre)', style: TextStyle(color: Colors.grey, fontSize: 12)), + ], + ), + ), + + if (emotionProvider.isLoading) + const Padding( + padding: EdgeInsets.all(20.0), + child: CircularProgressIndicator(), + ) + else if (emotionProvider.entries.isEmpty) + const Padding( + padding: EdgeInsets.all(40.0), + child: Column( + children: [ + Icon(Icons.history, size: 48, color: Colors.grey), + SizedBox(height: 10), + Text('Aucun historique pour le moment.', style: TextStyle(color: Colors.grey)), + ], + ), + ) + else + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: emotionProvider.entries.length, + separatorBuilder: (context, index) => const Divider(height: 1), + itemBuilder: (context, index) { + final entry = emotionProvider.entries[index]; + final dateStr = DateFormat('dd/MM/yyyy HH:mm').format(entry.createdAt); + return ListTile( + leading: _getMoodEmoji(entry.emotion), + title: Text(entry.emotion, style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text(entry.note ?? 'Saisie rapide'), + trailing: Text(dateStr, style: const TextStyle(fontSize: 11, color: Colors.grey)), + ); + }, + ), + + const SizedBox(height: 100), // Espace pour le scroll + ], + ), + ), + ); + } + + Widget _getMoodEmoji(String mood) { + String emoji = "😐"; + Color color = Colors.grey; + switch (mood) { + case 'Très bien': + emoji = "😊"; + color = const Color(0xFF4CAF50); + break; + case 'Bien': + emoji = "🙂"; + color = const Color(0xFF8BC34A); + break; + case 'Neutre': + emoji = "😐"; + color = const Color(0xFFFFC107); + break; + case 'Pas top': + emoji = "🙁"; + color = const Color(0xFFFF9800); + break; + case 'Stressé': + emoji = "😫"; + color = const Color(0xFFF44336); + break; + } + return CircleAvatar( + backgroundColor: color.withOpacity(0.1), + child: Text(emoji, style: const TextStyle(fontSize: 20)), + ); + } +} diff --git a/mobile/lib/features/auth/screens/register_screen.dart b/mobile/lib/features/auth/screens/register_screen.dart index a9d4693..e52baac 100644 --- a/mobile/lib/features/auth/screens/register_screen.dart +++ b/mobile/lib/features/auth/screens/register_screen.dart @@ -1,132 +1,132 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../providers/auth_provider.dart'; - -class RegisterScreen extends StatefulWidget { - const RegisterScreen({super.key}); - - @override - State createState() => _RegisterScreenState(); -} - -class _RegisterScreenState extends State { - final _formKey = GlobalKey(); - final _nameController = TextEditingController(); - final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - final _confirmPasswordController = TextEditingController(); - - @override - void dispose() { - _nameController.dispose(); - _emailController.dispose(); - _passwordController.dispose(); - _confirmPasswordController.dispose(); - super.dispose(); - } - - void _register() async { - if (_formKey.currentState!.validate()) { - final authProvider = Provider.of(context, listen: false); - final success = await authProvider.register( - _nameController.text, - _emailController.text, - _passwordController.text, - _confirmPasswordController.text, - ); - - if (success) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Compte créé avec succès !')), - ); - // On vide la pile et on retourne au menu principal (HomeScreen) - Navigator.of(context).popUntil((route) => route.isFirst); - } - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(authProvider.errorMessage ?? 'Échec de l\'inscription')), - ); - } - } - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Inscription CESIZen')), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: Column( - children: [ - const SizedBox(height: 20), - TextFormField( - controller: _nameController, - decoration: const InputDecoration( - labelText: 'Nom complet', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.person), - ), - validator: (value) => value == null || value.isEmpty ? 'Entrez votre nom' : null, - ), - const SizedBox(height: 16), - TextFormField( - controller: _emailController, - decoration: const InputDecoration( - labelText: 'Email', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.email), - ), - keyboardType: TextInputType.emailAddress, - validator: (value) => value == null || !value.contains('@') ? 'Email invalide' : null, - ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - decoration: const InputDecoration( - labelText: 'Mot de passe', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.lock), - ), - obscureText: true, - validator: (value) => value == null || value.length < 8 ? '8 caractères minimum' : null, - ), - const SizedBox(height: 16), - TextFormField( - controller: _confirmPasswordController, - decoration: const InputDecoration( - labelText: 'Confirmer le mot de passe', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.lock_outline), - ), - obscureText: true, - validator: (value) { - if (value != _passwordController.text) return 'Les mots de passe ne correspondent pas'; - return null; - }, - ), - const SizedBox(height: 24), - Consumer( - builder: (context, auth, child) { - return auth.isLoading - ? const CircularProgressIndicator() - : ElevatedButton( - onPressed: _register, - style: ElevatedButton.styleFrom( - minimumSize: const Size.fromHeight(50), - ), - child: const Text('S\'inscrire'), - ); - }, - ), - ], - ), - ), - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../providers/auth_provider.dart'; + +class RegisterScreen extends StatefulWidget { + const RegisterScreen({super.key}); + + @override + State createState() => _RegisterScreenState(); +} + +class _RegisterScreenState extends State { + final _formKey = GlobalKey(); + final _nameController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + final _confirmPasswordController = TextEditingController(); + + @override + void dispose() { + _nameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + _confirmPasswordController.dispose(); + super.dispose(); + } + + void _register() async { + if (_formKey.currentState!.validate()) { + final authProvider = Provider.of(context, listen: false); + final success = await authProvider.register( + _nameController.text, + _emailController.text, + _passwordController.text, + _confirmPasswordController.text, + ); + + if (success) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Compte créé avec succès !')), + ); + // On vide la pile et on retourne au menu principal (HomeScreen) + Navigator.of(context).popUntil((route) => route.isFirst); + } + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(authProvider.errorMessage ?? 'Échec de l\'inscription')), + ); + } + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Inscription CESIZen')), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + children: [ + const SizedBox(height: 20), + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + labelText: 'Nom complet', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.person), + ), + validator: (value) => value == null || value.isEmpty ? 'Entrez votre nom' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + labelText: 'Email', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.email), + ), + keyboardType: TextInputType.emailAddress, + validator: (value) => value == null || !value.contains('@') ? 'Email invalide' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + decoration: const InputDecoration( + labelText: 'Mot de passe', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.lock), + ), + obscureText: true, + validator: (value) => value == null || value.length < 8 ? '8 caractères minimum' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: _confirmPasswordController, + decoration: const InputDecoration( + labelText: 'Confirmer le mot de passe', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.lock_outline), + ), + obscureText: true, + validator: (value) { + if (value != _passwordController.text) return 'Les mots de passe ne correspondent pas'; + return null; + }, + ), + const SizedBox(height: 24), + Consumer( + builder: (context, auth, child) { + return auth.isLoading + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: _register, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(50), + ), + child: const Text('S\'inscrire'), + ); + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/auth/services/auth_service.dart b/mobile/lib/features/auth/services/auth_service.dart index dfad7c5..96f7154 100644 --- a/mobile/lib/features/auth/services/auth_service.dart +++ b/mobile/lib/features/auth/services/auth_service.dart @@ -1,130 +1,130 @@ -import 'package:dio/dio.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import '../../../core/network/dio_client.dart'; -import '../models/user.dart'; - -class AuthService { - final Dio _dio = DioClient().dio; - final FlutterSecureStorage _storage = const FlutterSecureStorage(); - - Future> login(String email, String password) async { - try { - final response = await _dio.post('/login', data: { - 'email': email, - 'password': password, - }); - - if (response.statusCode == 200) { - String token = response.data['token']; - await _storage.write(key: 'token', value: token); - return { - 'status': 'success', - 'user': User.fromJson(response.data['user']), - }; - } - return {'status': 'error', 'message': 'Erreur inconnue'}; - } on DioException catch (e) { - return { - 'status': 'error', - 'message': e.response?.data['message'] ?? 'Erreur de connexion' - }; - } - } - - Future> register(String name, String email, String password, String passwordConfirmation) async { - try { - final response = await _dio.post('/register', data: { - 'name': name, - 'email': email, - 'password': password, - 'password_confirmation': passwordConfirmation, - }); - - if (response.statusCode == 201) { - String token = response.data['token']; - await _storage.write(key: 'token', value: token); - return { - 'status': 'success', - 'user': User.fromJson(response.data['user']), - }; - } - return {'status': 'error', 'message': 'Erreur inconnue'}; - } on DioException catch (e) { - return { - 'status': 'error', - 'errors': e.response?.data['errors'] ?? 'Erreur lors de l\'inscription' - }; - } - } - - Future> updateProfile(String name, String email, {String? password}) async { - try { - String? token = await getToken(); - - final Map data = { - 'name': name, - 'email': email, - }; - - if (password != null && password.isNotEmpty) { - data['current_password'] = password; - data['password'] = password; - data['password_confirmation'] = password; - } - - final response = await _dio.post( - '/update-profile', - data: data, - options: Options( - headers: { - 'Authorization': 'Bearer $token', - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - ), - ); - - if (response.statusCode == 200) { - return { - 'status': 'success', - 'user': User.fromJson(response.data['user']), - }; - } - return {'status': 'error', 'message': 'Erreur lors de la mise à jour'}; - } on DioException catch (e) { - return { - 'status': 'error', - 'message': e.response?.data['message'] ?? 'Erreur lors de la modification du profil' - }; - } - } - - Future> getProfile() async { - try { - String? token = await getToken(); - if (token == null) return {'status': 'error', 'message': 'No token'}; - - final response = await _dio.get('/profile', options: Options( - headers: {'Authorization': 'Bearer $token'} - )); - - if (response.statusCode == 200) { - return { - 'status': 'success', - 'user': User.fromJson(response.data), - }; - } - return {'status': 'error', 'message': 'Session expirée'}; - } catch (e) { - return {'status': 'error', 'message': e.toString()}; - } - } - - Future logout() async { - await _storage.delete(key: 'token'); - } - - Future getToken() async { - return await _storage.read(key: 'token'); - } -} +import 'package:dio/dio.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import '../../../core/network/dio_client.dart'; +import '../models/user.dart'; + +class AuthService { + final Dio _dio = DioClient().dio; + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + Future> login(String email, String password) async { + try { + final response = await _dio.post('/login', data: { + 'email': email, + 'password': password, + }); + + if (response.statusCode == 200) { + String token = response.data['token']; + await _storage.write(key: 'token', value: token); + return { + 'status': 'success', + 'user': User.fromJson(response.data['user']), + }; + } + return {'status': 'error', 'message': 'Erreur inconnue'}; + } on DioException catch (e) { + return { + 'status': 'error', + 'message': e.response?.data['message'] ?? 'Erreur de connexion' + }; + } + } + + Future> register(String name, String email, String password, String passwordConfirmation) async { + try { + final response = await _dio.post('/register', data: { + 'name': name, + 'email': email, + 'password': password, + 'password_confirmation': passwordConfirmation, + }); + + if (response.statusCode == 201) { + String token = response.data['token']; + await _storage.write(key: 'token', value: token); + return { + 'status': 'success', + 'user': User.fromJson(response.data['user']), + }; + } + return {'status': 'error', 'message': 'Erreur inconnue'}; + } on DioException catch (e) { + return { + 'status': 'error', + 'errors': e.response?.data['errors'] ?? 'Erreur lors de l\'inscription' + }; + } + } + + Future> updateProfile(String name, String email, {String? password}) async { + try { + String? token = await getToken(); + + final Map data = { + 'name': name, + 'email': email, + }; + + if (password != null && password.isNotEmpty) { + data['current_password'] = password; + data['password'] = password; + data['password_confirmation'] = password; + } + + final response = await _dio.post( + '/update-profile', + data: data, + options: Options( + headers: { + 'Authorization': 'Bearer $token', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return { + 'status': 'success', + 'user': User.fromJson(response.data['user']), + }; + } + return {'status': 'error', 'message': 'Erreur lors de la mise à jour'}; + } on DioException catch (e) { + return { + 'status': 'error', + 'message': e.response?.data['message'] ?? 'Erreur lors de la modification du profil' + }; + } + } + + Future> getProfile() async { + try { + String? token = await getToken(); + if (token == null) return {'status': 'error', 'message': 'No token'}; + + final response = await _dio.get('/profile', options: Options( + headers: {'Authorization': 'Bearer $token'} + )); + + if (response.statusCode == 200) { + return { + 'status': 'success', + 'user': User.fromJson(response.data), + }; + } + return {'status': 'error', 'message': 'Session expirée'}; + } catch (e) { + return {'status': 'error', 'message': e.toString()}; + } + } + + Future logout() async { + await _storage.delete(key: 'token'); + } + + Future getToken() async { + return await _storage.read(key: 'token'); + } +} diff --git a/mobile/lib/features/diagnostics/models/question.dart b/mobile/lib/features/diagnostics/models/question.dart index d353d65..f235ecf 100644 --- a/mobile/lib/features/diagnostics/models/question.dart +++ b/mobile/lib/features/diagnostics/models/question.dart @@ -1,13 +1,13 @@ -class Question { - final String text; - final List