This commit is contained in:
2026-05-14 16:54:32 +00:00
parent 5b2a2e7ddb
commit 15fcbcc9eb
212 changed files with 18586 additions and 10360 deletions
+3 -3
View File
@@ -1,3 +1,3 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
Generated
+1
View File
@@ -0,0 +1 @@
\\192.168.1.50\cesizen\
+51 -51
View File
@@ -1,52 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4"> <module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/mobile/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/build" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.pub" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/build" /> <excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/build" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>
+1783 -1669
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="deploymentTargetSelector"> <component name="deploymentTargetSelector">
<selectionStates> <selectionStates>
<SelectionState runConfigName="Wear App"> <SelectionState runConfigName="Wear App">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>
+7 -7
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="MarkdownSettings"> <component name="MarkdownSettings">
<option name="previewPanelProviderInfo"> <option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" /> <ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option> </option>
</component> </component>
</project> </project>
+7 -7
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/CESIZen.iml" filepath="$PROJECT_DIR$/.idea/CESIZen.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/CESIZen.iml" filepath="$PROJECT_DIR$/.idea/CESIZen.iml" />
</modules> </modules>
</component> </component>
</project> </project>
+7 -7
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Launch All (Mobile &amp; Wear)" type="CompoundRunConfigurationType"> <configuration default="false" name="Launch All (Mobile &amp; Wear)" type="CompoundRunConfigurationType">
<toRun name="Mobile App" type="FlutterRunConfigurationType" /> <toRun name="Mobile App" type="FlutterRunConfigurationType" />
<toRun name="Wear App" type="AndroidRunConfigurationType" /> <toRun name="Wear App" type="AndroidRunConfigurationType" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>
+7 -7
View File
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Mobile App" type="FlutterRunConfigurationType" factoryName="Flutter"> <configuration default="false" name="Mobile App" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/mobile/lib/main.dart" /> <option name="filePath" value="$PROJECT_DIR$/mobile/lib/main.dart" />
<option name="additionalArgs" value="-d PV5HRKVCJVKZ9DOB" /> <option name="additionalArgs" value="-d PV5HRKVCJVKZ9DOB" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>
+68 -68
View File
@@ -1,68 +1,68 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Wear App" type="AndroidRunConfigurationType" factoryName="Android App"> <configuration default="false" name="Wear App" type="AndroidRunConfigurationType" factoryName="Android App">
<module name="CESIZen.Wear.app.main" /> <module name="CESIZen.Wear.app.main" />
<option name="DEPLOY" value="true" /> <option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" /> <option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" /> <option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" /> <option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" /> <option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" /> <option name="ALL_USERS" value="false" />
<option name="ALWAYS_INSTALL_WITH_PM" value="false" /> <option name="ALWAYS_INSTALL_WITH_PM" value="false" />
<option name="CLEAR_APP_STORAGE" value="false" /> <option name="CLEAR_APP_STORAGE" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" /> <option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" /> <option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" /> <option name="MODE" value="default_activity" />
<option name="CLEAR_LOGCAT" value="false" /> <option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" /> <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" /> <option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" /> <option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="PREFERRED_AVD" value="" /> <option name="PREFERRED_AVD" value="" />
<option name="SELECTED_DEVICE_ID" value="adb-d76a0274-X63ZH9 (2)._adb-tls-connect._tcp" /> <option name="SELECTED_DEVICE_ID" value="adb-d76a0274-X63ZH9 (2)._adb-tls-connect._tcp" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" /> <option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" /> <option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" /> <option name="DEBUGGER_TYPE" value="Auto" />
<Auto> <Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" /> <option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" /> <option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" /> <option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto> </Auto>
<Hybrid> <Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" /> <option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" /> <option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" /> <option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid> </Hybrid>
<Java> <Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" /> <option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" /> <option name="DEBUG_SANDBOX_SDK" value="false" />
</Java> </Java>
<Native> <Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> <option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" /> <option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" /> <option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> <option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> <option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" /> <option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" /> <option name="DEBUG_SANDBOX_SDK" value="false" />
</Native> </Native>
<Profilers> <Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" /> <option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" /> <option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="DETAILED_REPORT_APP_STARTUP_PROFILING_ENABLED" value="false" /> <option name="DETAILED_REPORT_APP_STARTUP_PROFILING_ENABLED" value="false" />
</Profilers> </Profilers>
<option name="DEEP_LINK" value="" /> <option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="" /> <option name="ACTIVITY_CLASS" value="" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" /> <option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" /> <option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2"> <method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method> </method>
</configuration> </configuration>
</component> </component>
Generated
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$/" vcs="Git" />
</component> </component>
</project> </project>
+85 -85
View File
@@ -1,85 +1,85 @@
# 🌿 CESIZen - Écosystème de Bien-être Unifié # 🌿 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). 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 ## 🚀 Structure du Projet
- **/WEB** : API et interface d'administration/diagnostic (Laravel 10, DSFR, Alpine.js). - **/WEB** : API et interface d'administration/diagnostic (Laravel 10, DSFR, Alpine.js).
- **/mobile** : Application compagnon pour le suivi et les exercices (Flutter). - **/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). - **/Wear** : Application de montre pour le tracking rapide de l'humeur (Android Wear OS, Kotlin/Compose).
--- ---
## 🛠 Installation & Configuration ## 🛠 Installation & Configuration
### 1. Backend & Web (Laravel) ### 1. Backend & Web (Laravel)
Le serveur central gère l'authentification (Sanctum) et la persistance des données. Le serveur central gère l'authentification (Sanctum) et la persistance des données.
```bash ```bash
cd WEB cd WEB
composer install composer install
npm install npm install
cp .env.example .env # Configurez votre base de données et l'URL du serveur cp .env.example .env # Configurez votre base de données et l'URL du serveur
php artisan key:generate php artisan key:generate
php artisan migrate --seed php artisan migrate --seed
php artisan serve php artisan serve
``` ```
*Note : Assurez-vous que l'IP de votre serveur est accessible par votre téléphone/émulateur pour la synchronisation.* *Note : Assurez-vous que l'IP de votre serveur est accessible par votre téléphone/émulateur pour la synchronisation.*
### 2. Application Mobile (Flutter) ### 2. Application Mobile (Flutter)
L'application mobile se connecte à l'API Laravel. L'application mobile se connecte à l'API Laravel.
```bash ```bash
cd mobile cd mobile
flutter pub get flutter pub get
# Modifiez l'URL de l'API dans lib/features/auth/services/auth_service.dart si nécessaire # Modifiez l'URL de l'API dans lib/features/auth/services/auth_service.dart si nécessaire
flutter run flutter run
``` ```
### 3. Application Montre (Wear OS) ### 3. Application Montre (Wear OS)
La montre communique avec le téléphone via le **Google Wearable Data Layer**. La montre communique avec le téléphone via le **Google Wearable Data Layer**.
1. Ouvrez le dossier `/Wear` dans **Android Studio**. 1. Ouvrez le dossier `/Wear` dans **Android Studio**.
2. Compilez et installez l'APK sur une montre (ou émulateur) Wear OS. 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. 3. Assurez-vous que le téléphone appairé possède également l'application mobile CESIZen installée.
--- ---
## 📱 Utilisation & Fonctionnalités ## 📱 Utilisation & Fonctionnalités
### 🖥️ Plateforme Web ### 🖥️ Plateforme Web
- **Tableau de bord** : Visualisation des statistiques de stress. - **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é). - **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. - **Diagnostics** : Historique détaillé des relevés émotionnels.
### 🤳 Application Mobile ### 🤳 Application Mobile
- **Authentification** : Connexion sécurisée avec persistance de session. - **Authentification** : Connexion sécurisée avec persistance de session.
- **Tracker** : Enregistrement quotidien de l'état émotionnel. - **Tracker** : Enregistrement quotidien de l'état émotionnel.
- **Exercices** : Respiration guidée avec animation synchronisée du logo CESIZen. - **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. - **Sync Montre** : Envoi automatique de la date et réception des humeurs capturées sur la montre.
### ⌚ Application Wear OS ### ⌚ Application Wear OS
- **Quick Mood** : Sélectionnez votre humeur en un clic parmi 5 niveaux. - **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. - **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. - **Indicateur visuel** : Confirmation visuelle immédiate de l'enregistrement.
--- ---
## 🔗 Synchronisation Bidirectionnelle ## 🔗 Synchronisation Bidirectionnelle
- **Mobile -> Montre** : Envoie la `date du jour` pour synchroniser le cycle quotidien. - **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`). - **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. - **Mobile -> Web** : Synchronise les données finales vers la base de données Laravel pour analyse.
--- ---
## 🎨 Identité Visuelle ## 🎨 Identité Visuelle
L'ensemble de l'écosystème utilise la charte graphique **CesiZen** : L'ensemble de l'écosystème utilise la charte graphique **CesiZen** :
- **Couleur Primaire** : Bleu Marine (`#000080`) - **Couleur Primaire** : Bleu Marine (`#000080`)
- **Couleur Accent** : Orange (`#FFCC80`) - **Couleur Accent** : Orange (`#FFCC80`)
- **Typographie** : Marianne (standard DSFR) - **Typographie** : Marianne (standard DSFR)
- **Logo** : Point central de l'animation de respiration. - **Logo** : Point central de l'animation de respiration.
--- ---
## 📄 Licence ## 📄 Licence
Projet réalisé dans le cadre de la formation CDA CESI. Projet réalisé dans le cadre de la formation CDA CESI.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
node_modules
vendor
public/build
.git
.env
+51
View File
@@ -0,0 +1,51 @@
FROM php:8.3-fpm
# 1. Installer les dépendances système
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
libicu-dev \
libzip-dev \
zip \
unzip \
gnupg
# 2. Installer Node.js (pour Vite)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs
# 3. Nettoyer le cache apt
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# 4. Installer les extensions PHP
# On ajoute 'zip' et on configure 'intl' correctement
RUN docker-php-ext-configure intl && \
docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd intl zip
# 5. Installer Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 6. Définir le répertoire de travail
WORKDIR /var/www
# 7. Copier le projet
COPY . .
# 8. Installer les dépendances PHP et Node
RUN composer install --no-interaction --optimize-autoloader --no-dev
RUN npm install
# 9. Forcer les permissions sur les binaires node_modules
RUN chmod -R +x node_modules/.bin/
# 10. Lancer le build de Vite
RUN npm run build
# 11. Fixer les permissions pour Laravel
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
EXPOSE 9000
CMD ["php-fpm"]
@@ -0,0 +1,98 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Filament\Admin\Resources\InformationResource\Pages;
use App\Models\Information;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class InformationResource extends Resource
{
protected static ?string $model = Information::class;
protected static ?string $navigationIcon = 'heroicon-o-information-circle';
protected static ?string $navigationLabel = 'Informations';
protected static ?string $modelLabel = 'Information';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('title')
->label('Titre')
->required()
->maxLength(255),
Forms\Components\TextInput::make('category')
->label('Catégorie')
->required()
->maxLength(255)
->default('Général'),
Forms\Components\RichEditor::make('content')
->label('Contenu')
->required()
->columnSpanFull(),
Forms\Components\FileUpload::make('image_url')
->label('Image')
->image()
->directory('informations'),
Forms\Components\Toggle::make('is_published')
->label('Publié')
->default(true)
->required(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title')
->label('Titre')
->searchable(),
Tables\Columns\TextColumn::make('category')
->label('Catégorie')
->badge()
->searchable(),
Tables\Columns\IconColumn::make('is_published')
->label('Publié')
->boolean(),
Tables\Columns\TextColumn::make('created_at')
->label('Créé le')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TernaryFilter::make('is_published')
->label('Statut de publication')
->boolean()
->trueLabel('Publiés')
->falseLabel('Brouillons')
->native(false),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListInformation::route('/'),
'create' => Pages\CreateInformation::route('/create'),
'edit' => Pages\EditInformation::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\InformationResource\Pages;
use App\Filament\Admin\Resources\InformationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateInformation extends CreateRecord
{
protected static string $resource = InformationResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\InformationResource\Pages;
use App\Filament\Admin\Resources\InformationResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditInformation extends EditRecord
{
protected static string $resource = InformationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\InformationResource\Pages;
use App\Filament\Admin\Resources\InformationResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListInformation extends ListRecords
{
protected static string $resource = InformationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -0,0 +1,103 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
use App\Models\RelaxationActivity;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class RelaxationActivityResource extends Resource
{
protected static ?string $model = RelaxationActivity::class;
protected static ?string $navigationIcon = 'heroicon-o-sparkles';
protected static ?string $navigationLabel = 'Activités de détente';
protected static ?string $modelLabel = 'Activité de détente';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('title')
->label('Titre')
->required()
->maxLength(255),
Forms\Components\Select::make('type')
->options([
'video' => 'Vidéo',
'audio' => 'Audio',
'article' => 'Article',
'exercice' => 'Exercice',
])
->required(),
Forms\Components\TextInput::make('duration')
->label('Durée (min)')
->numeric(),
Forms\Components\TextInput::make('url')
->label('Lien / URL')
->url()
->maxLength(255),
Forms\Components\Textarea::make('description')
->columnSpanFull(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title')
->label('Titre')
->searchable(),
Tables\Columns\TextColumn::make('type')
->badge(),
Tables\Columns\TextColumn::make('duration')
->label('Durée')
->suffix(' min'),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('type')
->options([
'video' => 'Vidéo',
'audio' => 'Audio',
'article' => 'Article',
'exercice' => 'Exercice',
]),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRelaxationActivities::route('/'),
'create' => Pages\CreateRelaxationActivity::route('/create'),
'edit' => Pages\EditRelaxationActivity::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
use App\Filament\Admin\Resources\RelaxationActivityResource;
use Filament\Resources\Pages\CreateRecord;
class CreateRelaxationActivity extends CreateRecord
{
protected static string $resource = RelaxationActivityResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
use App\Filament\Admin\Resources\RelaxationActivityResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditRelaxationActivity extends EditRecord
{
protected static string $resource = RelaxationActivityResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
use App\Filament\Admin\Resources\RelaxationActivityResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListRelaxationActivities extends ListRecords
{
protected static string $resource = RelaxationActivityResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -0,0 +1,89 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Filament\Admin\Resources\StressEventResource\Pages;
use App\Models\StressEvent;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class StressEventResource extends Resource
{
protected static ?string $model = StressEvent::class;
protected static ?string $navigationIcon = 'heroicon-o-bolt';
protected static ?string $navigationLabel = 'Événements de stress';
protected static ?string $modelLabel = 'Événement de stress';
protected static ?string $pluralModelLabel = 'Événements de stress';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('event_name')
->label('Nom de l\'événement')
->required()
->maxLength(255),
Forms\Components\TextInput::make('points')
->label('Points de stress')
->required()
->numeric()
->minValue(0)
->maxValue(100),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('event_name')
->label('Événement')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('points')
->label('Points')
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
->label('Dernière modification')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListStressEvents::route('/'),
'create' => Pages\CreateStressEvent::route('/create'),
'edit' => Pages\EditStressEvent::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
use App\Filament\Admin\Resources\StressEventResource;
use Filament\Resources\Pages\CreateRecord;
class CreateStressEvent extends CreateRecord
{
protected static string $resource = StressEventResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
use App\Filament\Admin\Resources\StressEventResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditStressEvent extends EditRecord
{
protected static string $resource = StressEventResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
use App\Filament\Admin\Resources\StressEventResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListStressEvents extends ListRecords
{
protected static string $resource = StressEventResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -0,0 +1,104 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Filament\Admin\Resources\UserResource\Pages;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class UserResource extends Resource
{
protected static ?string $model = User::class;
protected static ?string $navigationIcon = 'heroicon-o-users';
protected static ?string $navigationLabel = 'Utilisateurs';
protected static ?string $modelLabel = 'Utilisateur';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->email()
->required()
->maxLength(255),
Forms\Components\Select::make('id_role')
->relationship('role', 'libelle')
->required(),
Forms\Components\TextInput::make('password')
->password()
->dehydrated(fn ($state) => filled($state))
->required(fn (string $context): bool => $context === 'create'),
Forms\Components\Toggle::make('is_active')
->label('Compte actif')
->default(true)
->required(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('email')
->searchable(),
Tables\Columns\TextColumn::make('role.libelle')
->label('Rôle')
->badge()
->color(fn (string $state): string => match ($state) {
'Admin' => 'danger',
'Utilisateur' => 'info',
default => 'gray',
}),
Tables\Columns\ToggleColumn::make('is_active')
->label('Actif'),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TernaryFilter::make('is_active')
->label('Statut')
->boolean()
->trueLabel('Utilisateurs actifs')
->falseLabel('Utilisateurs désactivés')
->native(false),
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListUsers::route('/'),
'create' => Pages\CreateUser::route('/create'),
'edit' => Pages\EditUser::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource;
use Filament\Resources\Pages\CreateRecord;
class CreateUser extends CreateRecord
{
protected static string $resource = UserResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditUser extends EditRecord
{
protected static string $resource = UserResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListUsers extends ListRecords
{
protected static string $resource = UserResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}
@@ -31,6 +31,14 @@ class AuthController extends Controller
], 401); ], 401);
} }
// Vérification si le compte est actif
if (!$user->is_active) {
return response([
'status' => 'error',
'message' => 'Votre compte a été désactivé par un administrateur.'
], 403);
}
// Log::info('Email reçu: ' . $request->email); // Log::info('Email reçu: ' . $request->email);
// Log::info('Password saisi: ' . $request->password); // Log::info('Password saisi: ' . $request->password);
// Log::info('Hachage en BDD: ' . $user->password); // Log::info('Hachage en BDD: ' . $user->password);
@@ -45,7 +53,7 @@ class AuthController extends Controller
], 200); ], 200);
} }
public function register(Request $request) public function register(Request $request)
{ {
// 1. Validation des données (Standard Qualité) // 1. Validation des données (Standard Qualité)
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
@@ -54,7 +62,7 @@ class AuthController extends Controller
'password' => 'required|string|min:8|confirmed', 'password' => 'required|string|min:8|confirmed',
]); ]);
Log::info('Tentative de création', $request->all()); Log::info('Tentative de création', $request->all());
if ($validator->fails()) { if ($validator->fails()) {
@@ -90,8 +98,8 @@ class AuthController extends Controller
$request->validate([ $request->validate([
'current_password' => 'required', 'current_password' => 'required',
'email' => [ 'email' => [
'sometimes', 'sometimes',
'email', 'email',
Rule::unique('users')->ignore($user->id) // Empêche les doublons en BDD Rule::unique('users')->ignore($user->id) // Empêche les doublons en BDD
], ],
]); ]);
@@ -99,7 +107,7 @@ class AuthController extends Controller
// 2. Vérification de sécurité (RGPD/Confidentialité) // 2. Vérification de sécurité (RGPD/Confidentialité)
if (!Hash::check($request->current_password, $user->password)) { if (!Hash::check($request->current_password, $user->password)) {
return response()->json([ return response()->json([
'status' => 'error', 'status' => 'error',
'message' => 'Le mot de passe actuel est incorrect' 'message' => 'Le mot de passe actuel est incorrect'
], 403); ], 403);
} }
@@ -114,4 +122,4 @@ class AuthController extends Controller
'user' => $user 'user' => $user
]); ]);
} }
} }
@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Information;
use Illuminate\Http\Request;
class InformationController extends Controller
{
public function index()
{
$informations = Information::where('is_published', true)
->orderBy('created_at', 'desc')
->get();
return response()->json([
'status' => 'success',
'data' => $informations
]);
}
public function show($id)
{
$information = Information::where('is_published', true)->find($id);
if (!$information) {
return response()->json([
'status' => 'error',
'message' => 'Information non trouvée'
], 404);
}
return response()->json([
'status' => 'success',
'data' => $information
]);
}
}
@@ -30,20 +30,16 @@ class DiagnosticController extends Controller
return view('diagnostics.history', compact('history')); return view('diagnostics.history', compact('history'));
} }
public function store(Request $request) public function store(Request $request, \App\Services\StressCalculator $calculator)
{ {
// Logique pour calculer le score Holmes-Rahe // Logique pour calculer le score Holmes-Rahe
$totalPoints = 0; $totalPoints = 0;
if ($request->has('events')) { if ($request->has('events')) {
$totalPoints = StressEvent::whereIn('id', $request->events)->sum('points'); $points = StressEvent::whereIn('id', $request->events)->pluck('points')->toArray();
$totalPoints = $calculator->calculateScore($points);
} }
$niveauStress = 'Faible'; $niveauStress = $calculator->determineLevel($totalPoints);
if ($totalPoints >= 300) {
$niveauStress = 'Élevé';
} elseif ($totalPoints >= 150) {
$niveauStress = 'Modéré';
}
// Enregistrement si l'utilisateur est connecté // Enregistrement si l'utilisateur est connecté
if (Auth::check()) { if (Auth::check()) {
@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Models\Information;
use Illuminate\Http\Request;
class InformationController extends Controller
{
public function index()
{
$informations = Information::where('is_published', true)
->orderBy('created_at', 'desc')
->paginate(6);
return view('informations.index', compact('informations'));
}
public function show($id)
{
$information = Information::where('is_published', true)->findOrFail($id);
return view('informations.show', compact('information'));
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Information extends Model
{
use HasFactory;
protected $table = 'information';
protected $fillable = [
'title',
'content',
'category',
'image_url',
'is_published',
];
protected $casts = [
'is_published' => 'boolean',
];
}
+7 -1
View File
@@ -2,9 +2,15 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class StressEvent extends Model class StressEvent extends Model
{ {
// use HasFactory;
protected $fillable = [
'event_name',
'points',
];
} }
+22 -23
View File
@@ -2,61 +2,60 @@
namespace App\Models; namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; use Filament\Models\Contracts\FilamentUser;
use Database\Factories\UserFactory; use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable class User extends Authenticatable implements FilamentUser
{ {
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
use HasApiTokens, HasFactory, Notifiable; use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [ protected $fillable = [
'name', 'name',
'email', 'email',
'password', 'password',
'id_role', 'id_role',
'is_active',
]; ];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [ protected $hidden = [
'password', 'password',
'remember_token', 'remember_token',
]; ];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array protected function casts(): array
{ {
return [ return [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'password' => 'hashed', 'password' => 'hashed',
'is_active' => 'boolean',
]; ];
} }
public function emotionRecords() public function role(): BelongsTo
{ {
return $this->hasMany(EmotionRecord::class); return $this->belongsTo(Role::class, 'id_role');
} }
public function favoritedBy() public function emotionRecords(): HasMany
{
return $this->hasMany(EmotionRecord::class, 'user_id');
}
public function favoritedActivities(): BelongsToMany
{ {
return $this->belongsToMany(RelaxationActivity::class, 'favorites', 'id_user', 'id_activite'); return $this->belongsToMany(RelaxationActivity::class, 'favorites', 'id_user', 'id_activite');
} }
public function canAccessPanel(Panel $panel): bool
{
// Autorise l'accès si l'utilisateur est actif et a le rôle 'Admin'
return $this->is_active && $this->role && $this->role->libelle === 'Admin';
}
} }
+3 -1
View File
@@ -19,6 +19,8 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
// if (config('app.env') === 'production' || config('app.env') === 'local') {
\Illuminate\Support\Facades\URL::forceScheme('https');
}
} }
} }
@@ -0,0 +1,58 @@
<?php
namespace App\Providers\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Widgets;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
->path('admin')
->brandName('CESIZen')
->homeUrl('/')
->colors([
'primary' => Color::Amber,
])
->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources')
->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages')
->pages([
Pages\Dashboard::class,
])
->discoverWidgets(in: app_path('Filament/Admin/Widgets'), for: 'App\\Filament\\Admin\\Widgets')
->widgets([
Widgets\AccountWidget::class,
Widgets\FilamentInfoWidget::class,
])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
Authenticate::class,
]);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Services;
class StressCalculator
{
public const LEVEL_HIGH = 'Élevé';
public const LEVEL_MODERATE = 'Modéré';
public const LEVEL_LOW = 'Faible';
public function calculateScore(array $points): int
{
return array_sum($points);
}
public function determineLevel(int $score): string
{
if ($score >= 300) {
return self::LEVEL_HIGH;
}
if ($score >= 150) {
return self::LEVEL_MODERATE;
}
return self::LEVEL_LOW;
}
}
+2 -3
View File
@@ -1,7 +1,6 @@
<?php <?php
use App\Providers\AppServiceProvider;
return [ return [
AppServiceProvider::class, App\Providers\AppServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
]; ];
+4 -1
View File
@@ -8,6 +8,7 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"blade-ui-kit/blade-heroicons": "^2.7", "blade-ui-kit/blade-heroicons": "^2.7",
"filament/filament": "^3.2",
"guzzlehttp/guzzle": "^7.10", "guzzlehttp/guzzle": "^7.10",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
@@ -53,7 +54,8 @@
], ],
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi" "@php artisan package:discover --ansi",
"@php artisan filament:upgrade"
], ],
"post-update-cmd": [ "post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force" "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
@@ -79,6 +81,7 @@
"optimize-autoloader": true, "optimize-autoloader": true,
"preferred-install": "dist", "preferred-install": "dist",
"sort-packages": true, "sort-packages": true,
"platform-check": false,
"allow-plugins": { "allow-plugins": {
"pestphp/pest-plugin": true, "pestphp/pest-plugin": true,
"php-http/discovery": true "php-http/discovery": true
+1692 -158
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -30,6 +30,7 @@ class UserFactory extends Factory
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'), 'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10), 'remember_token' => Str::random(10),
'id_role' => \App\Models\Role::first() ?? \App\Models\Role::create(['libelle' => 'Utilisateur']),
]; ];
} }
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->bigInteger('expiration')->index();
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->bigInteger('expiration')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sessions');
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_active')->default(true)->after('id_role');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_active');
});
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('information', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('category')->default('Général'); // ex: Prévention, Stress, Sommeil
$table->string('image_url')->nullable();
$table->boolean('is_published')->default(true);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('information');
}
};
+8 -6
View File
@@ -26,11 +26,13 @@ class DatabaseSeeder extends Seeder
$adminRole = Role::where('libelle', 'Admin')->first(); $adminRole = Role::where('libelle', 'Admin')->first();
User::create([ User::firstOrCreate(
'name' => 'AdminCESI', ['email' => 'admin@cesizen.fr'],
'email' => 'admin@cesizen.fr', [
'password' => Hash::make('okokokok'), // Toujours hacher ! 'name' => 'AdminCESI',
'id_role' => $adminRole->id, 'password' => Hash::make('okokokok'),
]); 'id_role' => $adminRole->id,
]
);
} }
} }
+36
View File
@@ -0,0 +1,36 @@
services:
# PHP Application
app:
build:
context: .
dockerfile: Dockerfile
container_name: cesizen-app
restart: unless-stopped
tty: true
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./:/var/www
- /var/www/node_modules
- ./storage:/var/www/storage
networks:
- cesizen-network
# Nginx Service
webserver:
image: nginx:alpine
container_name: cesizen-webserver
restart: unless-stopped
tty: true
ports:
- "8090:80" # WEB
- "8091:81" # API
volumes:
- ./:/var/www
- ./nginx.conf:/etc/nginx/conf.d/default.conf
networks:
- cesizen-network
networks:
cesizen-network:
driver: bridge
+51
View File
@@ -0,0 +1,51 @@
# Configuration pour le Web (Port 80 interne, mappé sur 8090)
server {
listen 80;
server_name _; # Accepte tout car NPM a déjà filtré le domaine
index index.php index.html;
root /var/www/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Optimisation pour le proxy
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
}
}
# Configuration pour l'API (Port 81 interne, mappé sur 8091)
server {
listen 81;
server_name _; # Accepte tout car NPM a déjà filtré le domaine
index index.php index.html;
root /var/www/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Optimisation pour le proxy
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "WEB", "name": "www",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};
@@ -0,0 +1 @@
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
function d(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){this.isLoading=!0;let t=await this.$wire.getGroupedSelectableTableRecordKeys(e);this.areRecordsSelected(this.getRecordsInGroupOnPage(e))?this.deselectRecords(t):this.selectRecords(t),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let l=s.indexOf(this.lastChecked),r=s.indexOf(t),o=[l,r].sort((c,n)=>c-n),i=[];for(let c=o[0];c<=o[1];c++)s[c].checked=t.checked,i.push(s[c].value);t.checked?this.selectRecords(i):this.deselectRecords(i)}this.lastChecked=t}}}export{d as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold text-indigo-900">Informations & Santé Mentale</h1>
<p class="text-gray-600">Découvrez nos conseils et articles pour mieux gérer votre quotidien.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
@foreach($informations as $info)
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
@if($info->image_url)
<img src="{{ asset('storage/' . $info->image_url) }}" alt="{{ $info->title }}" class="w-full h-48 object-cover">
@else
<div class="w-full h-48 bg-indigo-50 flex items-center justify-center">
<svg class="w-12 h-12 text-indigo-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
@endif
<div class="p-6">
<span class="inline-block px-3 py-1 bg-indigo-100 text-indigo-700 text-xs font-semibold rounded-full mb-3">
{{ $info->category }}
</span>
<h2 class="text-xl font-bold text-gray-900 mb-2">{{ $info->title }}</h2>
<p class="text-gray-600 text-sm mb-4 line-clamp-3">
{!! strip_tags($info->content) !!}
</p>
<a href="{{ route('informations.show', $info->id) }}" class="text-indigo-600 font-semibold hover:text-indigo-800 flex items-center">
Lire la suite
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</a>
</div>
</div>
@endforeach
</div>
<div class="mt-12">
{{ $informations->links() }}
</div>
</div>
@endsection
@@ -0,0 +1,56 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<a href="{{ route('informations.index') }}" class="inline-flex items-center text-indigo-600 hover:text-indigo-800 mb-6">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
</svg>
Retour aux articles
</a>
<div class="bg-white rounded-3xl shadow-sm border border-gray-100 overflow-hidden">
@if($information->image_url)
<img src="{{ asset('storage/' . $information->image_url) }}" alt="{{ $information->title }}" class="w-full h-96 object-cover">
@endif
<div class="p-8 md:p-12">
<span class="inline-block px-4 py-1 bg-indigo-100 text-indigo-700 text-sm font-semibold rounded-full mb-6">
{{ $information->category }}
</span>
<h1 class="text-4xl font-bold text-gray-900 mb-6 leading-tight">
{{ $information->title }}
</h1>
<div class="flex items-center text-gray-500 mb-8 text-sm">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
Publié le {{ $information->created_at->format('d/m/Y') }}
</div>
<div class="prose prose-indigo max-w-none text-gray-700 leading-relaxed text-lg">
{!! $information->content !!}
</div>
<div class="mt-12 p-6 bg-gray-50 rounded-2xl border border-gray-100">
<div class="flex items-start">
<div class="flex-shrink-0">
<svg class="h-6 w-6 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm text-gray-600 italic">
Information de santé publique - CESIZen. Ces contenus sont rédigés à but informatif et ne sauraient remplacer une consultation professionnelle.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
+40 -13
View File
@@ -14,20 +14,47 @@
<nav class="flex items-center gap-6" role="navigation" aria-label="Menu utilisateur"> <nav class="flex items-center gap-6" role="navigation" aria-label="Menu utilisateur">
@auth @auth
<a href="{{ route('profile.index') }}" class="flex items-center gap-3 px-4 py-1.5 bg-slate-50 rounded-lg border border-slate-200 shadow-sm hover:border-[#000091] transition-all group"> <div class="relative" x-data="{ open: false }">
<div class="text-right"> <button @click="open = !open" @click.away="open = false" class="flex items-center gap-3 px-4 py-1.5 bg-slate-50 rounded-lg border border-slate-200 shadow-sm hover:border-[#000091] transition-all group">
<p class="text-sm font-bold leading-none text-slate-900 group-hover:text-[#000091]">{{ Auth::user()->name }}</p> <div class="text-right">
<p class="text-[10px] text-slate-500 font-bold uppercase tracking-tighter">Mon Profil</p> <p class="text-sm font-bold leading-none text-slate-900 group-hover:text-[#000091]">{{ Auth::user()->name }}</p>
</div> <p class="text-[10px] text-slate-500 font-bold uppercase tracking-tighter">Mon Profil</p>
<img src="https://ui-avatars.com/api/?name={{ urlencode(Auth::user()->name) }}&background=000091&color=fff" class="w-9 h-9 rounded-lg border border-slate-200" alt="Avatar de {{ Auth::user()->name }}"> </div>
</a> <img src="https://ui-avatars.com/api/?name={{ urlencode(Auth::user()->name) }}&background=000091&color=fff" class="w-9 h-9 rounded-lg border border-slate-200" alt="Avatar de {{ Auth::user()->name }}">
<form action="{{ route('logout') }}" method="POST" class="inline"> <x-heroicon-o-chevron-down class="h-4 w-4 text-slate-400 group-hover:text-[#000091] transition-transform" ::class="open ? 'rotate-180' : ''" />
@csrf
<button type="submit" class="text-xs font-bold text-slate-500 hover:text-[#E1000F] uppercase tracking-tighter flex items-center gap-1">
<x-heroicon-o-arrow-left-on-rectangle class="h-4 w-4" />
Quitter
</button> </button>
</form>
<!-- Dropdown Menu -->
<div x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
class="absolute right-0 mt-2 w-56 bg-white border border-slate-200 rounded-lg shadow-xl z-50 py-2 overflow-hidden">
<a href="{{ route('profile.index') }}" class="flex items-center gap-3 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition-colors">
<x-heroicon-o-user class="h-4 w-4 text-slate-400" />
Mon compte
</a>
@if(Auth::user()->role && Auth::user()->role->libelle === 'Admin')
<div class="border-t border-slate-100 my-1"></div>
<a href="/admin" class="flex items-center gap-3 px-4 py-2.5 text-sm font-bold text-[--fr-blue] hover:bg-blue-50 transition-colors">
<x-heroicon-o-shield-check class="h-4 w-4" />
Administration
</a>
@endif
<div class="border-t border-slate-100 my-1"></div>
<form action="{{ route('logout') }}" method="POST" class="block w-full">
@csrf
<button type="submit" class="flex items-center gap-3 w-full px-4 py-2.5 text-sm font-medium text-red-600 hover:bg-red-50 transition-colors">
<x-heroicon-o-arrow-left-on-rectangle class="h-4 w-4" />
Déconnexion
</button>
</form>
</div>
</div>
@else @else
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a href="/login" class="text-sm font-bold text-[#000091] hover:underline px-2 py-2">Se connecter</a> <a href="/login" class="text-sm font-bold text-[#000091] hover:underline px-2 py-2">Se connecter</a>
@@ -40,6 +40,16 @@
Exercices de Respiration Exercices de Respiration
</a> </a>
<!-- Informations / Guides -->
<a href="{{ route('informations.index') }}"
class="flex items-center gap-4 px-4 py-3 rounded-sm transition-all group {{ request()->routeIs('informations.*') ? 'text-white bg-[#000091] font-bold shadow-md' : 'text-slate-700 hover:bg-slate-50 font-medium border-b border-slate-100/50' }}"
{!! request()->routeIs('informations.*') ? 'aria-current="page"' : '' !!}>
<div class="w-8 h-8 rounded-sm flex items-center justify-center transition-colors {{ request()->routeIs('informations.*') ? 'bg-white/20 text-white' : 'bg-blue-50 text-[--fr-blue]' }}" aria-hidden="true">
<x-heroicon-o-document-text class="h-4 w-4" />
</div>
Guides & Informations
</a>
<div class="pt-10 space-y-1"> <div class="pt-10 space-y-1">
<p class="text-[10px] font-bold text-slate-500 uppercase tracking-[0.2em] mb-4 px-4">Services de l'État</p> <p class="text-[10px] font-bold text-slate-500 uppercase tracking-[0.2em] mb-4 px-4">Services de l'État</p>
<a href="#" class="flex items-center justify-between px-4 py-2 text-sm text-slate-600 hover:text-[--fr-blue] font-medium transition-colors"> <a href="#" class="flex items-center justify-between px-4 py-2 text-sm text-slate-600 hover:text-[--fr-blue] font-medium transition-colors">
+2 -2
View File
@@ -77,7 +77,7 @@
</div> </div>
<h4 class="text-xl font-bold text-slate-900 mb-2">Guides Pratiques</h4> <h4 class="text-xl font-bold text-slate-900 mb-2">Guides Pratiques</h4>
<p class="text-slate-600 text-sm leading-relaxed mb-6">Consultez les fiches de prévention sur le sommeil et la nutrition.</p> <p class="text-slate-600 text-sm leading-relaxed mb-6">Consultez les fiches de prévention sur le sommeil et la nutrition.</p>
<a href="#" class="inline-flex items-center gap-2 text-sm font-bold text-[--fr-blue] hover:underline underline-offset-4 decoration-2"> <a href="{{ route('informations.index') }}" class="inline-flex items-center gap-2 text-sm font-bold text-[--fr-blue] hover:underline underline-offset-4 decoration-2">
Lire les guides Lire les guides
<x-heroicon-o-arrow-right class="h-4 w-4" aria-hidden="true" /> <x-heroicon-o-arrow-right class="h-4 w-4" aria-hidden="true" />
</a> </a>
@@ -105,7 +105,7 @@
</div> </div>
<!-- Global Footer Gov --> <!-- Global Footer Gov -->
</main> </main>
</div> </div>
</body> </body>
+4
View File
@@ -25,4 +25,8 @@ Route::middleware('auth:sanctum')->group(function () {
Route::get('/emotions', [App\Http\Controllers\Api\EmotionController::class, 'index']); Route::get('/emotions', [App\Http\Controllers\Api\EmotionController::class, 'index']);
Route::post('/emotions', [App\Http\Controllers\Api\EmotionController::class, 'store']); Route::post('/emotions', [App\Http\Controllers\Api\EmotionController::class, 'store']);
// Routes pour les informations (accessibles aux utilisateurs connectés)
Route::get('/informations', [App\Http\Controllers\Api\InformationController::class, 'index']);
Route::get('/informations/{id}', [App\Http\Controllers\Api\InformationController::class, 'show']);
}); });
+4
View File
@@ -9,10 +9,14 @@ use App\Http\Controllers\Web\RelaxationController;
use App\Http\Controllers\Web\AuthController; use App\Http\Controllers\Web\AuthController;
use App\Http\Controllers\Web\ProfileController; use App\Http\Controllers\Web\ProfileController;
use App\Http\Controllers\Web\InformationController;
// Public Routes // Public Routes
Route::get('/', [DashboardController::class, 'index'])->name('home'); Route::get('/', [DashboardController::class, 'index'])->name('home');
Route::get('/diagnostics', [DiagnosticController::class, 'index'])->name('diagnostics.index'); Route::get('/diagnostics', [DiagnosticController::class, 'index'])->name('diagnostics.index');
Route::get('/relaxation', [RelaxationController::class, 'index'])->name('relaxation.index'); Route::get('/relaxation', [RelaxationController::class, 'index'])->name('relaxation.index');
Route::get('/informations', [InformationController::class, 'index'])->name('informations.index');
Route::get('/informations/{id}', [InformationController::class, 'show'])->name('informations.show');
// Authentification // Authentification
Route::get('/login', [AuthController::class, 'showLogin'])->name('login'); Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Models\Role;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AdminAccessTest extends TestCase
{
use RefreshDatabase;
public function test_guest_is_redirected_to_login(): void
{
$response = $this->get('/admin');
$response->assertRedirect('/login');
}
public function test_non_admin_user_cannot_access_admin_panel(): void
{
$role = Role::create(['libelle' => 'Utilisateur']);
$user = User::factory()->create(['id_role' => $role->id]);
$response = $this->actingAs($user)->get('/admin');
$response->assertForbidden();
}
public function test_admin_user_can_access_admin_panel(): void
{
$role = Role::create(['libelle' => 'Admin']);
$user = User::factory()->create(['id_role' => $role->id]);
$response = $this->actingAs($user)->get('/admin');
$response->assertSuccessful();
}
}
@@ -1,19 +1,21 @@
<?php <?php
namespace Tests\Unit; namespace Tests\Feature;
use Tests\TestCase; use Tests\TestCase;
use App\Models\User; use App\Models\User;
use App\Models\StressEvent; use App\Models\StressEvent;
use App\Models\ResultatDiag; use App\Models\ResultatDiag;
use Illuminate\Foundation.Testing.RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
class DiagnosticTest extends TestCase class DiagnosticTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
public function test_diagnostic_score_calculation_and_storage() public function test_diagnostic_score_calculation_and_storage(): void
{ {
$this->withoutMiddleware();
// Créer un utilisateur // Créer un utilisateur
$user = User::factory()->create(); $user = User::factory()->create();
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace Tests\Unit;
use App\Services\StressCalculator;
use PHPUnit\Framework\TestCase;
class StressCalculatorTest extends TestCase
{
private StressCalculator $calculator;
protected function setUp(): void
{
parent::setUp();
$this->calculator = new StressCalculator();
}
public function test_calculate_score_sums_points_correctly(): void
{
$points = [100, 73, 50];
$this->assertEquals(223, $this->calculator->calculateScore($points));
}
public function test_calculate_score_returns_zero_for_empty_array(): void
{
$this->assertEquals(0, $this->calculator->calculateScore([]));
}
/**
* @dataProvider stressLevelProvider
*/
public function test_determine_level_returns_correct_label(int $score, string $expectedLevel): void
{
$this->assertEquals($expectedLevel, $this->calculator->determineLevel($score));
}
public static function stressLevelProvider(): array
{
return [
[350, StressCalculator::LEVEL_HIGH],
[300, StressCalculator::LEVEL_HIGH],
[299, StressCalculator::LEVEL_MODERATE],
[150, StressCalculator::LEVEL_MODERATE],
[149, StressCalculator::LEVEL_LOW],
[0, StressCalculator::LEVEL_LOW],
];
}
}
+3 -3
View File
@@ -1,3 +1,3 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="AndroidProjectSystem"> <component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" /> <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component> </component>
</project> </project>
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" /> <bytecodeTargetLevel target="21" />
</component> </component>
</project> </project>
+17 -17
View File
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="deploymentTargetSelector"> <component name="deploymentTargetSelector">
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-04-21T21:27:34.624668600Z"> <DropdownSelection timestamp="2026-04-21T21:27:34.624668600Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=d76a0274" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=d76a0274" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>
+17 -17
View File
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" /> <component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>
</project> </project>
+50
View File
@@ -0,0 +1,50 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
</profile>
</component>
+16 -16
View File
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="RunConfigurationProducerService"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" /> <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" /> <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" /> <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" /> <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" /> <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" /> <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" /> <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" /> <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set> </set>
</option> </option>
</component> </component>
</project> </project>
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component> </component>
</project> </project>
+49 -49
View File
@@ -1,50 +1,50 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-feature android:name="android.hardware.type.watch" /> <uses-feature android:name="android.hardware.type.watch" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault"> android:theme="@android:style/Theme.DeviceDefault">
<uses-library <uses-library
android:name="com.google.android.wearable" android:name="com.google.android.wearable"
android:required="true" /> android:required="true" />
<uses-library <uses-library
android:name="wear-sdk" android:name="wear-sdk"
android:required="false" /> android:required="false" />
<!-- <!--
Set to true if your app is Standalone, that is, it does not require the handheld Set to true if your app is Standalone, that is, it does not require the handheld
app to run. app to run.
--> -->
<meta-data <meta-data
android:name="com.google.android.gms.wearable.standalone" android:name="com.google.android.gms.wearable.standalone"
android:value="true" /> android:value="true" />
<meta-data <meta-data
android:name="com.google.android.gms.wearable.capabilities" android:name="com.google.android.gms.wearable.capabilities"
android:resource="@xml/wearable_app_values" /> android:resource="@xml/wearable_app_values" />
<activity <activity
android:name="com.cesizen.app.MainActivity" android:name="com.cesizen.app.MainActivity"
android:exported="true" android:exported="true"
android:taskAffinity="" android:taskAffinity=""
android:theme="@style/MainActivityTheme.Starting"> android:theme="@style/MainActivityTheme.Starting">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>
@@ -1,282 +1,282 @@
package com.cesizen.app package com.cesizen.app
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.* import androidx.wear.compose.material3.*
import com.cesizen.app.presentation.theme.MyApplicationTheme import com.cesizen.app.presentation.theme.MyApplicationTheme
import com.google.android.gms.tasks.Tasks import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.* import com.google.android.gms.wearable.*
import org.json.JSONObject import org.json.JSONObject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener { class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener {
private var userName by mutableStateOf<String?>(null) private var userName by mutableStateOf<String?>(null)
private var currentMood by mutableStateOf<String?>(null) private var currentMood by mutableStateOf<String?>(null)
private var isRefreshing by mutableStateOf(false) private var isRefreshing by mutableStateOf(false)
private val TAG = "CESIZen_Watch" private val TAG = "CESIZen_Watch"
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Wearable.getDataClient(this).addListener(this) Wearable.getDataClient(this).addListener(this)
refreshData() refreshData()
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val request = NetworkRequest.Builder() val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // Priorité Wi-Fi .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // Priorité Wi-Fi
.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH) // Secours Bluetooth .addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH) // Secours Bluetooth
.build() .build()
connectivityManager.requestNetwork(request, object : ConnectivityManager.NetworkCallback() { connectivityManager.requestNetwork(request, object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
// Le système a trouvé soit du Wi-Fi, soit le pont Bluetooth du téléphone // Le système a trouvé soit du Wi-Fi, soit le pont Bluetooth du téléphone
connectivityManager.bindProcessToNetwork(network) connectivityManager.bindProcessToNetwork(network)
} }
}) })
setContent { setContent {
WearApp( WearApp(
userName = userName, userName = userName,
currentMood = currentMood, currentMood = currentMood,
isRefreshing = isRefreshing, isRefreshing = isRefreshing,
onRefresh = { refreshData() }, onRefresh = { refreshData() },
onMoodSelected = { mood -> sendMoodToPhone(mood) } onMoodSelected = { mood -> sendMoodToPhone(mood) }
) )
} }
} }
private fun refreshData() { private fun refreshData() {
if (isRefreshing) return if (isRefreshing) return
isRefreshing = true isRefreshing = true
val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date())
Wearable.getDataClient(this).dataItems.addOnSuccessListener { items -> Wearable.getDataClient(this).dataItems.addOnSuccessListener { items ->
for (item in items) { for (item in items) {
if (item.uri.path == "/auth_status") { if (item.uri.path == "/auth_status") {
val dataMap = DataMapItem.fromDataItem(item).dataMap val dataMap = DataMapItem.fromDataItem(item).dataMap
userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null
} else if (item.uri.path == "/daily_mood") { } else if (item.uri.path == "/daily_mood") {
val dataMap = DataMapItem.fromDataItem(item).dataMap val dataMap = DataMapItem.fromDataItem(item).dataMap
val moodDate = dataMap.getString("date") val moodDate = dataMap.getString("date")
if (moodDate == today) { if (moodDate == today) {
currentMood = dataMap.getString("mood") currentMood = dataMap.getString("mood")
} else { } else {
currentMood = null currentMood = null
} }
} }
} }
isRefreshing = false isRefreshing = false
} }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes) val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes)
for (node in nodes) { for (node in nodes) {
val payload = JSONObject().apply { put("command", "get_sync_data") }.toString().toByteArray() val payload = JSONObject().apply { put("command", "get_sync_data") }.toString().toByteArray()
Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload) Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error sync nodes", e) Log.e(TAG, "Error sync nodes", e)
} }
} }
} }
private fun sendMoodToPhone(mood: String) { private fun sendMoodToPhone(mood: String) {
currentMood = mood currentMood = mood
if (mood.isEmpty()) { if (mood.isEmpty()) {
// Reset mood locally to show the selection again // Reset mood locally to show the selection again
currentMood = null currentMood = null
} }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes) val nodes = Tasks.await(Wearable.getNodeClient(this@MainActivity).connectedNodes)
for (node in nodes) { for (node in nodes) {
val payload = JSONObject().apply { val payload = JSONObject().apply {
put("command", "save_mood") put("command", "save_mood")
put("mood", mood) put("mood", mood)
}.toString().toByteArray() }.toString().toByteArray()
Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload) Wearable.getMessageClient(this@MainActivity).sendMessage(node.id, "/wearable_communication", payload)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error sending mood", e) Log.e(TAG, "Error sending mood", e)
} }
} }
} }
override fun onDataChanged(dataEvents: DataEventBuffer) { override fun onDataChanged(dataEvents: DataEventBuffer) {
val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) val today = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date())
for (event in dataEvents) { for (event in dataEvents) {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
if (event.dataItem.uri.path == "/auth_status") { if (event.dataItem.uri.path == "/auth_status") {
userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null userName = if (dataMap.getString("status") == "authenticated") dataMap.getString("userName") else null
} else if (event.dataItem.uri.path == "/daily_mood") { } else if (event.dataItem.uri.path == "/daily_mood") {
val moodDate = dataMap.getString("date") val moodDate = dataMap.getString("date")
if (moodDate == today) { if (moodDate == today) {
currentMood = dataMap.getString("mood") currentMood = dataMap.getString("mood")
} else { } else {
currentMood = null currentMood = null
} }
} }
} }
} }
} }
@Composable @Composable
fun WearApp(userName: String?, currentMood: String?, isRefreshing: Boolean, onRefresh: () -> Unit, onMoodSelected: (String) -> Unit) { fun WearApp(userName: String?, currentMood: String?, isRefreshing: Boolean, onRefresh: () -> Unit, onMoodSelected: (String) -> Unit) {
val cesiBlue = Color(0xFF000091) val cesiBlue = Color(0xFF000091)
MyApplicationTheme { MyApplicationTheme {
AppScaffold { AppScaffold {
val listState = rememberTransformingLazyColumnState() val listState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = listState) { contentPadding -> ScreenScaffold(scrollState = listState) { contentPadding ->
TransformingLazyColumn( TransformingLazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Brush.verticalGradient(listOf(Color.Black, Color(0xFF000022)))), .background(Brush.verticalGradient(listOf(Color.Black, Color(0xFF000022)))),
contentPadding = contentPadding, contentPadding = contentPadding,
state = listState, state = listState,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// Header // Header
item { item {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(bottom = 12.dp) modifier = Modifier.padding(bottom = 12.dp)
) { ) {
Text( Text(
"CESIZen", "CESIZen",
color = cesiBlue, color = cesiBlue,
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
} }
} }
if (userName == null) { if (userName == null) {
item { item {
TitleCard( TitleCard(
onClick = onRefresh, onClick = onRefresh,
title = { Text("Connexion requise", textAlign = TextAlign.Center) }, title = { Text("Connexion requise", textAlign = TextAlign.Center) },
subtitle = { Text("Ouvrez l'app sur votre téléphone", textAlign = TextAlign.Center, fontSize = 12.sp) }, subtitle = { Text("Ouvrez l'app sur votre téléphone", textAlign = TextAlign.Center, fontSize = 12.sp) },
modifier = Modifier.padding(horizontal = 8.dp) modifier = Modifier.padding(horizontal = 8.dp)
) )
} }
} else { } else {
// User Status // User Status
item { item {
val displayName = userName.split(" ").firstOrNull() ?: "Zen" val displayName = userName.split(" ").firstOrNull() ?: "Zen"
Text( Text(
"Bonjour $displayName", "Bonjour $displayName",
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
if (currentMood.isNullOrEmpty()) { if (currentMood.isNullOrEmpty()) {
item { item {
Text( Text(
"Comment allez-vous ?", "Comment allez-vous ?",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = Color.LightGray, color = Color.LightGray,
modifier = Modifier.padding(vertical = 8.dp) modifier = Modifier.padding(vertical = 8.dp)
) )
} }
val moods = listOf( val moods = listOf(
MoodItem("Très bien", "😊", Color(0xFF4CAF50)), MoodItem("Très bien", "😊", Color(0xFF4CAF50)),
MoodItem("Bien", "🙂", Color(0xFF8BC34A)), MoodItem("Bien", "🙂", Color(0xFF8BC34A)),
MoodItem("Neutre", "😐", Color(0xFFFFC107)), MoodItem("Neutre", "😐", Color(0xFFFFC107)),
MoodItem("Pas top", "🙁", Color(0xFFFF9800)), MoodItem("Pas top", "🙁", Color(0xFFFF9800)),
MoodItem("Stressé", "😫", Color(0xFFF44336)) MoodItem("Stressé", "😫", Color(0xFFF44336))
) )
moods.forEach { mood -> moods.forEach { mood ->
item { item {
FilledTonalButton( FilledTonalButton(
onClick = { onMoodSelected(mood.label) }, onClick = { onMoodSelected(mood.label) },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 2.dp, horizontal = 16.dp), .padding(vertical = 2.dp, horizontal = 16.dp),
colors = ButtonDefaults.filledTonalButtonColors( colors = ButtonDefaults.filledTonalButtonColors(
containerColor = mood.color.copy(alpha = 0.2f), containerColor = mood.color.copy(alpha = 0.2f),
contentColor = Color.White contentColor = Color.White
) )
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text(mood.emoji, fontSize = 20.sp) Text(mood.emoji, fontSize = 20.sp)
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Text(mood.label, style = MaterialTheme.typography.labelLarge) Text(mood.label, style = MaterialTheme.typography.labelLarge)
} }
} }
} }
} }
} else { } else {
item { item {
Card( Card(
onClick = { onMoodSelected("") }, onClick = { onMoodSelected("") },
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
colors = CardDefaults.cardColors(containerColor = cesiBlue.copy(alpha = 0.3f)) colors = CardDefaults.cardColors(containerColor = cesiBlue.copy(alpha = 0.3f))
) { ) {
Column( Column(
modifier = Modifier.fillMaxWidth().padding(8.dp), modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text("Humeur enregistrée", style = MaterialTheme.typography.labelSmall) Text("Humeur enregistrée", style = MaterialTheme.typography.labelSmall)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text(currentMood, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) Text(currentMood, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text("Tapoter pour changer", fontSize = 10.sp, color = Color.LightGray) Text("Tapoter pour changer", fontSize = 10.sp, color = Color.LightGray)
} }
} }
} }
} }
} }
// Bottom Spacer // Bottom Spacer
item { item {
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
} }
} }
} }
} }
} }
} }
data class MoodItem(val label: String, val emoji: String, val color: Color) data class MoodItem(val label: String, val emoji: String, val color: Color)
@@ -1,13 +1,13 @@
package com.cesizen.app.presentation.theme package com.cesizen.app.presentation.theme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.wear.compose.material3.MaterialTheme import androidx.wear.compose.material3.MaterialTheme
@Composable @Composable
fun MyApplicationTheme( fun MyApplicationTheme(
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
MaterialTheme( MaterialTheme(
content = content content = content
) )
} }
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<capability name="cesizen_wear_app" /> <capability name="cesizen_wear_app" />
</resources> </resources>
+9 -9
View File
@@ -1,9 +1,9 @@
#Tue Apr 21 22:42:57 CEST 2026 #Tue Apr 21 22:42:57 CEST 2026
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06 distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+94 -94
View File
@@ -1,94 +1,94 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @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 not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @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 distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0 @rem SPDX-License-Identifier: Apache-2.0
@rem @rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused @rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter. @rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 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. @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" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 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 echo location of your Java installation. 1>&2
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2 echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 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 echo location of your Java installation. 1>&2
goto fail goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH= set CLASSPATH=
@rem Execute Gradle @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" %* "%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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE% exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega

Some files were not shown because too many files have changed in this diff Show More