merge(main): première version stable et testée du produit (v1.0.0-debug)
Fusionne develop dans main — tickets #1 à #11 clos : scaffolding Flutter, modèle de données Drift, couche application, gestion des médias, tous les écrans (bibliothèque d'exercices, programme, séance-modèle, exécution de séance, historique), QA fonctionnelle finale. 21/21 tests verts, analyze propre, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6
.gitignore
vendored
@ -73,11 +73,6 @@ DerivedData/
|
|||||||
*.dSYM.zip
|
*.dSYM.zip
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
|
||||||
# Generated files
|
|
||||||
**/*.g.dart
|
|
||||||
**/*.freezed.dart
|
|
||||||
**/*.gr.dart
|
|
||||||
|
|
||||||
# Coverage
|
# Coverage
|
||||||
coverage/
|
coverage/
|
||||||
|
|
||||||
@ -93,3 +88,4 @@ coverage/
|
|||||||
.ideai/background-tasks/
|
.ideai/background-tasks/
|
||||||
.ideai/live-state.json
|
.ideai/live-state.json
|
||||||
.ideai/layouts.json
|
.ideai/layouts.json
|
||||||
|
.ideai/permissions.json
|
||||||
|
|||||||
@ -3,3 +3,4 @@
|
|||||||
- [gametime-product-scope](gametime-product-scope.md) — memory note gametime-product-scope
|
- [gametime-product-scope](gametime-product-scope.md) — memory note gametime-product-scope
|
||||||
- [gametime-ux-conception](gametime-ux-conception.md) — memory note gametime-ux-conception
|
- [gametime-ux-conception](gametime-ux-conception.md) — memory note gametime-ux-conception
|
||||||
- [gametime-architecture-initial-stack-data-model](gametime-architecture-initial-stack-data-model.md) — memory note gametime-architecture-initial-stack-data-model
|
- [gametime-architecture-initial-stack-data-model](gametime-architecture-initial-stack-data-model.md) — memory note gametime-architecture-initial-stack-data-model
|
||||||
|
- [gametime-dev-environment](gametime-dev-environment.md) — memory note gametime-dev-environment
|
||||||
|
|||||||
46
.ideai/memory/gametime-dev-environment.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
name: gametime-dev-environment
|
||||||
|
description: memory note gametime-dev-environment
|
||||||
|
metadata:
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
# GameTime — Environnement de build local
|
||||||
|
|
||||||
|
Flutter SDK et Android SDK sont installés et opérationnels sur la machine du projet (2026-07-17). Premier APK debug buildé avec succès le 2026-07-17.
|
||||||
|
|
||||||
|
## Flutter
|
||||||
|
- Installé via le paquet AUR `flutter-bin` (3.44.6, stable), pas `flutter` (source AUR) — ce dernier a un conflit de dépendance avec `dart` déjà présent sur le système (`dart<3.12.0` requis alors que 3.12.2 est installé).
|
||||||
|
- Binaire `flutter` disponible dans `/usr/bin/flutter` (wrapper `flutter-bin`), SDK réel monté via unionfs sous `~/.cache/flutter_sdk`.
|
||||||
|
|
||||||
|
## Android SDK
|
||||||
|
- Installé via le paquet AUR `android-sdk-cmdline-tools-latest`, posé dans `/opt/android-sdk` (appartenait à root par défaut — il a fallu `chown -R anthony:anthony /opt/android-sdk` pour que `sdkmanager` puisse installer des composants sans sudo).
|
||||||
|
- Composants installés : `platform-tools`, `platforms;android-34/35/36`, `build-tools;34.0.0/28.0.3/36.0.0`, NDK 28.2.13676358, CMake 3.22.1 (certains installés automatiquement par Gradle au premier build).
|
||||||
|
- Licences acceptées via `sdkmanager --licenses`.
|
||||||
|
- `flutter config --android-sdk /opt/android-sdk` exécuté pour lier Flutter au SDK.
|
||||||
|
|
||||||
|
## JDK — point de friction important
|
||||||
|
- Le JDK système par défaut est `java-26-openjdk` (trop récent : `Unsupported class file major version 70` avec Gradle 9.1 utilisé par le template Flutter).
|
||||||
|
- Installé `jdk21-openjdk` (dépôt officiel Arch, pas besoin d'AUR) en complément, sans le mettre par défaut système.
|
||||||
|
- Flutter configuré spécifiquement pour l'utiliser : `flutter config --jdk-dir=/usr/lib/jvm/java-21-openjdk`. Cette config est stockée dans la config Flutter (probablement `~/.config/flutter/settings` ou équivalent), donc persistante indépendamment du JDK système par défaut.
|
||||||
|
|
||||||
|
## Variables d'environnement persistées
|
||||||
|
Ajoutées dans `~/.zshrc`, `~/.bashrc` et `~/.config/fish/config.fish` (le shell de login est zsh, mais les agents peuvent tourner en bash) :
|
||||||
|
```
|
||||||
|
ANDROID_HOME=/opt/android-sdk
|
||||||
|
ANDROID_SDK_ROOT=/opt/android-sdk
|
||||||
|
PATH inclut $ANDROID_HOME/cmdline-tools/latest/bin et $ANDROID_HOME/platform-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limite connue
|
||||||
|
- Pas de Chrome installé (web toolchain Flutter indisponible), sans impact puisque la cible du projet est mobile (Android/iOS), pas web.
|
||||||
|
- Pas de device/émulateur Android connecté — seul `flutter build apk --debug` (sans device) est utilisable pour produire l'APK à transférer manuellement sur le téléphone de l'utilisateur (pas de `flutter run` direct sur device depuis cet environnement).
|
||||||
|
- **Les sandbox des agents (DevBackend, DevFrontend, etc.) n'ont pas d'accès réseau à pub.dev**, contrairement à l'environnement de Main (Bash direct). Conséquence pratique : `flutter pub get`, `flutter analyze` (si dépend de packages non encore en cache) et `flutter build apk` doivent être exécutés par Main en vérification finale après le travail de code d'un agent, pas par l'agent lui-même. Les agents peuvent en revanche modifier pubspec.yaml, écrire du code Dart, etc.
|
||||||
|
|
||||||
|
## Commande de build APK de référence
|
||||||
|
```bash
|
||||||
|
export ANDROID_HOME=/opt/android-sdk
|
||||||
|
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"
|
||||||
|
cd /home/anthony/Documents/Projects/GameTime
|
||||||
|
flutter pub get && flutter analyze && flutter build apk --debug
|
||||||
|
```
|
||||||
|
APK produit : `build/app/outputs/flutter-apk/app-debug.apk`.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#1"
|
issueRef: "#1"
|
||||||
version: 3
|
version: 4
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301472470
|
updatedAt: 1784301585782
|
||||||
---
|
---
|
||||||
Décisions à prendre par Git : nom de la branche principale de dev (ex: develop) si on ne travaille pas directement sur main, convention de nommage des branches de feature par ticket (ex: feature/#2-scaffolding-flutter). Aucune action sortante (push distant, remote) sans validation explicite de l'utilisateur — repo local pour l'instant.
|
Décisions à prendre par Git : nom de la branche principale de dev (ex: develop) si on ne travaille pas directement sur main, convention de nommage des branches de feature par ticket (ex: feature/#2-scaffolding-flutter). Aucune action sortante (push distant, remote) sans validation explicite de l'utilisateur — repo local pour l'instant.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "e6f0a540-055c-4b9d-9d14-3ceabf35c7d6"
|
id: "e6f0a540-055c-4b9d-9d14-3ceabf35c7d6"
|
||||||
number: 1
|
number: 1
|
||||||
title: "[Git] Initialiser le dépôt et la stratégie de branches GameTime"
|
title: "[Git] Initialiser le dépôt et la stratégie de branches GameTime"
|
||||||
status: "inProgress"
|
status: "closed"
|
||||||
priority: "high"
|
priority: "high"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: []
|
links: []
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"8f065f64-ef6e-4a00-af9c-d00be079e3cc","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301284213
|
createdAt: 1784301284213
|
||||||
updatedAt: 1784301472470
|
updatedAt: 1784301585782
|
||||||
version: 3
|
version: 4
|
||||||
---
|
---
|
||||||
Mettre en place la structure de dépôt pour le projet Flutter GameTime : stratégie de branches (main protégée + branches de feature par ticket), conventions de commit, .gitignore adapté Flutter/Dart.
|
Mettre en place la structure de dépôt pour le projet Flutter GameTime : stratégie de branches (main protégée + branches de feature par ticket), conventions de commit, .gitignore adapté Flutter/Dart.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#10"
|
issueRef: "#10"
|
||||||
version: 4
|
version: 6
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301394213
|
updatedAt: 1784308662161
|
||||||
---
|
---
|
||||||
Référence : mémoire "gametime-ux-conception" section 5. Relance : utiliser la séance-modèle source si elle existe encore ; sinon relancer depuis le snapshot historique avec un message explicite ("La séance originale n'existe plus. Une copie va être utilisée."). L'historique doit rester lisible même si exercices/programmes/séances-modèles sources ont été supprimés depuis (snapshot autonome, cf. ticket #3/#4).
|
Référence : mémoire "gametime-ux-conception" section 5. Relance : utiliser la séance-modèle source si elle existe encore ; sinon relancer depuis le snapshot historique avec un message explicite ("La séance originale n'existe plus. Une copie va être utilisée."). L'historique doit rester lisible même si exercices/programmes/séances-modèles sources ont été supprimés depuis (snapshot autonome, cf. ticket #3/#4).
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "4c2a9393-bf0f-4f41-a243-9ce0b4f0c4e1"
|
id: "4c2a9393-bf0f-4f41-a243-9ce0b4f0c4e1"
|
||||||
number: 10
|
number: 10
|
||||||
title: "[DevFrontend] Écran Historique des séances"
|
title: "[DevFrontend] Écran Historique des séances"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "medium"
|
priority: "medium"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#4","kind":"dependsOn"},{"target":"#9","kind":"dependsOn"}]
|
links: [{"target":"#4","kind":"dependsOn"},{"target":"#9","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301309677
|
createdAt: 1784301309677
|
||||||
updatedAt: 1784301394213
|
updatedAt: 1784308662161
|
||||||
version: 4
|
version: 6
|
||||||
---
|
---
|
||||||
Liste groupée par période (Aujourd'hui/Cette semaine/Plus ancien) avec résumé par séance. Détail par programme puis exercice puis série (temps/répétitions/score si suivis). Relance : utilise la séance-modèle associée si elle existe encore, sinon relance depuis le snapshot historique avec message explicite. Suppression d'historique (action destructive confirmée). Cf. mémoire "gametime-ux-conception" section 5.
|
Liste groupée par période (Aujourd'hui/Cette semaine/Plus ancien) avec résumé par séance. Détail par programme puis exercice puis série (temps/répétitions/score si suivis). Relance : utilise la séance-modèle associée si elle existe encore, sinon relance depuis le snapshot historique avec message explicite. Suppression d'historique (action destructive confirmée). Cf. mémoire "gametime-ux-conception" section 5.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#11"
|
issueRef: "#11"
|
||||||
version: 7
|
version: 9
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301397898
|
updatedAt: 1784309299325
|
||||||
---
|
---
|
||||||
Points à couvrir en priorité : (1) impossibilité de modifier les mesures activées ou l'ordre des exercices depuis une séance-modèle, seuls setsCount et cibles numériques doivent être modifiables ; (2) reprise de séance après kill complet de l'app (pas juste mise en arrière-plan) ; (3) recalcul correct des timers de repos ajustés (+/-15s) après reprise ; (4) intégrité de l'historique quand l'exercice, le programme ou la séance-modèle source ont été supprimés/archivés entre-temps ; (5) règle "au moins une mesure active" sur un exercice. Rapport d'échec réel obligatoire (commande, sortie brute, diagnostic) — pas d'enjolivement si KO, cf. règle du cycle dans le contexte Main.
|
Points à couvrir en priorité : (1) impossibilité de modifier les mesures activées ou l'ordre des exercices depuis une séance-modèle, seuls setsCount et cibles numériques doivent être modifiables ; (2) reprise de séance après kill complet de l'app (pas juste mise en arrière-plan) ; (3) recalcul correct des timers de repos ajustés (+/-15s) après reprise ; (4) intégrité de l'historique quand l'exercice, le programme ou la séance-modèle source ont été supprimés/archivés entre-temps ; (5) règle "au moins une mesure active" sur un exercice. Rapport d'échec réel obligatoire (commande, sortie brute, diagnostic) — pas d'enjolivement si KO, cf. règle du cycle dans le contexte Main.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "9592e96c-2c9f-439c-87f0-42b9147c1e00"
|
id: "9592e96c-2c9f-439c-87f0-42b9147c1e00"
|
||||||
number: 11
|
number: 11
|
||||||
title: "[QA] Plan et exécution des tests fonctionnels GameTime"
|
title: "[QA] Plan et exécution des tests fonctionnels GameTime"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "high"
|
priority: "high"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#6","kind":"dependsOn"},{"target":"#7","kind":"dependsOn"},{"target":"#8","kind":"dependsOn"},{"target":"#9","kind":"dependsOn"},{"target":"#10","kind":"dependsOn"}]
|
links: [{"target":"#6","kind":"dependsOn"},{"target":"#7","kind":"dependsOn"},{"target":"#8","kind":"dependsOn"},{"target":"#9","kind":"dependsOn"},{"target":"#10","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"7efa512f-3b3a-47b5-ade0-a2dd13073055","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301312946
|
createdAt: 1784301312946
|
||||||
updatedAt: 1784301397898
|
updatedAt: 1784309299325
|
||||||
version: 7
|
version: 9
|
||||||
---
|
---
|
||||||
Écrire et exécuter les tests couvrant : CRUD Exercice/Programme/Séance-modèle, règles d'overrides limités en séance, robustesse de la session active (reprise après fermeture/mise en arrière-plan de l'app, recalcul des timers depuis horodatages), génération correcte de l'historique (snapshot autonome), relance de séance (via séance-modèle et via snapshot si séance-modèle supprimée). Rapport d'échec réel (commande + sortie + diagnostic) sans enjoliver en cas de KO.
|
Écrire et exécuter les tests couvrant : CRUD Exercice/Programme/Séance-modèle, règles d'overrides limités en séance, robustesse de la session active (reprise après fermeture/mise en arrière-plan de l'app, recalcul des timers depuis horodatages), génération correcte de l'historique (snapshot autonome), relance de séance (via séance-modèle et via snapshot si séance-modèle supprimée). Rapport d'échec réel (commande + sortie + diagnostic) sans enjoliver en cas de KO.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#2"
|
issueRef: "#2"
|
||||||
version: 3
|
version: 5
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301371028
|
updatedAt: 1784304100048
|
||||||
---
|
---
|
||||||
Référence : mémoire projet "gametime-architecture-initial-stack-data-model" (Architect). Stack : Flutter + Drift (SQLite). Structure hexagonale obligatoire : domain / application / infrastructure(local) / presentation, composition root séparée. Pas de schéma de tables à ce stade (ticket #3), juste la connexion Drift vide et le wiring. Cibler iOS + Android dans le pubspec/config dès le départ même si le test utilisateur se fait sur Android.
|
Référence : mémoire projet "gametime-architecture-initial-stack-data-model" (Architect). Stack : Flutter + Drift (SQLite). Structure hexagonale obligatoire : domain / application / infrastructure(local) / presentation, composition root séparée. Pas de schéma de tables à ce stade (ticket #3), juste la connexion Drift vide et le wiring. Cibler iOS + Android dans le pubspec/config dès le départ même si le test utilisateur se fait sur Android.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "736ad58d-1020-438a-b563-5d0c30a05b04"
|
id: "736ad58d-1020-438a-b563-5d0c30a05b04"
|
||||||
number: 2
|
number: 2
|
||||||
title: "[DevBackend] Scaffolding projet Flutter + architecture hexagonale"
|
title: "[DevBackend] Scaffolding projet Flutter + architecture hexagonale"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "critical"
|
priority: "critical"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#1","kind":"dependsOn"}]
|
links: [{"target":"#1","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301286030
|
createdAt: 1784301286030
|
||||||
updatedAt: 1784301371028
|
updatedAt: 1784304100048
|
||||||
version: 3
|
version: 5
|
||||||
---
|
---
|
||||||
Initialiser le projet Flutter (cibles iOS + Android). Mettre en place la structure de dossiers domain / application / infrastructure(local) / presentation. Config lint/format. Intégration Drift de base (connexion DB vide, pas encore de schéma). Le domaine ne doit dépendre ni de Flutter ni de Drift.
|
Initialiser le projet Flutter (cibles iOS + Android). Mettre en place la structure de dossiers domain / application / infrastructure(local) / presentation. Config lint/format. Intégration Drift de base (connexion DB vide, pas encore de schéma). Le domaine ne doit dépendre ni de Flutter ni de Drift.
|
||||||
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#3"
|
issueRef: "#3"
|
||||||
version: 3
|
version: 5
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301376032
|
updatedAt: 1784305026425
|
||||||
---
|
---
|
||||||
Référence : mémoire "gametime-architecture-initial-stack-data-model" pour le détail des entités (Exercise, MediaAsset, Program, ProgramExercise, WorkoutTemplate, WorkoutTemplateProgram, WorkoutTemplateExerciseOverride, ActiveWorkoutSession, ActiveSetResult, ActiveRestState, WorkoutHistory, WorkoutHistorySetResult, change_log).
|
Référence : mémoire "gametime-architecture-initial-stack-data-model" pour le détail des entités (Exercise, MediaAsset, Program, ProgramExercise, WorkoutTemplate, WorkoutTemplateProgram, WorkoutTemplateExerciseOverride, ActiveWorkoutSession, ActiveSetResult, ActiveRestState, WorkoutHistory, WorkoutHistorySetResult, change_log).
|
||||||
Points de friction actés par Architect à respecter dans le schéma :
|
Points de friction actés par Architect à respecter dans le schéma :
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
id: "ea99a2e0-3c07-404c-95db-13818be0ee20"
|
id: "ea99a2e0-3c07-404c-95db-13818be0ee20"
|
||||||
number: 3
|
number: 3
|
||||||
title: "[DevBackend] Modèle de données Drift complet (entités + migrations)"
|
title: "[DevBackend] Modèle de données Drift complet (entités + migrations)"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "critical"
|
priority: "critical"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#2","kind":"dependsOn"}]
|
links: [{"target":"#2","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301289668
|
createdAt: 1784301289668
|
||||||
updatedAt: 1784301376032
|
updatedAt: 1784305026425
|
||||||
version: 3
|
version: 5
|
||||||
---
|
---
|
||||||
Implémenter les tables Drift : Exercise, MediaAsset, Program, ProgramExercise, WorkoutTemplate, WorkoutTemplateProgram, WorkoutTemplateExerciseOverride, ActiveWorkoutSession, ActiveSetResult, ActiveRestState, WorkoutHistory, WorkoutHistorySetResult, change_log. Champs sync-ready sur chaque agrégat : id stable (UUIDv7/ULID), createdAt/updatedAt/deletedAt, schemaVersion, syncState, localRevision, originDeviceId. Voir mémoire projet "gametime-architecture-initial-stack-data-model" pour le détail des champs par entité et les invariants (score = valeur numérique + label/unité snapshotés, exercice jamais supprimé en dur, etc.).
|
Implémenter les tables Drift : Exercise, MediaAsset, Program, ProgramExercise, WorkoutTemplate, WorkoutTemplateProgram, WorkoutTemplateExerciseOverride, ActiveWorkoutSession, ActiveSetResult, ActiveRestState, WorkoutHistory, WorkoutHistorySetResult, change_log. Champs sync-ready sur chaque agrégat : id stable (UUIDv7/ULID), createdAt/updatedAt/deletedAt, schemaVersion, syncState, localRevision, originDeviceId. Voir mémoire projet "gametime-architecture-initial-stack-data-model" pour le détail des champs par entité et les invariants (score = valeur numérique + label/unité snapshotés, exercice jamais supprimé en dur, etc.).
|
||||||
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#4"
|
issueRef: "#4"
|
||||||
version: 3
|
version: 5
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301378940
|
updatedAt: 1784305595841
|
||||||
---
|
---
|
||||||
Règle produit validée avec l'utilisateur (mémoire "gametime-ux-conception" section 3) : une séance-modèle peut surcharger uniquement le nombre de séries et les valeurs cibles numériques des mesures déjà actives d'un exercice — jamais quelles mesures sont actives, jamais l'ordre/la liste des exercices. Ce garde-fou doit être appliqué au niveau des use cases, pas seulement de l'UI.
|
Règle produit validée avec l'utilisateur (mémoire "gametime-ux-conception" section 3) : une séance-modèle peut surcharger uniquement le nombre de séries et les valeurs cibles numériques des mesures déjà actives d'un exercice — jamais quelles mesures sont actives, jamais l'ordre/la liste des exercices. Ce garde-fou doit être appliqué au niveau des use cases, pas seulement de l'UI.
|
||||||
Pause/reprise de session : recalculer les temps écoulés depuis les horodatages stockés (startedAt, pausedAt, lastPersistedAt), jamais depuis un compteur en mémoire — nécessaire pour survivre à une fermeture d'app.
|
Pause/reprise de session : recalculer les temps écoulés depuis les horodatages stockés (startedAt, pausedAt, lastPersistedAt), jamais depuis un compteur en mémoire — nécessaire pour survivre à une fermeture d'app.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "d3a8bc95-d1c7-4114-a964-7faa5b15835a"
|
id: "d3a8bc95-d1c7-4114-a964-7faa5b15835a"
|
||||||
number: 4
|
number: 4
|
||||||
title: "[DevBackend] Couche application : use cases et repositories"
|
title: "[DevBackend] Couche application : use cases et repositories"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "high"
|
priority: "high"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#3","kind":"dependsOn"}]
|
links: [{"target":"#3","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301293308
|
createdAt: 1784301293308
|
||||||
updatedAt: 1784301378940
|
updatedAt: 1784305595841
|
||||||
version: 3
|
version: 5
|
||||||
---
|
---
|
||||||
Implémenter ports/repositories et use cases pour : CRUD Exercise (règle "au moins une mesure active", avertissement si exercice déjà utilisé, archivage doux plutôt que suppression) ; CRUD Program (snapshot de l'exercice à l'ajout, mesures activées parmi celles autorisées, séries, cibles, repos rattaché à la série/l'exercice précédent) ; gestion WorkoutTemplate (composition de programmes en snapshot indépendant, overrides limités à setsCount et valeurs cibles numériques uniquement — pas de changement de mesures actives ni de structure) ; gestion ActiveWorkoutSession (démarrage, transitions série/repos, pause/reprise basées sur horodatages, pas sur un compteur mémoire) ; clôture de séance vers WorkoutHistory (snapshot autonome).
|
Implémenter ports/repositories et use cases pour : CRUD Exercise (règle "au moins une mesure active", avertissement si exercice déjà utilisé, archivage doux plutôt que suppression) ; CRUD Program (snapshot de l'exercice à l'ajout, mesures activées parmi celles autorisées, séries, cibles, repos rattaché à la série/l'exercice précédent) ; gestion WorkoutTemplate (composition de programmes en snapshot indépendant, overrides limités à setsCount et valeurs cibles numériques uniquement — pas de changement de mesures actives ni de structure) ; gestion ActiveWorkoutSession (démarrage, transitions série/repos, pause/reprise basées sur horodatages, pas sur un compteur mémoire) ; clôture de séance vers WorkoutHistory (snapshot autonome).
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#5"
|
issueRef: "#5"
|
||||||
version: 3
|
version: 5
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301380781
|
updatedAt: 1784305875016
|
||||||
---
|
---
|
||||||
Fichiers médias hors base SQLite, seulement les métadonnées dans MediaAsset (localUri, mimeType, sizeBytes, durationMs pour vidéo, width/height, checksum). Prévoir remoteUri nullable pour la sync future sans l'implémenter.
|
Fichiers médias hors base SQLite, seulement les métadonnées dans MediaAsset (localUri, mimeType, sizeBytes, durationMs pour vidéo, width/height, checksum). Prévoir remoteUri nullable pour la sync future sans l'implémenter.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "17cd97da-4b28-47c8-97e5-293e0173d640"
|
id: "17cd97da-4b28-47c8-97e5-293e0173d640"
|
||||||
number: 5
|
number: 5
|
||||||
title: "[DevBackend] Gestion des médias locaux"
|
title: "[DevBackend] Gestion des médias locaux"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "medium"
|
priority: "medium"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#3","kind":"dependsOn"}]
|
links: [{"target":"#3","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"10ee045b-1c41-479e-ba03-dceed9edd495","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301295496
|
createdAt: 1784301295496
|
||||||
updatedAt: 1784301380781
|
updatedAt: 1784305875016
|
||||||
version: 3
|
version: 5
|
||||||
---
|
---
|
||||||
Import/association d'images et vidéos aux exercices (entité MediaAsset), stockage fichier dans le stockage applicatif local, référencement par métadonnées SQLite, nettoyage des fichiers orphelins. Prévoir les champs sync-ready (remoteUri nullable, checksum) sans implémenter la synchro.
|
Import/association d'images et vidéos aux exercices (entité MediaAsset), stockage fichier dans le stockage applicatif local, référencement par métadonnées SQLite, nettoyage des fichiers orphelins. Prévoir les champs sync-ready (remoteUri nullable, checksum) sans implémenter la synchro.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#6"
|
issueRef: "#6"
|
||||||
version: 4
|
version: 6
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301382963
|
updatedAt: 1784306547848
|
||||||
---
|
---
|
||||||
Référence : mémoire "gametime-ux-conception" section 1. Point critique UX : la section "Mesures disponibles" doit rester très lisible (toggles Temps/Répétitions/Score avec aide courte par toggle, champs label+unité seulement si Score activé). Règle bloquante : au moins une mesure doit être active pour enregistrer. Message non bloquant si on modifie les mesures d'un exercice déjà utilisé ailleurs (les programmes existants restent inchangés grâce aux snapshots).
|
Référence : mémoire "gametime-ux-conception" section 1. Point critique UX : la section "Mesures disponibles" doit rester très lisible (toggles Temps/Répétitions/Score avec aide courte par toggle, champs label+unité seulement si Score activé). Règle bloquante : au moins une mesure doit être active pour enregistrer. Message non bloquant si on modifie les mesures d'un exercice déjà utilisé ailleurs (les programmes existants restent inchangés grâce aux snapshots).
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "c748c16e-b782-47a8-accb-aa5d88b9adfb"
|
id: "c748c16e-b782-47a8-accb-aa5d88b9adfb"
|
||||||
number: 6
|
number: 6
|
||||||
title: "[DevFrontend] Écran Bibliothèque d'exercices"
|
title: "[DevFrontend] Écran Bibliothèque d'exercices"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "high"
|
priority: "high"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#4","kind":"dependsOn"},{"target":"#5","kind":"dependsOn"}]
|
links: [{"target":"#4","kind":"dependsOn"},{"target":"#5","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301298045
|
createdAt: 1784301298045
|
||||||
updatedAt: 1784301382963
|
updatedAt: 1784306547848
|
||||||
version: 4
|
version: 6
|
||||||
---
|
---
|
||||||
Liste avec recherche et filtres chips par mesure disponible. Écran création/édition d'exercice : nom, description, image/vidéo optionnelles, section "Mesures disponibles" (toggles Temps/Répétitions/Score cumulables, label+unité si Score). Règle : au moins une mesure obligatoire. Avertissement non bloquant si modification des mesures d'un exercice déjà utilisé. Cf. mémoire "gametime-ux-conception" section 1.
|
Liste avec recherche et filtres chips par mesure disponible. Écran création/édition d'exercice : nom, description, image/vidéo optionnelles, section "Mesures disponibles" (toggles Temps/Répétitions/Score cumulables, label+unité si Score). Règle : au moins une mesure obligatoire. Avertissement non bloquant si modification des mesures d'un exercice déjà utilisé. Cf. mémoire "gametime-ux-conception" section 1.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#7"
|
issueRef: "#7"
|
||||||
version: 4
|
version: 6
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301385567
|
updatedAt: 1784307099507
|
||||||
---
|
---
|
||||||
Référence : mémoire "gametime-ux-conception" section 2. Repos affiché comme "Repos après cette série", attaché à la série/l'exercice précédent pour suivre le réordonnancement (poignée de déplacement). Pas de repos après la toute dernière série du programme. Ligne résumé attendue sur chaque carte exercice : "X séries · [mesures actives] · Repos Ys".
|
Référence : mémoire "gametime-ux-conception" section 2. Repos affiché comme "Repos après cette série", attaché à la série/l'exercice précédent pour suivre le réordonnancement (poignée de déplacement). Pas de repos après la toute dernière série du programme. Ligne résumé attendue sur chaque carte exercice : "X séries · [mesures actives] · Repos Ys".
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "f955dede-b254-49da-95bb-f63adca76d05"
|
id: "f955dede-b254-49da-95bb-f63adca76d05"
|
||||||
number: 7
|
number: 7
|
||||||
title: "[DevFrontend] Écran Création/édition de programme"
|
title: "[DevFrontend] Écran Création/édition de programme"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "high"
|
priority: "high"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#4","kind":"dependsOn"},{"target":"#6","kind":"dependsOn"}]
|
links: [{"target":"#4","kind":"dependsOn"},{"target":"#6","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301300945
|
createdAt: 1784301300945
|
||||||
updatedAt: 1784301385567
|
updatedAt: 1784307099507
|
||||||
version: 4
|
version: 6
|
||||||
---
|
---
|
||||||
Ajout d'exercices depuis la bibliothèque (recherche, badges de mesures). Cartes réordonnables par exercice. Configuration par exercice : nombre de séries, mesures à suivre (sous-ensemble de celles autorisées par l'exercice), cible par mesure activée, repos après chaque série (hérite du défaut programme, surchargeable). Gestion du cas "exercice archivé/supprimé de la bibliothèque". Cf. mémoire "gametime-ux-conception" section 2.
|
Ajout d'exercices depuis la bibliothèque (recherche, badges de mesures). Cartes réordonnables par exercice. Configuration par exercice : nombre de séries, mesures à suivre (sous-ensemble de celles autorisées par l'exercice), cible par mesure activée, repos après chaque série (hérite du défaut programme, surchargeable). Gestion du cas "exercice archivé/supprimé de la bibliothèque". Cf. mémoire "gametime-ux-conception" section 2.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#8"
|
issueRef: "#8"
|
||||||
version: 4
|
version: 6
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301388397
|
updatedAt: 1784307587164
|
||||||
---
|
---
|
||||||
Référence : mémoire "gametime-ux-conception" section 3 — décision produit explicitement validée par l'utilisateur. Au premier ajout d'un programme à la séance, afficher le message expliquant qu'une copie est enregistrée et que les futures modifications du programme source ne changeront pas cette séance. Les seuls champs éditables depuis cette surface sont : nombre de séries, et valeurs cibles numériques des mesures déjà actives (temps cible, répétitions cible, score cible). Ne pas permettre l'ajout/suppression/réordonnancement d'exercices ni le changement des mesures activées ici — ça reste au niveau du programme source (ticket #7).
|
Référence : mémoire "gametime-ux-conception" section 3 — décision produit explicitement validée par l'utilisateur. Au premier ajout d'un programme à la séance, afficher le message expliquant qu'une copie est enregistrée et que les futures modifications du programme source ne changeront pas cette séance. Les seuls champs éditables depuis cette surface sont : nombre de séries, et valeurs cibles numériques des mesures déjà actives (temps cible, répétitions cible, score cible). Ne pas permettre l'ajout/suppression/réordonnancement d'exercices ni le changement des mesures activées ici — ça reste au niveau du programme source (ticket #7).
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "8f24db05-5d56-4324-8bef-68e4837e0e3b"
|
id: "8f24db05-5d56-4324-8bef-68e4837e0e3b"
|
||||||
number: 8
|
number: 8
|
||||||
title: "[DevFrontend] Écran Composition de séance-modèle"
|
title: "[DevFrontend] Écran Composition de séance-modèle"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "high"
|
priority: "high"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#4","kind":"dependsOn"},{"target":"#7","kind":"dependsOn"}]
|
links: [{"target":"#4","kind":"dependsOn"},{"target":"#7","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301303492
|
createdAt: 1784301303492
|
||||||
updatedAt: 1784301388397
|
updatedAt: 1784307587164
|
||||||
version: 4
|
version: 6
|
||||||
---
|
---
|
||||||
Assemblage nommé et réordonnable de un ou plusieurs programmes (copie/snapshot au moment de l'ajout, avec message explicite à l'utilisateur sur l'indépendance vis-à-vis du programme source). Overrides autorisés uniquement : nombre de séries et valeurs cibles numériques des mesures déjà actives par exercice — pas de changement de mesures actives, pas de réordonnancement/ajout/suppression d'exercice depuis la séance. Cf. mémoire "gametime-ux-conception" section 3 (décision produit tranchée avec l'utilisateur).
|
Assemblage nommé et réordonnable de un ou plusieurs programmes (copie/snapshot au moment de l'ajout, avec message explicite à l'utilisateur sur l'indépendance vis-à-vis du programme source). Overrides autorisés uniquement : nombre de séries et valeurs cibles numériques des mesures déjà actives par exercice — pas de changement de mesures actives, pas de réordonnancement/ajout/suppression d'exercice depuis la séance. Cf. mémoire "gametime-ux-conception" section 3 (décision produit tranchée avec l'utilisateur).
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
issueRef: "#9"
|
issueRef: "#9"
|
||||||
version: 4
|
version: 6
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedAt: 1784301392430
|
updatedAt: 1784308091100
|
||||||
---
|
---
|
||||||
Référence : mémoire "gametime-ux-conception" section 4. Écran le plus sensible du produit : grandes zones tactiles (44-48px mini), utilisable à l'effort, une main. Hiérarchie de saisie si mesures cumulées : Temps (bloc principal) > Répétitions (stepper) > Score (champ + unité, clavier adapté au type). Écran "Repos" séparé avec +15s/-15s et "Ignorer le repos". Pause : "Quitter et sauvegarder" comme action sûre recommandée, "Abandonner" comme action destructive confirmée séparément. Reprise doit fonctionner même après fermeture complète de l'app (bandeau "Séance en cours" au retour). Fin de séance écrit dans WorkoutHistory (ticket #4) avec temps total + scores par série.
|
Référence : mémoire "gametime-ux-conception" section 4. Écran le plus sensible du produit : grandes zones tactiles (44-48px mini), utilisable à l'effort, une main. Hiérarchie de saisie si mesures cumulées : Temps (bloc principal) > Répétitions (stepper) > Score (champ + unité, clavier adapté au type). Écran "Repos" séparé avec +15s/-15s et "Ignorer le repos". Pause : "Quitter et sauvegarder" comme action sûre recommandée, "Abandonner" comme action destructive confirmée séparément. Reprise doit fonctionner même après fermeture complète de l'app (bandeau "Séance en cours" au retour). Fin de séance écrit dans WorkoutHistory (ticket #4) avec temps total + scores par série.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
id: "311cfb2b-0a09-4c18-ba66-4053994bc109"
|
id: "311cfb2b-0a09-4c18-ba66-4053994bc109"
|
||||||
number: 9
|
number: 9
|
||||||
title: "[DevFrontend] Écran Exécution de séance"
|
title: "[DevFrontend] Écran Exécution de séance"
|
||||||
status: "open"
|
status: "closed"
|
||||||
priority: "critical"
|
priority: "critical"
|
||||||
sprint: null
|
sprint: null
|
||||||
links: [{"target":"#4","kind":"dependsOn"},{"target":"#8","kind":"dependsOn"}]
|
links: [{"target":"#4","kind":"dependsOn"},{"target":"#8","kind":"dependsOn"}]
|
||||||
@ -10,7 +10,7 @@ agentRefs: [{"agentId":"9933c93a-b8a1-4164-a3bb-7063fdad747d","role":"assigned"}
|
|||||||
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
createdBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
updatedBy: {"kind":"agent","agent_id":"57695b92-24d0-4876-837c-76116e70a6ae"}
|
||||||
createdAt: 1784301307130
|
createdAt: 1784301307130
|
||||||
updatedAt: 1784301392430
|
updatedAt: 1784308091100
|
||||||
version: 4
|
version: 6
|
||||||
---
|
---
|
||||||
Surface critique optimisée pour l'usage à l'effort (grandes zones tactiles, une main). En-tête temps écoulé + pause. Progression Programme/Exercice/Série. Saisie hiérarchisée quand mesures cumulées (Temps > Répétitions > Score avec unité). Écran "Repos" dédié entre séries avec ajustement ±15s et "Ignorer le repos". Pause avec "Quitter et sauvegarder" vs "Abandonner" (destructif confirmé). Reprise après interruption/fermeture app, robuste à l'arrière-plan (recalcul depuis horodatages, pas depuis un état mémoire). Écran de fin de séance (récap + relance) → écriture dans l'historique. Cf. mémoire "gametime-ux-conception" section 4.
|
Surface critique optimisée pour l'usage à l'effort (grandes zones tactiles, une main). En-tête temps écoulé + pause. Progression Programme/Exercice/Série. Saisie hiérarchisée quand mesures cumulées (Temps > Répétitions > Score avec unité). Écran "Repos" dédié entre séries avec ajustement ±15s et "Ignorer le repos". Pause avec "Quitter et sauvegarder" vs "Abandonner" (destructif confirmé). Reprise après interruption/fermeture app, robuste à l'arrière-plan (recalcul depuis horodatages, pas depuis un état mémoire). Écran de fin de séance (récap + relance) → écriture dans l'historique. Cf. mémoire "gametime-ux-conception" section 4.
|
||||||
@ -5,133 +5,133 @@
|
|||||||
"issueRef": "#1",
|
"issueRef": "#1",
|
||||||
"path": "1",
|
"path": "1",
|
||||||
"title": "[Git] Initialiser le dépôt et la stratégie de branches GameTime",
|
"title": "[Git] Initialiser le dépôt et la stratégie de branches GameTime",
|
||||||
"status": "inProgress",
|
"status": "closed",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"8f065f64-ef6e-4a00-af9c-d00be079e3cc"
|
"8f065f64-ef6e-4a00-af9c-d00be079e3cc"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301472470
|
"updatedAt": 1784301585782
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#2",
|
"issueRef": "#2",
|
||||||
"path": "2",
|
"path": "2",
|
||||||
"title": "[DevBackend] Scaffolding projet Flutter + architecture hexagonale",
|
"title": "[DevBackend] Scaffolding projet Flutter + architecture hexagonale",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "critical",
|
"priority": "critical",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301371028
|
"updatedAt": 1784304100048
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#3",
|
"issueRef": "#3",
|
||||||
"path": "3",
|
"path": "3",
|
||||||
"title": "[DevBackend] Modèle de données Drift complet (entités + migrations)",
|
"title": "[DevBackend] Modèle de données Drift complet (entités + migrations)",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "critical",
|
"priority": "critical",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301376032
|
"updatedAt": 1784305026425
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#4",
|
"issueRef": "#4",
|
||||||
"path": "4",
|
"path": "4",
|
||||||
"title": "[DevBackend] Couche application : use cases et repositories",
|
"title": "[DevBackend] Couche application : use cases et repositories",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301378940
|
"updatedAt": 1784305595841
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#5",
|
"issueRef": "#5",
|
||||||
"path": "5",
|
"path": "5",
|
||||||
"title": "[DevBackend] Gestion des médias locaux",
|
"title": "[DevBackend] Gestion des médias locaux",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
"10ee045b-1c41-479e-ba03-dceed9edd495"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301380781
|
"updatedAt": 1784305875016
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#6",
|
"issueRef": "#6",
|
||||||
"path": "6",
|
"path": "6",
|
||||||
"title": "[DevFrontend] Écran Bibliothèque d'exercices",
|
"title": "[DevFrontend] Écran Bibliothèque d'exercices",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301382963
|
"updatedAt": 1784306547848
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#7",
|
"issueRef": "#7",
|
||||||
"path": "7",
|
"path": "7",
|
||||||
"title": "[DevFrontend] Écran Création/édition de programme",
|
"title": "[DevFrontend] Écran Création/édition de programme",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301385567
|
"updatedAt": 1784307099507
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#8",
|
"issueRef": "#8",
|
||||||
"path": "8",
|
"path": "8",
|
||||||
"title": "[DevFrontend] Écran Composition de séance-modèle",
|
"title": "[DevFrontend] Écran Composition de séance-modèle",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301388397
|
"updatedAt": 1784307587164
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#9",
|
"issueRef": "#9",
|
||||||
"path": "9",
|
"path": "9",
|
||||||
"title": "[DevFrontend] Écran Exécution de séance",
|
"title": "[DevFrontend] Écran Exécution de séance",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "critical",
|
"priority": "critical",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301392430
|
"updatedAt": 1784308091100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#10",
|
"issueRef": "#10",
|
||||||
"path": "10",
|
"path": "10",
|
||||||
"title": "[DevFrontend] Écran Historique des séances",
|
"title": "[DevFrontend] Écran Historique des séances",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "medium",
|
"priority": "medium",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
"9933c93a-b8a1-4164-a3bb-7063fdad747d"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301394213
|
"updatedAt": 1784308662161
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#11",
|
"issueRef": "#11",
|
||||||
"path": "11",
|
"path": "11",
|
||||||
"title": "[QA] Plan et exécution des tests fonctionnels GameTime",
|
"title": "[QA] Plan et exécution des tests fonctionnels GameTime",
|
||||||
"status": "open",
|
"status": "closed",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"sprint": null,
|
"sprint": null,
|
||||||
"assignedAgentIds": [
|
"assignedAgentIds": [
|
||||||
"7efa512f-3b3a-47b5-ade0-a2dd13073055"
|
"7efa512f-3b3a-47b5-ade0-a2dd13073055"
|
||||||
],
|
],
|
||||||
"updatedAt": 1784301397898
|
"updatedAt": 1784309299325
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"issueRef": "#12",
|
"issueRef": "#12",
|
||||||
|
|||||||
33
.metadata
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "ee80f08bbf97172ec030b8751ceab557177a34a6"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: android
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
- platform: ios
|
||||||
|
create_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
base_revision: ee80f08bbf97172ec030b8751ceab557177a34a6
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
9
analysis_options.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- '**/*.g.dart'
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
prefer_single_quotes: true
|
||||||
14
android/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
42
android/app/build.gradle.kts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.gametime.app"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.gametime.app"
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
45
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="GameTime"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.gametime.app
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
18
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
24
android/build.gradle.kts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory =
|
||||||
|
rootProject.layout.buildDirectory
|
||||||
|
.dir("../../build")
|
||||||
|
.get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
6
android/gradle.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
# This newDsl flag was added by the Flutter template
|
||||||
|
android.newDsl=false
|
||||||
|
# This builtInKotlin flag was added by the Flutter template
|
||||||
|
android.builtInKotlin=false
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
|
||||||
26
android/settings.gradle.kts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath =
|
||||||
|
run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "9.0.1" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
34
ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
24
ios/Flutter/AppFrameworkInfo.plist
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
1
ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
42
ios/Podfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
platform :ios, '12.0'
|
||||||
|
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. Run flutter pub get first."
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise 'FLUTTER_ROOT not found in Generated.xcconfig.'
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
644
ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,644 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
packageProductDependencies = (
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
);
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
packageReferences = (
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||||
|
);
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gametime.app;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gametime.app.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gametime.app.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gametime.app.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gametime.app;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.gametime.app;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
119
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<PreActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Prepare Flutter Framework Script"
|
||||||
|
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PreActions>
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
16
ios/Runner/AppDelegate.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
70
ios/Runner/Info.plist
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Gametime</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>gametime</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
6
ios/Runner/SceneDelegate.swift
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SceneDelegate: FlutterSceneDelegate {
|
||||||
|
|
||||||
|
}
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
90
lib/application/app_bootstrap.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import '../infrastructure/local/local.dart';
|
||||||
|
import 'application.dart';
|
||||||
|
|
||||||
|
final class AppBootstrap {
|
||||||
|
AppBootstrap._({
|
||||||
|
required this.database,
|
||||||
|
required this.exerciseUseCases,
|
||||||
|
required this.mediaUseCases,
|
||||||
|
required this.programUseCases,
|
||||||
|
required this.workoutTemplateUseCases,
|
||||||
|
required this.activeWorkoutSessionUseCases,
|
||||||
|
required this.closeWorkoutSessionUseCase,
|
||||||
|
required this.workoutHistoryUseCases,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AppDatabase database;
|
||||||
|
final ExerciseUseCases exerciseUseCases;
|
||||||
|
final MediaUseCases mediaUseCases;
|
||||||
|
final ProgramUseCases programUseCases;
|
||||||
|
final WorkoutTemplateUseCases workoutTemplateUseCases;
|
||||||
|
final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
|
||||||
|
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
|
||||||
|
final WorkoutHistoryUseCases workoutHistoryUseCases;
|
||||||
|
|
||||||
|
static Future<AppBootstrap> create() async {
|
||||||
|
final database = AppDatabase.open();
|
||||||
|
await database.customSelect('SELECT 1').get();
|
||||||
|
final exerciseRepository = DriftExerciseRepository(database);
|
||||||
|
final mediaRepository = DriftMediaAssetRepository(database);
|
||||||
|
final programRepository = DriftProgramRepository(database);
|
||||||
|
final templateRepository = DriftWorkoutTemplateRepository(database);
|
||||||
|
final activeSessionRepository = DriftActiveSessionRepository(database);
|
||||||
|
final historyRepository = DriftWorkoutHistoryRepository(database);
|
||||||
|
final ids = LocalIdGenerator();
|
||||||
|
const clock = SystemClock();
|
||||||
|
const originDeviceId = 'local-device';
|
||||||
|
|
||||||
|
return AppBootstrap._(
|
||||||
|
database: database,
|
||||||
|
exerciseUseCases: ExerciseUseCases(
|
||||||
|
repository: exerciseRepository,
|
||||||
|
clock: clock,
|
||||||
|
ids: ids,
|
||||||
|
originDeviceId: originDeviceId,
|
||||||
|
),
|
||||||
|
mediaUseCases: MediaUseCases(
|
||||||
|
mediaRepository: mediaRepository,
|
||||||
|
exerciseRepository: exerciseRepository,
|
||||||
|
storage: const PathProviderLocalMediaStorage(),
|
||||||
|
clock: clock,
|
||||||
|
ids: ids,
|
||||||
|
originDeviceId: originDeviceId,
|
||||||
|
),
|
||||||
|
programUseCases: ProgramUseCases(
|
||||||
|
programRepository: programRepository,
|
||||||
|
exerciseRepository: exerciseRepository,
|
||||||
|
clock: clock,
|
||||||
|
ids: ids,
|
||||||
|
originDeviceId: originDeviceId,
|
||||||
|
),
|
||||||
|
workoutTemplateUseCases: WorkoutTemplateUseCases(
|
||||||
|
templateRepository: templateRepository,
|
||||||
|
programRepository: programRepository,
|
||||||
|
clock: clock,
|
||||||
|
ids: ids,
|
||||||
|
originDeviceId: originDeviceId,
|
||||||
|
),
|
||||||
|
activeWorkoutSessionUseCases: ActiveWorkoutSessionUseCases(
|
||||||
|
sessionRepository: activeSessionRepository,
|
||||||
|
templateRepository: templateRepository,
|
||||||
|
clock: clock,
|
||||||
|
ids: ids,
|
||||||
|
originDeviceId: originDeviceId,
|
||||||
|
),
|
||||||
|
closeWorkoutSessionUseCase: CloseWorkoutSessionUseCase(
|
||||||
|
sessionRepository: activeSessionRepository,
|
||||||
|
historyRepository: historyRepository,
|
||||||
|
clock: clock,
|
||||||
|
ids: ids,
|
||||||
|
originDeviceId: originDeviceId,
|
||||||
|
),
|
||||||
|
workoutHistoryUseCases: WorkoutHistoryUseCases(
|
||||||
|
repository: historyRepository,
|
||||||
|
clock: clock,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() => database.close();
|
||||||
|
}
|
||||||
8
lib/application/application.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// Application layer entry point.
|
||||||
|
///
|
||||||
|
/// This layer owns use cases and ports. It may depend on domain, but concrete
|
||||||
|
/// adapters are wired only from the composition root.
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'ports.dart';
|
||||||
|
export 'use_cases.dart';
|
||||||
93
lib/application/ports.dart
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import '../domain/domain.dart';
|
||||||
|
|
||||||
|
abstract interface class Clock {
|
||||||
|
DateTime now();
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SystemClock implements Clock {
|
||||||
|
const SystemClock();
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime now() => DateTime.now().toUtc();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class IdGenerator {
|
||||||
|
String newId();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class ExerciseRepository {
|
||||||
|
Future<Exercise?> findById(String id);
|
||||||
|
Future<List<Exercise>> listActive();
|
||||||
|
Future<bool> isReferencedByProgram(String id);
|
||||||
|
Future<void> save(Exercise exercise);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class StoredMediaFile {
|
||||||
|
const StoredMediaFile({
|
||||||
|
required this.localUri,
|
||||||
|
required this.mimeType,
|
||||||
|
required this.sizeBytes,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.durationMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String localUri;
|
||||||
|
final String? mimeType;
|
||||||
|
final int sizeBytes;
|
||||||
|
final int? width;
|
||||||
|
final int? height;
|
||||||
|
final int? durationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class LocalMediaStorage {
|
||||||
|
Future<StoredMediaFile> importFile({
|
||||||
|
required String sourcePath,
|
||||||
|
required MediaKind kind,
|
||||||
|
required String stableFileName,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Set<String>> listManagedLocalUris();
|
||||||
|
Future<void> deleteByLocalUri(String localUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class MediaAssetRepository {
|
||||||
|
Future<MediaAsset?> findById(String id);
|
||||||
|
Future<List<MediaAsset>> listActive();
|
||||||
|
Future<void> save(MediaAsset mediaAsset);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class ProgramRepository {
|
||||||
|
Future<Program?> findById(String id);
|
||||||
|
Future<List<Program>> listActive();
|
||||||
|
Future<void> save(Program program);
|
||||||
|
Future<void> saveExercise(ProgramExercise exercise);
|
||||||
|
Future<void> replaceExercises(Program program, DateTime deletedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class WorkoutTemplateRepository {
|
||||||
|
Future<WorkoutTemplate?> findById(String id);
|
||||||
|
Future<List<WorkoutTemplate>> listActive();
|
||||||
|
Future<void> save(WorkoutTemplate template);
|
||||||
|
Future<void> saveProgram(WorkoutTemplateProgram program);
|
||||||
|
Future<void> saveOverride(WorkoutTemplateExerciseOverride override);
|
||||||
|
Future<void> replaceComposition(WorkoutTemplate template, DateTime deletedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class ActiveSessionRepository {
|
||||||
|
Future<ActiveWorkoutSession?> findById(String id);
|
||||||
|
Future<ActiveWorkoutSession?> findOpen();
|
||||||
|
Future<void> save(ActiveWorkoutSession session);
|
||||||
|
Future<void> saveSetResult(ActiveSetResult result);
|
||||||
|
Future<void> saveRestState(ActiveRestState restState);
|
||||||
|
Future<List<ActiveSetResult>> listSetResults(String sessionId);
|
||||||
|
Future<List<ActiveRestState>> listRestStates(String sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract interface class WorkoutHistoryRepository {
|
||||||
|
Future<WorkoutHistory?> findById(String id);
|
||||||
|
Future<List<WorkoutHistory>> listActive();
|
||||||
|
Future<void> save(WorkoutHistory history);
|
||||||
|
Future<void> saveSetResult(WorkoutHistorySetResult result);
|
||||||
|
Future<void> delete(String id, DateTime deletedAt);
|
||||||
|
}
|
||||||
1051
lib/application/use_cases.dart
Normal file
6
lib/domain/domain.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/// Domain layer entry point.
|
||||||
|
///
|
||||||
|
/// Keep this layer free from Flutter, Drift, platform APIs, and IO.
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'entities.dart';
|
||||||
758
lib/domain/entities.dart
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
enum SyncState { localOnly, dirty, synced, deleted }
|
||||||
|
|
||||||
|
enum MediaKind { image, video }
|
||||||
|
|
||||||
|
enum WorkoutMeasure { time, reps, score }
|
||||||
|
|
||||||
|
enum ActiveWorkoutStatus { running, paused, savedExit, completed, abandoned }
|
||||||
|
|
||||||
|
final class DomainException implements Exception {
|
||||||
|
const DomainException(this.message);
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EntityMetadata {
|
||||||
|
const EntityMetadata({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.originDeviceId,
|
||||||
|
this.deletedAt,
|
||||||
|
this.schemaVersion = 1,
|
||||||
|
this.syncState = SyncState.dirty,
|
||||||
|
this.localRevision = 0,
|
||||||
|
this.futureOwnerProfileId,
|
||||||
|
this.lastSyncedAt,
|
||||||
|
this.remoteRevision,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
final int schemaVersion;
|
||||||
|
final SyncState syncState;
|
||||||
|
final int localRevision;
|
||||||
|
final String originDeviceId;
|
||||||
|
final String? futureOwnerProfileId;
|
||||||
|
final DateTime? lastSyncedAt;
|
||||||
|
final String? remoteRevision;
|
||||||
|
|
||||||
|
EntityMetadata touch(DateTime now) {
|
||||||
|
return copyWith(updatedAt: now, localRevision: localRevision + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityMetadata markDeleted(DateTime now) {
|
||||||
|
return copyWith(
|
||||||
|
updatedAt: now,
|
||||||
|
deletedAt: now,
|
||||||
|
syncState: SyncState.deleted,
|
||||||
|
localRevision: localRevision + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityMetadata copyWith({
|
||||||
|
DateTime? createdAt,
|
||||||
|
DateTime? updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
int? schemaVersion,
|
||||||
|
SyncState? syncState,
|
||||||
|
int? localRevision,
|
||||||
|
String? originDeviceId,
|
||||||
|
String? futureOwnerProfileId,
|
||||||
|
DateTime? lastSyncedAt,
|
||||||
|
String? remoteRevision,
|
||||||
|
}) {
|
||||||
|
return EntityMetadata(
|
||||||
|
id: id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
deletedAt: deletedAt ?? this.deletedAt,
|
||||||
|
schemaVersion: schemaVersion ?? this.schemaVersion,
|
||||||
|
syncState: syncState ?? this.syncState,
|
||||||
|
localRevision: localRevision ?? this.localRevision,
|
||||||
|
originDeviceId: originDeviceId ?? this.originDeviceId,
|
||||||
|
futureOwnerProfileId: futureOwnerProfileId ?? this.futureOwnerProfileId,
|
||||||
|
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
|
||||||
|
remoteRevision: remoteRevision ?? this.remoteRevision,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MediaAsset {
|
||||||
|
const MediaAsset({
|
||||||
|
required this.metadata,
|
||||||
|
required this.kind,
|
||||||
|
required this.localUri,
|
||||||
|
this.mimeType,
|
||||||
|
this.sizeBytes,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.durationMs,
|
||||||
|
this.checksum,
|
||||||
|
this.remoteUri,
|
||||||
|
this.thumbnailLocalUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final MediaKind kind;
|
||||||
|
final String localUri;
|
||||||
|
final String? mimeType;
|
||||||
|
final int? sizeBytes;
|
||||||
|
final int? width;
|
||||||
|
final int? height;
|
||||||
|
final int? durationMs;
|
||||||
|
final String? checksum;
|
||||||
|
final String? remoteUri;
|
||||||
|
final String? thumbnailLocalUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Exercise {
|
||||||
|
Exercise({
|
||||||
|
required this.metadata,
|
||||||
|
required String name,
|
||||||
|
this.description,
|
||||||
|
this.imageMediaId,
|
||||||
|
this.videoMediaId,
|
||||||
|
required this.hasTimeMeasure,
|
||||||
|
required this.hasRepsMeasure,
|
||||||
|
required this.hasScoreMeasure,
|
||||||
|
this.scoreLabel,
|
||||||
|
this.scoreUnit,
|
||||||
|
this.archivedAt,
|
||||||
|
}) : name = _nonBlank(name, 'Exercise name') {
|
||||||
|
_requireAtLeastOneMeasure(
|
||||||
|
hasTime: hasTimeMeasure,
|
||||||
|
hasReps: hasRepsMeasure,
|
||||||
|
hasScore: hasScoreMeasure,
|
||||||
|
);
|
||||||
|
if (hasScoreMeasure) {
|
||||||
|
_nonBlank(scoreLabel, 'Score label');
|
||||||
|
_nonBlank(scoreUnit, 'Score unit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String name;
|
||||||
|
final String? description;
|
||||||
|
final String? imageMediaId;
|
||||||
|
final String? videoMediaId;
|
||||||
|
final bool hasTimeMeasure;
|
||||||
|
final bool hasRepsMeasure;
|
||||||
|
final bool hasScoreMeasure;
|
||||||
|
final String? scoreLabel;
|
||||||
|
final String? scoreUnit;
|
||||||
|
final DateTime? archivedAt;
|
||||||
|
|
||||||
|
Set<WorkoutMeasure> get availableMeasures => {
|
||||||
|
if (hasTimeMeasure) WorkoutMeasure.time,
|
||||||
|
if (hasRepsMeasure) WorkoutMeasure.reps,
|
||||||
|
if (hasScoreMeasure) WorkoutMeasure.score,
|
||||||
|
};
|
||||||
|
|
||||||
|
Exercise archive(DateTime now) {
|
||||||
|
return copyWith(archivedAt: now, metadata: metadata.touch(now));
|
||||||
|
}
|
||||||
|
|
||||||
|
Exercise copyWith({
|
||||||
|
EntityMetadata? metadata,
|
||||||
|
String? name,
|
||||||
|
Object? description = _unchanged,
|
||||||
|
Object? imageMediaId = _unchanged,
|
||||||
|
Object? videoMediaId = _unchanged,
|
||||||
|
bool? hasTimeMeasure,
|
||||||
|
bool? hasRepsMeasure,
|
||||||
|
bool? hasScoreMeasure,
|
||||||
|
Object? scoreLabel = _unchanged,
|
||||||
|
Object? scoreUnit = _unchanged,
|
||||||
|
Object? archivedAt = _unchanged,
|
||||||
|
}) {
|
||||||
|
return Exercise(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
name: name ?? this.name,
|
||||||
|
description: description == _unchanged
|
||||||
|
? this.description
|
||||||
|
: description as String?,
|
||||||
|
imageMediaId: imageMediaId == _unchanged
|
||||||
|
? this.imageMediaId
|
||||||
|
: imageMediaId as String?,
|
||||||
|
videoMediaId: videoMediaId == _unchanged
|
||||||
|
? this.videoMediaId
|
||||||
|
: videoMediaId as String?,
|
||||||
|
hasTimeMeasure: hasTimeMeasure ?? this.hasTimeMeasure,
|
||||||
|
hasRepsMeasure: hasRepsMeasure ?? this.hasRepsMeasure,
|
||||||
|
hasScoreMeasure: hasScoreMeasure ?? this.hasScoreMeasure,
|
||||||
|
scoreLabel: scoreLabel == _unchanged
|
||||||
|
? this.scoreLabel
|
||||||
|
: scoreLabel as String?,
|
||||||
|
scoreUnit: scoreUnit == _unchanged
|
||||||
|
? this.scoreUnit
|
||||||
|
: scoreUnit as String?,
|
||||||
|
archivedAt: archivedAt == _unchanged
|
||||||
|
? this.archivedAt
|
||||||
|
: archivedAt as DateTime?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Object _unchanged = Object();
|
||||||
|
|
||||||
|
final class Program {
|
||||||
|
Program({
|
||||||
|
required this.metadata,
|
||||||
|
required String name,
|
||||||
|
required this.defaultRestSeconds,
|
||||||
|
this.exercises = const [],
|
||||||
|
}) : name = _nonBlank(name, 'Program name') {
|
||||||
|
_requireNonNegative(defaultRestSeconds, 'Default rest seconds');
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String name;
|
||||||
|
final int defaultRestSeconds;
|
||||||
|
final List<ProgramExercise> exercises;
|
||||||
|
|
||||||
|
Program copyWith({
|
||||||
|
List<ProgramExercise>? exercises,
|
||||||
|
EntityMetadata? metadata,
|
||||||
|
String? name,
|
||||||
|
int? defaultRestSeconds,
|
||||||
|
}) {
|
||||||
|
return Program(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
name: name ?? this.name,
|
||||||
|
defaultRestSeconds: defaultRestSeconds ?? this.defaultRestSeconds,
|
||||||
|
exercises: exercises ?? this.exercises,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ProgramExercise {
|
||||||
|
ProgramExercise({
|
||||||
|
required this.metadata,
|
||||||
|
required this.programId,
|
||||||
|
this.sourceExerciseId,
|
||||||
|
required this.position,
|
||||||
|
required String exerciseNameSnapshot,
|
||||||
|
this.exerciseDescriptionSnapshot,
|
||||||
|
this.exerciseImageMediaIdSnapshot,
|
||||||
|
this.exerciseVideoMediaIdSnapshot,
|
||||||
|
this.exerciseArchivedSnapshot = false,
|
||||||
|
required this.availableTimeSnapshot,
|
||||||
|
required this.availableRepsSnapshot,
|
||||||
|
required this.availableScoreSnapshot,
|
||||||
|
this.scoreLabelSnapshot,
|
||||||
|
this.scoreUnitSnapshot,
|
||||||
|
required this.setsCount,
|
||||||
|
required this.timeEnabled,
|
||||||
|
required this.repsEnabled,
|
||||||
|
required this.scoreEnabled,
|
||||||
|
this.targetTimeSeconds,
|
||||||
|
this.targetReps,
|
||||||
|
this.targetScore,
|
||||||
|
this.restSecondsOverride,
|
||||||
|
}) : exerciseNameSnapshot = _nonBlank(
|
||||||
|
exerciseNameSnapshot,
|
||||||
|
'Exercise snapshot name',
|
||||||
|
) {
|
||||||
|
_requireNonNegative(position, 'Position');
|
||||||
|
_requirePositive(setsCount, 'Sets count');
|
||||||
|
_requireAtLeastOneMeasure(
|
||||||
|
hasTime: timeEnabled,
|
||||||
|
hasReps: repsEnabled,
|
||||||
|
hasScore: scoreEnabled,
|
||||||
|
);
|
||||||
|
if (timeEnabled && !availableTimeSnapshot) {
|
||||||
|
throw const DomainException('Time is not available on this exercise.');
|
||||||
|
}
|
||||||
|
if (repsEnabled && !availableRepsSnapshot) {
|
||||||
|
throw const DomainException('Reps are not available on this exercise.');
|
||||||
|
}
|
||||||
|
if (scoreEnabled && !availableScoreSnapshot) {
|
||||||
|
throw const DomainException('Score is not available on this exercise.');
|
||||||
|
}
|
||||||
|
_requireNullablePositive(targetTimeSeconds, 'Target time seconds');
|
||||||
|
_requireNullablePositive(targetReps, 'Target reps');
|
||||||
|
_requireNullableNonNegativeDouble(targetScore, 'Target score');
|
||||||
|
_requireNullableNonNegative(restSecondsOverride, 'Rest seconds override');
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String programId;
|
||||||
|
final String? sourceExerciseId;
|
||||||
|
final int position;
|
||||||
|
final String exerciseNameSnapshot;
|
||||||
|
final String? exerciseDescriptionSnapshot;
|
||||||
|
final String? exerciseImageMediaIdSnapshot;
|
||||||
|
final String? exerciseVideoMediaIdSnapshot;
|
||||||
|
final bool exerciseArchivedSnapshot;
|
||||||
|
final bool availableTimeSnapshot;
|
||||||
|
final bool availableRepsSnapshot;
|
||||||
|
final bool availableScoreSnapshot;
|
||||||
|
final String? scoreLabelSnapshot;
|
||||||
|
final String? scoreUnitSnapshot;
|
||||||
|
final int setsCount;
|
||||||
|
final bool timeEnabled;
|
||||||
|
final bool repsEnabled;
|
||||||
|
final bool scoreEnabled;
|
||||||
|
final int? targetTimeSeconds;
|
||||||
|
final int? targetReps;
|
||||||
|
final double? targetScore;
|
||||||
|
final int? restSecondsOverride;
|
||||||
|
|
||||||
|
static ProgramExercise snapshotFromExercise({
|
||||||
|
required EntityMetadata metadata,
|
||||||
|
required String programId,
|
||||||
|
required Exercise exercise,
|
||||||
|
required int position,
|
||||||
|
required int setsCount,
|
||||||
|
required Set<WorkoutMeasure> enabledMeasures,
|
||||||
|
int? targetTimeSeconds,
|
||||||
|
int? targetReps,
|
||||||
|
double? targetScore,
|
||||||
|
int? restSecondsOverride,
|
||||||
|
}) {
|
||||||
|
final available = exercise.availableMeasures;
|
||||||
|
if (!available.containsAll(enabledMeasures)) {
|
||||||
|
throw const DomainException(
|
||||||
|
'Enabled measures must be a subset of exercise measures.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ProgramExercise(
|
||||||
|
metadata: metadata,
|
||||||
|
programId: programId,
|
||||||
|
sourceExerciseId: exercise.metadata.id,
|
||||||
|
position: position,
|
||||||
|
exerciseNameSnapshot: exercise.name,
|
||||||
|
exerciseDescriptionSnapshot: exercise.description,
|
||||||
|
exerciseImageMediaIdSnapshot: exercise.imageMediaId,
|
||||||
|
exerciseVideoMediaIdSnapshot: exercise.videoMediaId,
|
||||||
|
exerciseArchivedSnapshot: exercise.archivedAt != null,
|
||||||
|
availableTimeSnapshot: exercise.hasTimeMeasure,
|
||||||
|
availableRepsSnapshot: exercise.hasRepsMeasure,
|
||||||
|
availableScoreSnapshot: exercise.hasScoreMeasure,
|
||||||
|
scoreLabelSnapshot: exercise.scoreLabel,
|
||||||
|
scoreUnitSnapshot: exercise.scoreUnit,
|
||||||
|
setsCount: setsCount,
|
||||||
|
timeEnabled: enabledMeasures.contains(WorkoutMeasure.time),
|
||||||
|
repsEnabled: enabledMeasures.contains(WorkoutMeasure.reps),
|
||||||
|
scoreEnabled: enabledMeasures.contains(WorkoutMeasure.score),
|
||||||
|
targetTimeSeconds: targetTimeSeconds,
|
||||||
|
targetReps: targetReps,
|
||||||
|
targetScore: targetScore,
|
||||||
|
restSecondsOverride: restSecondsOverride,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object?> toSnapshotJson() => {
|
||||||
|
'id': metadata.id,
|
||||||
|
'sourceExerciseId': sourceExerciseId,
|
||||||
|
'position': position,
|
||||||
|
'exerciseNameSnapshot': exerciseNameSnapshot,
|
||||||
|
'exerciseDescriptionSnapshot': exerciseDescriptionSnapshot,
|
||||||
|
'exerciseImageMediaIdSnapshot': exerciseImageMediaIdSnapshot,
|
||||||
|
'exerciseVideoMediaIdSnapshot': exerciseVideoMediaIdSnapshot,
|
||||||
|
'exerciseArchivedSnapshot': exerciseArchivedSnapshot,
|
||||||
|
'availableTimeSnapshot': availableTimeSnapshot,
|
||||||
|
'availableRepsSnapshot': availableRepsSnapshot,
|
||||||
|
'availableScoreSnapshot': availableScoreSnapshot,
|
||||||
|
'scoreLabelSnapshot': scoreLabelSnapshot,
|
||||||
|
'scoreUnitSnapshot': scoreUnitSnapshot,
|
||||||
|
'setsCount': setsCount,
|
||||||
|
'timeEnabled': timeEnabled,
|
||||||
|
'repsEnabled': repsEnabled,
|
||||||
|
'scoreEnabled': scoreEnabled,
|
||||||
|
'targetTimeSeconds': targetTimeSeconds,
|
||||||
|
'targetReps': targetReps,
|
||||||
|
'targetScore': targetScore,
|
||||||
|
'restSecondsOverride': restSecondsOverride,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WorkoutTemplate {
|
||||||
|
WorkoutTemplate({
|
||||||
|
required this.metadata,
|
||||||
|
required String name,
|
||||||
|
this.lastStartedAt,
|
||||||
|
this.programs = const [],
|
||||||
|
this.overrides = const [],
|
||||||
|
}) : name = _nonBlank(name, 'Workout template name');
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String name;
|
||||||
|
final DateTime? lastStartedAt;
|
||||||
|
final List<WorkoutTemplateProgram> programs;
|
||||||
|
final List<WorkoutTemplateExerciseOverride> overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WorkoutTemplateProgram {
|
||||||
|
WorkoutTemplateProgram({
|
||||||
|
required this.metadata,
|
||||||
|
required this.workoutTemplateId,
|
||||||
|
this.sourceProgramId,
|
||||||
|
required this.position,
|
||||||
|
required String programNameSnapshot,
|
||||||
|
required this.defaultRestSecondsSnapshot,
|
||||||
|
required this.programSnapshotJson,
|
||||||
|
}) : programNameSnapshot = _nonBlank(
|
||||||
|
programNameSnapshot,
|
||||||
|
'Program snapshot name',
|
||||||
|
) {
|
||||||
|
_requireNonNegative(position, 'Position');
|
||||||
|
_requireNonNegative(defaultRestSecondsSnapshot, 'Default rest seconds');
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String workoutTemplateId;
|
||||||
|
final String? sourceProgramId;
|
||||||
|
final int position;
|
||||||
|
final String programNameSnapshot;
|
||||||
|
final int defaultRestSecondsSnapshot;
|
||||||
|
final String programSnapshotJson;
|
||||||
|
|
||||||
|
static WorkoutTemplateProgram snapshotFromProgram({
|
||||||
|
required EntityMetadata metadata,
|
||||||
|
required String workoutTemplateId,
|
||||||
|
required Program program,
|
||||||
|
required int position,
|
||||||
|
}) {
|
||||||
|
return WorkoutTemplateProgram(
|
||||||
|
metadata: metadata,
|
||||||
|
workoutTemplateId: workoutTemplateId,
|
||||||
|
sourceProgramId: program.metadata.id,
|
||||||
|
position: position,
|
||||||
|
programNameSnapshot: program.name,
|
||||||
|
defaultRestSecondsSnapshot: program.defaultRestSeconds,
|
||||||
|
programSnapshotJson: jsonEncode({
|
||||||
|
'programId': program.metadata.id,
|
||||||
|
'name': program.name,
|
||||||
|
'defaultRestSeconds': program.defaultRestSeconds,
|
||||||
|
'exercises': program.exercises
|
||||||
|
.map((exercise) => exercise.toSnapshotJson())
|
||||||
|
.toList(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WorkoutTemplateExerciseOverride {
|
||||||
|
WorkoutTemplateExerciseOverride({
|
||||||
|
required this.metadata,
|
||||||
|
required this.workoutTemplateProgramId,
|
||||||
|
required String snapshotProgramExerciseId,
|
||||||
|
this.setsCountOverride,
|
||||||
|
this.targetTimeSecondsOverride,
|
||||||
|
this.targetRepsOverride,
|
||||||
|
this.targetScoreOverride,
|
||||||
|
}) : snapshotProgramExerciseId = _nonBlank(
|
||||||
|
snapshotProgramExerciseId,
|
||||||
|
'Snapshot program exercise id',
|
||||||
|
) {
|
||||||
|
_requireNullablePositive(setsCountOverride, 'Sets count override');
|
||||||
|
_requireNullablePositive(
|
||||||
|
targetTimeSecondsOverride,
|
||||||
|
'Target time seconds override',
|
||||||
|
);
|
||||||
|
_requireNullablePositive(targetRepsOverride, 'Target reps override');
|
||||||
|
_requireNullableNonNegativeDouble(
|
||||||
|
targetScoreOverride,
|
||||||
|
'Target score override',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String workoutTemplateProgramId;
|
||||||
|
final String snapshotProgramExerciseId;
|
||||||
|
final int? setsCountOverride;
|
||||||
|
final int? targetTimeSecondsOverride;
|
||||||
|
final int? targetRepsOverride;
|
||||||
|
final double? targetScoreOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ActiveWorkoutSession {
|
||||||
|
ActiveWorkoutSession({
|
||||||
|
required this.metadata,
|
||||||
|
this.sourceWorkoutTemplateId,
|
||||||
|
required this.status,
|
||||||
|
required this.startedAt,
|
||||||
|
this.pausedAt,
|
||||||
|
this.endedAt,
|
||||||
|
required this.lastPersistedAt,
|
||||||
|
required this.elapsedActiveMs,
|
||||||
|
required this.currentProgramIndex,
|
||||||
|
required this.currentExerciseIndex,
|
||||||
|
required this.currentSetIndex,
|
||||||
|
required this.resolvedTemplateSnapshotJson,
|
||||||
|
}) {
|
||||||
|
_requireNonNegative(elapsedActiveMs, 'Elapsed active milliseconds');
|
||||||
|
_requireNonNegative(currentProgramIndex, 'Current program index');
|
||||||
|
_requireNonNegative(currentExerciseIndex, 'Current exercise index');
|
||||||
|
_requireNonNegative(currentSetIndex, 'Current set index');
|
||||||
|
}
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String? sourceWorkoutTemplateId;
|
||||||
|
final ActiveWorkoutStatus status;
|
||||||
|
final DateTime startedAt;
|
||||||
|
final DateTime? pausedAt;
|
||||||
|
final DateTime? endedAt;
|
||||||
|
final DateTime lastPersistedAt;
|
||||||
|
final int elapsedActiveMs;
|
||||||
|
final int currentProgramIndex;
|
||||||
|
final int currentExerciseIndex;
|
||||||
|
final int currentSetIndex;
|
||||||
|
final String resolvedTemplateSnapshotJson;
|
||||||
|
|
||||||
|
int elapsedActiveMillisecondsAt(DateTime now) {
|
||||||
|
if (status != ActiveWorkoutStatus.running) {
|
||||||
|
return elapsedActiveMs;
|
||||||
|
}
|
||||||
|
return elapsedActiveMs + now.difference(lastPersistedAt).inMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveWorkoutSession pause(DateTime now) {
|
||||||
|
return copyWith(
|
||||||
|
status: ActiveWorkoutStatus.paused,
|
||||||
|
pausedAt: now,
|
||||||
|
lastPersistedAt: now,
|
||||||
|
elapsedActiveMs: elapsedActiveMillisecondsAt(now),
|
||||||
|
metadata: metadata.touch(now),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveWorkoutSession resume(DateTime now) {
|
||||||
|
return ActiveWorkoutSession(
|
||||||
|
metadata: metadata.touch(now),
|
||||||
|
sourceWorkoutTemplateId: sourceWorkoutTemplateId,
|
||||||
|
status: ActiveWorkoutStatus.running,
|
||||||
|
startedAt: startedAt,
|
||||||
|
pausedAt: null,
|
||||||
|
endedAt: endedAt,
|
||||||
|
lastPersistedAt: now,
|
||||||
|
elapsedActiveMs: elapsedActiveMs,
|
||||||
|
currentProgramIndex: currentProgramIndex,
|
||||||
|
currentExerciseIndex: currentExerciseIndex,
|
||||||
|
currentSetIndex: currentSetIndex,
|
||||||
|
resolvedTemplateSnapshotJson: resolvedTemplateSnapshotJson,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveWorkoutSession copyWith({
|
||||||
|
EntityMetadata? metadata,
|
||||||
|
ActiveWorkoutStatus? status,
|
||||||
|
DateTime? pausedAt,
|
||||||
|
DateTime? endedAt,
|
||||||
|
DateTime? lastPersistedAt,
|
||||||
|
int? elapsedActiveMs,
|
||||||
|
int? currentProgramIndex,
|
||||||
|
int? currentExerciseIndex,
|
||||||
|
int? currentSetIndex,
|
||||||
|
}) {
|
||||||
|
return ActiveWorkoutSession(
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
sourceWorkoutTemplateId: sourceWorkoutTemplateId,
|
||||||
|
status: status ?? this.status,
|
||||||
|
startedAt: startedAt,
|
||||||
|
pausedAt: pausedAt,
|
||||||
|
endedAt: endedAt ?? this.endedAt,
|
||||||
|
lastPersistedAt: lastPersistedAt ?? this.lastPersistedAt,
|
||||||
|
elapsedActiveMs: elapsedActiveMs ?? this.elapsedActiveMs,
|
||||||
|
currentProgramIndex: currentProgramIndex ?? this.currentProgramIndex,
|
||||||
|
currentExerciseIndex: currentExerciseIndex ?? this.currentExerciseIndex,
|
||||||
|
currentSetIndex: currentSetIndex ?? this.currentSetIndex,
|
||||||
|
resolvedTemplateSnapshotJson: resolvedTemplateSnapshotJson,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ActiveSetResult {
|
||||||
|
const ActiveSetResult({
|
||||||
|
required this.metadata,
|
||||||
|
required this.activeWorkoutSessionId,
|
||||||
|
required this.programSnapshotId,
|
||||||
|
required this.exerciseSnapshotId,
|
||||||
|
required this.programIndex,
|
||||||
|
required this.exerciseIndex,
|
||||||
|
required this.setIndex,
|
||||||
|
this.startedAt,
|
||||||
|
this.completedAt,
|
||||||
|
this.actualTimeMs,
|
||||||
|
this.actualReps,
|
||||||
|
this.actualScore,
|
||||||
|
this.scoreLabelSnapshot,
|
||||||
|
this.scoreUnitSnapshot,
|
||||||
|
this.note,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String activeWorkoutSessionId;
|
||||||
|
final String programSnapshotId;
|
||||||
|
final String exerciseSnapshotId;
|
||||||
|
final int programIndex;
|
||||||
|
final int exerciseIndex;
|
||||||
|
final int setIndex;
|
||||||
|
final DateTime? startedAt;
|
||||||
|
final DateTime? completedAt;
|
||||||
|
final int? actualTimeMs;
|
||||||
|
final int? actualReps;
|
||||||
|
final double? actualScore;
|
||||||
|
final String? scoreLabelSnapshot;
|
||||||
|
final String? scoreUnitSnapshot;
|
||||||
|
final String? note;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ActiveRestState {
|
||||||
|
const ActiveRestState({
|
||||||
|
required this.metadata,
|
||||||
|
required this.activeWorkoutSessionId,
|
||||||
|
required this.afterProgramIndex,
|
||||||
|
required this.afterExerciseIndex,
|
||||||
|
required this.afterSetIndex,
|
||||||
|
required this.plannedRestSeconds,
|
||||||
|
required this.adjustedRestSeconds,
|
||||||
|
required this.startedAt,
|
||||||
|
this.endedAt,
|
||||||
|
this.skippedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String activeWorkoutSessionId;
|
||||||
|
final int afterProgramIndex;
|
||||||
|
final int afterExerciseIndex;
|
||||||
|
final int afterSetIndex;
|
||||||
|
final int plannedRestSeconds;
|
||||||
|
final int adjustedRestSeconds;
|
||||||
|
final DateTime startedAt;
|
||||||
|
final DateTime? endedAt;
|
||||||
|
final DateTime? skippedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WorkoutHistory {
|
||||||
|
const WorkoutHistory({
|
||||||
|
required this.metadata,
|
||||||
|
this.sourceWorkoutTemplateId,
|
||||||
|
this.sourceActiveWorkoutSessionId,
|
||||||
|
required this.nameSnapshot,
|
||||||
|
required this.startedAt,
|
||||||
|
required this.endedAt,
|
||||||
|
required this.totalActiveMs,
|
||||||
|
required this.completed,
|
||||||
|
required this.historySnapshotJson,
|
||||||
|
this.results = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String? sourceWorkoutTemplateId;
|
||||||
|
final String? sourceActiveWorkoutSessionId;
|
||||||
|
final String nameSnapshot;
|
||||||
|
final DateTime startedAt;
|
||||||
|
final DateTime endedAt;
|
||||||
|
final int totalActiveMs;
|
||||||
|
final bool completed;
|
||||||
|
final String historySnapshotJson;
|
||||||
|
final List<WorkoutHistorySetResult> results;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WorkoutHistorySetResult {
|
||||||
|
const WorkoutHistorySetResult({
|
||||||
|
required this.metadata,
|
||||||
|
required this.workoutHistoryId,
|
||||||
|
required this.programSnapshotId,
|
||||||
|
required this.exerciseSnapshotId,
|
||||||
|
required this.programIndex,
|
||||||
|
required this.exerciseIndex,
|
||||||
|
required this.setIndex,
|
||||||
|
required this.programNameSnapshot,
|
||||||
|
required this.exerciseNameSnapshot,
|
||||||
|
required this.timeEnabledSnapshot,
|
||||||
|
required this.repsEnabledSnapshot,
|
||||||
|
required this.scoreEnabledSnapshot,
|
||||||
|
this.targetTimeSecondsSnapshot,
|
||||||
|
this.targetRepsSnapshot,
|
||||||
|
this.targetScoreSnapshot,
|
||||||
|
this.actualTimeMs,
|
||||||
|
this.actualReps,
|
||||||
|
this.actualScore,
|
||||||
|
this.scoreLabelSnapshot,
|
||||||
|
this.scoreUnitSnapshot,
|
||||||
|
this.startedAt,
|
||||||
|
this.completedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EntityMetadata metadata;
|
||||||
|
final String workoutHistoryId;
|
||||||
|
final String programSnapshotId;
|
||||||
|
final String exerciseSnapshotId;
|
||||||
|
final int programIndex;
|
||||||
|
final int exerciseIndex;
|
||||||
|
final int setIndex;
|
||||||
|
final String programNameSnapshot;
|
||||||
|
final String exerciseNameSnapshot;
|
||||||
|
final bool timeEnabledSnapshot;
|
||||||
|
final bool repsEnabledSnapshot;
|
||||||
|
final bool scoreEnabledSnapshot;
|
||||||
|
final int? targetTimeSecondsSnapshot;
|
||||||
|
final int? targetRepsSnapshot;
|
||||||
|
final double? targetScoreSnapshot;
|
||||||
|
final int? actualTimeMs;
|
||||||
|
final int? actualReps;
|
||||||
|
final double? actualScore;
|
||||||
|
final String? scoreLabelSnapshot;
|
||||||
|
final String? scoreUnitSnapshot;
|
||||||
|
final DateTime? startedAt;
|
||||||
|
final DateTime? completedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _nonBlank(String? value, String label) {
|
||||||
|
final trimmed = value?.trim();
|
||||||
|
if (trimmed == null || trimmed.isEmpty) {
|
||||||
|
throw DomainException('$label must not be blank.');
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requireAtLeastOneMeasure({
|
||||||
|
required bool hasTime,
|
||||||
|
required bool hasReps,
|
||||||
|
required bool hasScore,
|
||||||
|
}) {
|
||||||
|
if (!hasTime && !hasReps && !hasScore) {
|
||||||
|
throw const DomainException('At least one measure must be active.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requirePositive(int value, String label) {
|
||||||
|
if (value <= 0) {
|
||||||
|
throw DomainException('$label must be positive.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requireNonNegative(int value, String label) {
|
||||||
|
if (value < 0) {
|
||||||
|
throw DomainException('$label must not be negative.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requireNullablePositive(int? value, String label) {
|
||||||
|
if (value != null) {
|
||||||
|
_requirePositive(value, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requireNullableNonNegative(int? value, String label) {
|
||||||
|
if (value != null) {
|
||||||
|
_requireNonNegative(value, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _requireNullableNonNegativeDouble(double? value, String label) {
|
||||||
|
if (value != null && value < 0) {
|
||||||
|
throw DomainException('$label must not be negative.');
|
||||||
|
}
|
||||||
|
}
|
||||||
5
lib/infrastructure/infrastructure.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/// Infrastructure layer entry point.
|
||||||
|
///
|
||||||
|
/// Concrete adapters live here and depend inward on application/domain
|
||||||
|
/// contracts.
|
||||||
|
library;
|
||||||
132
lib/infrastructure/local/app_database.dart
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
|
|
||||||
|
import 'tables.dart';
|
||||||
|
|
||||||
|
part 'app_database.g.dart';
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
tables: [
|
||||||
|
ActiveRestStates,
|
||||||
|
ActiveSetResults,
|
||||||
|
ActiveWorkoutSessions,
|
||||||
|
ChangeLogEntries,
|
||||||
|
Exercises,
|
||||||
|
MediaAssets,
|
||||||
|
ProgramExercises,
|
||||||
|
Programs,
|
||||||
|
WorkoutHistories,
|
||||||
|
WorkoutHistorySetResults,
|
||||||
|
WorkoutTemplateExerciseOverrides,
|
||||||
|
WorkoutTemplatePrograms,
|
||||||
|
WorkoutTemplates,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
final class AppDatabase extends _$AppDatabase {
|
||||||
|
AppDatabase(super.executor);
|
||||||
|
|
||||||
|
factory AppDatabase.open() {
|
||||||
|
return AppDatabase(
|
||||||
|
driftDatabase(
|
||||||
|
name: 'gametime',
|
||||||
|
native: const DriftNativeOptions(shareAcrossIsolates: true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration {
|
||||||
|
return MigrationStrategy(
|
||||||
|
onCreate: (migrator) async {
|
||||||
|
await migrator.createAll();
|
||||||
|
await _createIndexes();
|
||||||
|
},
|
||||||
|
beforeOpen: (details) async {
|
||||||
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createIndexes() async {
|
||||||
|
for (final tableName in _syncableTableNames) {
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_${tableName}_deleted_at '
|
||||||
|
'ON $tableName (deleted_at)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_${tableName}_updated_at '
|
||||||
|
'ON $tableName (updated_at)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_${tableName}_sync_state '
|
||||||
|
'ON $tableName (sync_state)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_${tableName}_local_revision '
|
||||||
|
'ON $tableName (local_revision)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_program_exercises_program_id '
|
||||||
|
'ON program_exercises (program_id)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_workout_template_programs_template_id '
|
||||||
|
'ON workout_template_programs (workout_template_id)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_active_set_results_session_id '
|
||||||
|
'ON active_set_results (active_workout_session_id)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_active_rest_states_session_id '
|
||||||
|
'ON active_rest_states (active_workout_session_id)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS '
|
||||||
|
'idx_active_workout_sessions_single_open '
|
||||||
|
'ON active_workout_sessions ((1)) '
|
||||||
|
'WHERE deleted_at IS NULL '
|
||||||
|
"AND status IN ('running', 'paused', 'savedExit')",
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_workout_history_started_at '
|
||||||
|
'ON workout_history (started_at)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_workout_history_set_results_history_id '
|
||||||
|
'ON workout_history_set_results (workout_history_id)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_change_log_entity '
|
||||||
|
'ON change_log (entity_type, entity_id)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_change_log_local_revision '
|
||||||
|
'ON change_log (local_revision)',
|
||||||
|
);
|
||||||
|
await customStatement(
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_change_log_synced_at '
|
||||||
|
'ON change_log (synced_at)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _syncableTableNames = [
|
||||||
|
'active_rest_states',
|
||||||
|
'active_set_results',
|
||||||
|
'active_workout_sessions',
|
||||||
|
'exercises',
|
||||||
|
'media_assets',
|
||||||
|
'program_exercises',
|
||||||
|
'programs',
|
||||||
|
'workout_history',
|
||||||
|
'workout_history_set_results',
|
||||||
|
'workout_template_exercise_overrides',
|
||||||
|
'workout_template_programs',
|
||||||
|
'workout_templates',
|
||||||
|
];
|
||||||
25878
lib/infrastructure/local/app_database.g.dart
Normal file
1145
lib/infrastructure/local/drift_repositories.dart
Normal file
4
lib/infrastructure/local/local.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export 'app_database.dart';
|
||||||
|
export 'drift_repositories.dart';
|
||||||
|
export 'local_id.dart';
|
||||||
|
export 'local_media_storage.dart';
|
||||||
33
lib/infrastructure/local/local_id.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import '../../application/ports.dart';
|
||||||
|
|
||||||
|
const _crockfordBase32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
||||||
|
|
||||||
|
final class LocalIdGenerator implements IdGenerator {
|
||||||
|
LocalIdGenerator({Random? random}) : _random = random ?? Random.secure();
|
||||||
|
|
||||||
|
final Random _random;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String newId() => newUlid();
|
||||||
|
|
||||||
|
String newUlid({DateTime? now}) {
|
||||||
|
final timestamp = (now ?? DateTime.now().toUtc()).millisecondsSinceEpoch;
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
|
var remainingTimestamp = timestamp;
|
||||||
|
final timestampChars = List<String>.filled(10, '0');
|
||||||
|
for (var index = 9; index >= 0; index--) {
|
||||||
|
timestampChars[index] = _crockfordBase32[remainingTimestamp & 0x1F];
|
||||||
|
remainingTimestamp >>= 5;
|
||||||
|
}
|
||||||
|
buffer.writeAll(timestampChars);
|
||||||
|
|
||||||
|
for (var index = 0; index < 16; index++) {
|
||||||
|
buffer.write(_crockfordBase32[_random.nextInt(32)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||