From aac3a729a282c74c4491c4266ad03b22ca450b0a Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Fri, 12 Jun 2026 12:12:01 +0200 Subject: [PATCH] Initialise l'app Flutter mobile : BDD Drift, onboarding sans compte et tableau de bord. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 du MVP : schéma SQLite porté depuis le prototype web, seed des règles/récompenses, flux d'onboarding en 4 étapes et écran d'accueil minimal. Co-authored-by: Cursor --- mobile/.gitignore | 45 + mobile/.metadata | 30 + mobile/README.md | 55 +- mobile/analysis_options.yaml | 28 + mobile/android/.gitignore | 14 + mobile/android/app/build.gradle.kts | 45 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 + .../bonpoint/bonpoint/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + mobile/android/build.gradle.kts | 24 + mobile/android/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.properties | 5 + mobile/android/settings.gradle.kts | 26 + mobile/lib/app.dart | 71 + mobile/lib/core/theme/app_theme.dart | 73 + mobile/lib/data/database/app_database.dart | 178 + mobile/lib/data/database/app_database.g.dart | 3692 +++++++++++++++++ mobile/lib/data/database/tables.dart | 54 + .../repositories/onboarding_repository.dart | 29 + mobile/lib/data/seed/default_rewards.dart | 31 + mobile/lib/data/seed/default_rules.dart | 75 + .../features/dashboard/dashboard_screen.dart | 125 + .../features/onboarding/onboarding_flow.dart | 129 + .../features/onboarding/onboarding_state.dart | 49 + .../features/onboarding/steps/child_step.dart | 137 + .../features/onboarding/steps/pin_step.dart | 115 + .../features/onboarding/steps/rules_step.dart | 117 + .../onboarding/steps/welcome_step.dart | 76 + .../widgets/onboarding_scaffold.dart | 88 + mobile/lib/main.dart | 13 + mobile/lib/providers/database_provider.dart | 24 + mobile/pubspec.lock | 741 ++++ mobile/pubspec.yaml | 30 + mobile/test/score_logic_test.dart | 60 + mobile/test/widget_test.dart | 23 + 45 files changed, 6328 insertions(+), 4 deletions(-) create mode 100644 mobile/.gitignore create mode 100644 mobile/.metadata create mode 100644 mobile/analysis_options.yaml create mode 100644 mobile/android/.gitignore create mode 100644 mobile/android/app/build.gradle.kts create mode 100644 mobile/android/app/src/debug/AndroidManifest.xml create mode 100644 mobile/android/app/src/main/AndroidManifest.xml create mode 100644 mobile/android/app/src/main/kotlin/fr/ptitspas/bonpoint/bonpoint/MainActivity.kt create mode 100644 mobile/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 mobile/android/app/src/main/res/drawable/launch_background.xml create mode 100644 mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/values-night/styles.xml create mode 100644 mobile/android/app/src/main/res/values/styles.xml create mode 100644 mobile/android/app/src/profile/AndroidManifest.xml create mode 100644 mobile/android/build.gradle.kts create mode 100644 mobile/android/gradle.properties create mode 100644 mobile/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 mobile/android/settings.gradle.kts create mode 100644 mobile/lib/app.dart create mode 100644 mobile/lib/core/theme/app_theme.dart create mode 100644 mobile/lib/data/database/app_database.dart create mode 100644 mobile/lib/data/database/app_database.g.dart create mode 100644 mobile/lib/data/database/tables.dart create mode 100644 mobile/lib/data/repositories/onboarding_repository.dart create mode 100644 mobile/lib/data/seed/default_rewards.dart create mode 100644 mobile/lib/data/seed/default_rules.dart create mode 100644 mobile/lib/features/dashboard/dashboard_screen.dart create mode 100644 mobile/lib/features/onboarding/onboarding_flow.dart create mode 100644 mobile/lib/features/onboarding/onboarding_state.dart create mode 100644 mobile/lib/features/onboarding/steps/child_step.dart create mode 100644 mobile/lib/features/onboarding/steps/pin_step.dart create mode 100644 mobile/lib/features/onboarding/steps/rules_step.dart create mode 100644 mobile/lib/features/onboarding/steps/welcome_step.dart create mode 100644 mobile/lib/features/onboarding/widgets/onboarding_scaffold.dart create mode 100644 mobile/lib/main.dart create mode 100644 mobile/lib/providers/database_provider.dart create mode 100644 mobile/pubspec.lock create mode 100644 mobile/pubspec.yaml create mode 100644 mobile/test/score_logic_test.dart create mode 100644 mobile/test/widget_test.dart diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/mobile/.metadata b/mobile/.metadata new file mode 100644 index 0000000..fdcdbd8 --- /dev/null +++ b/mobile/.metadata @@ -0,0 +1,30 @@ +# 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: "c9a6c484230f8b5e408ec57be1ef71dee1e77020" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + - platform: android + create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020 + + # 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' diff --git a/mobile/README.md b/mobile/README.md index 7090e52..5221d2e 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -1,7 +1,54 @@ -# Application mobile Android +# Bons Points — Application Android (Flutter) -Emplacement prévu pour l'app **Flutter** (Bons Points — produit commercial Play Store). +App familiale de bons points, **100 % locale** (SQLite, pas de compte cloud). -Voir le cahier des charges : [../docs/CAHIER_DES_CHARGES_APP_ANDROID.md](../docs/CAHIER_DES_CHARGES_APP_ANDROID.md). +## Stack (choix par défaut — voir CDC §14) -**Statut :** non initialisé — à développer dans un workspace Cursor dédié. +| Couche | Choix | +|--------|--------| +| UI | Flutter + Material 3 | +| État | **Riverpod** | +| BDD | **Drift** (SQLite) | +| PIN | bcrypt | + +## Démarrage + +```bash +cd mobile +flutter pub get +dart run build_runner build # après modification des tables Drift +flutter run +``` + +## Structure + +``` +lib/ +├── main.dart / app.dart +├── core/theme/ # charte visuelle (prototype web) +├── data/ +│ ├── database/ # schéma SQLite (port de web/src/db.js) +│ ├── seed/ # règles & récompenses par défaut +│ └── repositories/ +├── providers/ +└── features/ + ├── onboarding/ # bienvenue → enfant → PIN → règles + └── dashboard/ # tableau de bord MVP +``` + +## État actuel (phase 1) + +- [x] Projet Flutter (`fr.ptitspas.bonpoint`, Android seul) +- [x] Schéma SQLite + seed règles/récompenses +- [x] Logique score (plancher 0, delta réel) +- [x] Onboarding sans compte (4 étapes) +- [x] Tableau de bord minimal +- [ ] Espace parent (PIN, grille règles) +- [ ] Fiche enfant + boutique +- [ ] Export/import, IAP, AdMob + +## Tests + +```bash +flutter test +``` diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/mobile/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobile/android/.gitignore b/mobile/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/mobile/android/.gitignore @@ -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 diff --git a/mobile/android/app/build.gradle.kts b/mobile/android/app/build.gradle.kts new file mode 100644 index 0000000..1466d79 --- /dev/null +++ b/mobile/android/app/build.gradle.kts @@ -0,0 +1,45 @@ +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 = "fr.ptitspas.bonpoint.bonpoint" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "fr.ptitspas.bonpoint.bonpoint" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..06c1e3a --- /dev/null +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/fr/ptitspas/bonpoint/bonpoint/MainActivity.kt b/mobile/android/app/src/main/kotlin/fr/ptitspas/bonpoint/bonpoint/MainActivity.kt new file mode 100644 index 0000000..82756e3 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/fr/ptitspas/bonpoint/bonpoint/MainActivity.kt @@ -0,0 +1,5 @@ +package fr.ptitspas.bonpoint.bonpoint + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/mobile/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/mobile/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/android/build.gradle.kts b/mobile/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/mobile/android/build.gradle.kts @@ -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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/mobile/android/gradle.properties @@ -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 diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/mobile/android/settings.gradle.kts b/mobile/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/mobile/android/settings.gradle.kts @@ -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") diff --git a/mobile/lib/app.dart b/mobile/lib/app.dart new file mode 100644 index 0000000..3ca3eb8 --- /dev/null +++ b/mobile/lib/app.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'core/theme/app_theme.dart'; +import 'features/dashboard/dashboard_screen.dart'; +import 'features/onboarding/onboarding_flow.dart'; +import 'providers/database_provider.dart'; + +class BonpointApp extends ConsumerStatefulWidget { + const BonpointApp({super.key}); + + @override + ConsumerState createState() => _BonpointAppState(); +} + +class _BonpointAppState extends ConsumerState { + @override + void initState() { + super.initState(); + Future.microtask(() => ref.read(databaseProvider).initialize()); + } + + @override + Widget build(BuildContext context) { + final onboardingAsync = ref.watch(onboardingCompleteProvider); + + return MaterialApp( + title: 'Bons Points', + debugShowCheckedModeBanner: false, + theme: AppTheme.light(), + home: onboardingAsync.when( + loading: () => const _SplashScreen(), + error: (_, _) => const OnboardingFlow(), + data: (complete) => + complete ? const DashboardScreen() : const OnboardingFlow(), + ), + ); + } +} + +class _SplashScreen extends StatelessWidget { + const _SplashScreen(); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: const BoxDecoration(gradient: AppTheme.primaryGradient), + child: Scaffold( + backgroundColor: Colors.transparent, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('⭐', style: TextStyle(fontSize: 64)), + const SizedBox(height: 16), + Text( + 'Bons Points', + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 32), + const CircularProgressIndicator(color: Colors.white), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/core/theme/app_theme.dart b/mobile/lib/core/theme/app_theme.dart new file mode 100644 index 0000000..eaa314d --- /dev/null +++ b/mobile/lib/core/theme/app_theme.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +/// Charte inspirée du prototype web (dégradé violet, cartes blanches). +abstract final class AppTheme { + static const primaryGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF667EEA), Color(0xFF764BA2)], + ); + + static const childColors = [ + '#6C63FF', + '#E91E8C', + '#2196F3', + '#FF9800', + '#4CAF50', + '#9C27B0', + '#00BCD4', + '#F44336', + ]; + + static Color parseColor(String hex) { + final value = hex.replaceFirst('#', ''); + return Color(int.parse('FF$value', radix: 16)); + } + + static ThemeData light() { + final seed = parseColor('#667EEA'); + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: seed, + brightness: Brightness.light, + ), + scaffoldBackgroundColor: seed, + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + elevation: 0, + centerTitle: true, + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16)), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.3)), + ), + ), + cardTheme: CardThemeData( + color: Colors.white, + elevation: 4, + shadowColor: Colors.black26, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: seed, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + textStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom(foregroundColor: Colors.white), + ), + ); + } +} diff --git a/mobile/lib/data/database/app_database.dart b/mobile/lib/data/database/app_database.dart new file mode 100644 index 0000000..fbed6a5 --- /dev/null +++ b/mobile/lib/data/database/app_database.dart @@ -0,0 +1,178 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +import '../seed/default_rewards.dart'; +import '../seed/default_rules.dart'; +import 'tables.dart'; + +part 'app_database.g.dart'; + +@DriftDatabase(tables: [Enfants, Regles, Mouvements, Recompenses, Config]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + AppDatabase.forTesting(super.executor); + + @override + int get schemaVersion => 1; + + @override + MigrationStrategy get migration => MigrationStrategy( + onCreate: (m) async { + await m.createAll(); + }, + onUpgrade: (m, from, to) async {}, + ); + + /// Met à jour les packs par défaut si l'utilisateur les a déjà importés. + Future initialize() async { + if (await getConfig('regles_version') != null) { + await _syncDefaultRules(); + } + if (await getConfig('recompenses_version') != null) { + await _syncDefaultRewards(); + } + } + + Future importDefaultPack() async { + await _syncDefaultRules(); + await _syncDefaultRewards(); + } + + Future _syncDefaultRules() async { + final version = await getConfig('regles_version'); + if (version == rulesVersion) return; + + await (update(regles)..where((r) => r.actif.equals(true))) + .write(const ReglesCompanion(actif: Value(false))); + + for (final rule in defaultRules) { + await into(regles).insert( + ReglesCompanion.insert( + libelle: rule.libelle, + points: rule.points, + icone: Value(rule.icone), + ordre: Value(rule.ordre), + famille: Value(rule.famille), + familleOrdre: Value(rule.familleOrdre), + ), + ); + } + + await setConfig('regles_version', rulesVersion); + } + + Future _syncDefaultRewards() async { + final version = await getConfig('recompenses_version'); + if (version == rewardsVersion) return; + + await (update(recompenses)..where((r) => r.actif.equals(true))) + .write(const RecompensesCompanion(actif: Value(false))); + + for (final reward in defaultRewards) { + await into(recompenses).insert( + RecompensesCompanion.insert( + libelle: reward.libelle, + coutPoints: reward.coutPoints, + icone: Value(reward.icone), + ordre: Value(reward.ordre), + ), + ); + } + + await setConfig('recompenses_version', rewardsVersion); + } + + Future getConfig(String key) async { + final row = await (select(config)..where((c) => c.cle.equals(key))) + .getSingleOrNull(); + return row?.valeur; + } + + Future setConfig(String key, String value) async { + await into(config).insertOnConflictUpdate( + ConfigCompanion.insert(cle: key, valeur: value), + ); + } + + Future isOnboardingComplete() async { + return await getConfig('onboarding_complete') == 'true'; + } + + Future completeOnboarding() async { + await setConfig('onboarding_complete', 'true'); + } + + Future> listChildren() { + return (select(enfants)..orderBy([(e) => OrderingTerm.asc(e.ordre)])).get(); + } + + Future countChildren() async { + final count = await enfants.count().getSingle(); + return count; + } + + Future insertChild({ + required String prenom, + required String couleur, + String? dateNaissance, + }) { + return into(enfants).insert( + EnfantsCompanion.insert( + prenom: prenom, + couleur: Value(couleur), + dateNaissance: Value(dateNaissance), + ordre: const Value(1), + ), + ); + } + + Future setPinHash(String hash) async { + await setConfig('pin_hash', hash); + } + + Future getPinHash() => getConfig('pin_hash'); + + /// Applique une règle : plancher score à 0, delta réel enregistré. + Future applyRule(int childId, int ruleId, {String? note}) async { + return transaction(() async { + final child = await (select(enfants)..where((e) => e.id.equals(childId))) + .getSingleOrNull(); + final rule = await (select(regles) + ..where((r) => r.id.equals(ruleId) & r.actif.equals(true))) + .getSingleOrNull(); + + if (child == null || rule == null) return null; + + final newScore = (child.score + rule.points).clamp(0, 1 << 30); + final realDelta = newScore - child.score; + + await into(mouvements).insert( + MouvementsCompanion.insert( + enfantId: childId, + regleId: Value(ruleId), + delta: realDelta, + note: Value(note), + ), + ); + + await (update(enfants)..where((e) => e.id.equals(childId))) + .write(EnfantsCompanion(score: Value(newScore))); + + return (select(enfants)..where((e) => e.id.equals(childId))) + .getSingleOrNull(); + }); + } +} + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dir = await getApplicationDocumentsDirectory(); + final file = File(p.join(dir.path, 'bonpoint.db')); + return NativeDatabase.createInBackground(file); + }); +} diff --git a/mobile/lib/data/database/app_database.g.dart b/mobile/lib/data/database/app_database.g.dart new file mode 100644 index 0000000..ba56db4 --- /dev/null +++ b/mobile/lib/data/database/app_database.g.dart @@ -0,0 +1,3692 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_database.dart'; + +// ignore_for_file: type=lint +class $EnfantsTable extends Enfants with TableInfo<$EnfantsTable, Enfant> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $EnfantsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _prenomMeta = const VerificationMeta('prenom'); + @override + late final GeneratedColumn prenom = GeneratedColumn( + 'prenom', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _dateNaissanceMeta = const VerificationMeta( + 'dateNaissance', + ); + @override + late final GeneratedColumn dateNaissance = GeneratedColumn( + 'date_naissance', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _scoreMeta = const VerificationMeta('score'); + @override + late final GeneratedColumn score = GeneratedColumn( + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _couleurMeta = const VerificationMeta( + 'couleur', + ); + @override + late final GeneratedColumn couleur = GeneratedColumn( + 'couleur', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('#6C63FF'), + ); + static const VerificationMeta _ordreMeta = const VerificationMeta('ordre'); + @override + late final GeneratedColumn ordre = GeneratedColumn( + 'ordre', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + List get $columns => [ + id, + prenom, + dateNaissance, + score, + couleur, + ordre, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'enfants'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('prenom')) { + context.handle( + _prenomMeta, + prenom.isAcceptableOrUnknown(data['prenom']!, _prenomMeta), + ); + } else if (isInserting) { + context.missing(_prenomMeta); + } + if (data.containsKey('date_naissance')) { + context.handle( + _dateNaissanceMeta, + dateNaissance.isAcceptableOrUnknown( + data['date_naissance']!, + _dateNaissanceMeta, + ), + ); + } + if (data.containsKey('score')) { + context.handle( + _scoreMeta, + score.isAcceptableOrUnknown(data['score']!, _scoreMeta), + ); + } + if (data.containsKey('couleur')) { + context.handle( + _couleurMeta, + couleur.isAcceptableOrUnknown(data['couleur']!, _couleurMeta), + ); + } + if (data.containsKey('ordre')) { + context.handle( + _ordreMeta, + ordre.isAcceptableOrUnknown(data['ordre']!, _ordreMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + List> get uniqueKeys => [ + {prenom}, + ]; + @override + Enfant map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Enfant( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + prenom: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}prenom'], + )!, + dateNaissance: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}date_naissance'], + ), + score: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}score'], + )!, + couleur: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}couleur'], + )!, + ordre: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}ordre'], + )!, + ); + } + + @override + $EnfantsTable createAlias(String alias) { + return $EnfantsTable(attachedDatabase, alias); + } +} + +class Enfant extends DataClass implements Insertable { + final int id; + final String prenom; + final String? dateNaissance; + final int score; + final String couleur; + final int ordre; + const Enfant({ + required this.id, + required this.prenom, + this.dateNaissance, + required this.score, + required this.couleur, + required this.ordre, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['prenom'] = Variable(prenom); + if (!nullToAbsent || dateNaissance != null) { + map['date_naissance'] = Variable(dateNaissance); + } + map['score'] = Variable(score); + map['couleur'] = Variable(couleur); + map['ordre'] = Variable(ordre); + return map; + } + + EnfantsCompanion toCompanion(bool nullToAbsent) { + return EnfantsCompanion( + id: Value(id), + prenom: Value(prenom), + dateNaissance: dateNaissance == null && nullToAbsent + ? const Value.absent() + : Value(dateNaissance), + score: Value(score), + couleur: Value(couleur), + ordre: Value(ordre), + ); + } + + factory Enfant.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Enfant( + id: serializer.fromJson(json['id']), + prenom: serializer.fromJson(json['prenom']), + dateNaissance: serializer.fromJson(json['dateNaissance']), + score: serializer.fromJson(json['score']), + couleur: serializer.fromJson(json['couleur']), + ordre: serializer.fromJson(json['ordre']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'prenom': serializer.toJson(prenom), + 'dateNaissance': serializer.toJson(dateNaissance), + 'score': serializer.toJson(score), + 'couleur': serializer.toJson(couleur), + 'ordre': serializer.toJson(ordre), + }; + } + + Enfant copyWith({ + int? id, + String? prenom, + Value dateNaissance = const Value.absent(), + int? score, + String? couleur, + int? ordre, + }) => Enfant( + id: id ?? this.id, + prenom: prenom ?? this.prenom, + dateNaissance: dateNaissance.present + ? dateNaissance.value + : this.dateNaissance, + score: score ?? this.score, + couleur: couleur ?? this.couleur, + ordre: ordre ?? this.ordre, + ); + Enfant copyWithCompanion(EnfantsCompanion data) { + return Enfant( + id: data.id.present ? data.id.value : this.id, + prenom: data.prenom.present ? data.prenom.value : this.prenom, + dateNaissance: data.dateNaissance.present + ? data.dateNaissance.value + : this.dateNaissance, + score: data.score.present ? data.score.value : this.score, + couleur: data.couleur.present ? data.couleur.value : this.couleur, + ordre: data.ordre.present ? data.ordre.value : this.ordre, + ); + } + + @override + String toString() { + return (StringBuffer('Enfant(') + ..write('id: $id, ') + ..write('prenom: $prenom, ') + ..write('dateNaissance: $dateNaissance, ') + ..write('score: $score, ') + ..write('couleur: $couleur, ') + ..write('ordre: $ordre') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, prenom, dateNaissance, score, couleur, ordre); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Enfant && + other.id == this.id && + other.prenom == this.prenom && + other.dateNaissance == this.dateNaissance && + other.score == this.score && + other.couleur == this.couleur && + other.ordre == this.ordre); +} + +class EnfantsCompanion extends UpdateCompanion { + final Value id; + final Value prenom; + final Value dateNaissance; + final Value score; + final Value couleur; + final Value ordre; + const EnfantsCompanion({ + this.id = const Value.absent(), + this.prenom = const Value.absent(), + this.dateNaissance = const Value.absent(), + this.score = const Value.absent(), + this.couleur = const Value.absent(), + this.ordre = const Value.absent(), + }); + EnfantsCompanion.insert({ + this.id = const Value.absent(), + required String prenom, + this.dateNaissance = const Value.absent(), + this.score = const Value.absent(), + this.couleur = const Value.absent(), + this.ordre = const Value.absent(), + }) : prenom = Value(prenom); + static Insertable custom({ + Expression? id, + Expression? prenom, + Expression? dateNaissance, + Expression? score, + Expression? couleur, + Expression? ordre, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (prenom != null) 'prenom': prenom, + if (dateNaissance != null) 'date_naissance': dateNaissance, + if (score != null) 'score': score, + if (couleur != null) 'couleur': couleur, + if (ordre != null) 'ordre': ordre, + }); + } + + EnfantsCompanion copyWith({ + Value? id, + Value? prenom, + Value? dateNaissance, + Value? score, + Value? couleur, + Value? ordre, + }) { + return EnfantsCompanion( + id: id ?? this.id, + prenom: prenom ?? this.prenom, + dateNaissance: dateNaissance ?? this.dateNaissance, + score: score ?? this.score, + couleur: couleur ?? this.couleur, + ordre: ordre ?? this.ordre, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (prenom.present) { + map['prenom'] = Variable(prenom.value); + } + if (dateNaissance.present) { + map['date_naissance'] = Variable(dateNaissance.value); + } + if (score.present) { + map['score'] = Variable(score.value); + } + if (couleur.present) { + map['couleur'] = Variable(couleur.value); + } + if (ordre.present) { + map['ordre'] = Variable(ordre.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('EnfantsCompanion(') + ..write('id: $id, ') + ..write('prenom: $prenom, ') + ..write('dateNaissance: $dateNaissance, ') + ..write('score: $score, ') + ..write('couleur: $couleur, ') + ..write('ordre: $ordre') + ..write(')')) + .toString(); + } +} + +class $ReglesTable extends Regles with TableInfo<$ReglesTable, Regle> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ReglesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _libelleMeta = const VerificationMeta( + 'libelle', + ); + @override + late final GeneratedColumn libelle = GeneratedColumn( + 'libelle', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _pointsMeta = const VerificationMeta('points'); + @override + late final GeneratedColumn points = GeneratedColumn( + 'points', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _iconeMeta = const VerificationMeta('icone'); + @override + late final GeneratedColumn icone = GeneratedColumn( + 'icone', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('⭐'), + ); + static const VerificationMeta _actifMeta = const VerificationMeta('actif'); + @override + late final GeneratedColumn actif = GeneratedColumn( + 'actif', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("actif" IN (0, 1))', + ), + defaultValue: const Constant(true), + ); + static const VerificationMeta _ordreMeta = const VerificationMeta('ordre'); + @override + late final GeneratedColumn ordre = GeneratedColumn( + 'ordre', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + static const VerificationMeta _familleMeta = const VerificationMeta( + 'famille', + ); + @override + late final GeneratedColumn famille = GeneratedColumn( + 'famille', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('📋 Autre'), + ); + static const VerificationMeta _familleOrdreMeta = const VerificationMeta( + 'familleOrdre', + ); + @override + late final GeneratedColumn familleOrdre = GeneratedColumn( + 'famille_ordre', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(99), + ); + @override + List get $columns => [ + id, + libelle, + points, + icone, + actif, + ordre, + famille, + familleOrdre, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'regles'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('libelle')) { + context.handle( + _libelleMeta, + libelle.isAcceptableOrUnknown(data['libelle']!, _libelleMeta), + ); + } else if (isInserting) { + context.missing(_libelleMeta); + } + if (data.containsKey('points')) { + context.handle( + _pointsMeta, + points.isAcceptableOrUnknown(data['points']!, _pointsMeta), + ); + } else if (isInserting) { + context.missing(_pointsMeta); + } + if (data.containsKey('icone')) { + context.handle( + _iconeMeta, + icone.isAcceptableOrUnknown(data['icone']!, _iconeMeta), + ); + } + if (data.containsKey('actif')) { + context.handle( + _actifMeta, + actif.isAcceptableOrUnknown(data['actif']!, _actifMeta), + ); + } + if (data.containsKey('ordre')) { + context.handle( + _ordreMeta, + ordre.isAcceptableOrUnknown(data['ordre']!, _ordreMeta), + ); + } + if (data.containsKey('famille')) { + context.handle( + _familleMeta, + famille.isAcceptableOrUnknown(data['famille']!, _familleMeta), + ); + } + if (data.containsKey('famille_ordre')) { + context.handle( + _familleOrdreMeta, + familleOrdre.isAcceptableOrUnknown( + data['famille_ordre']!, + _familleOrdreMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Regle map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Regle( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + libelle: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}libelle'], + )!, + points: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}points'], + )!, + icone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}icone'], + )!, + actif: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}actif'], + )!, + ordre: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}ordre'], + )!, + famille: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}famille'], + )!, + familleOrdre: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}famille_ordre'], + )!, + ); + } + + @override + $ReglesTable createAlias(String alias) { + return $ReglesTable(attachedDatabase, alias); + } +} + +class Regle extends DataClass implements Insertable { + final int id; + final String libelle; + final int points; + final String icone; + final bool actif; + final int ordre; + final String famille; + final int familleOrdre; + const Regle({ + required this.id, + required this.libelle, + required this.points, + required this.icone, + required this.actif, + required this.ordre, + required this.famille, + required this.familleOrdre, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['libelle'] = Variable(libelle); + map['points'] = Variable(points); + map['icone'] = Variable(icone); + map['actif'] = Variable(actif); + map['ordre'] = Variable(ordre); + map['famille'] = Variable(famille); + map['famille_ordre'] = Variable(familleOrdre); + return map; + } + + ReglesCompanion toCompanion(bool nullToAbsent) { + return ReglesCompanion( + id: Value(id), + libelle: Value(libelle), + points: Value(points), + icone: Value(icone), + actif: Value(actif), + ordre: Value(ordre), + famille: Value(famille), + familleOrdre: Value(familleOrdre), + ); + } + + factory Regle.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Regle( + id: serializer.fromJson(json['id']), + libelle: serializer.fromJson(json['libelle']), + points: serializer.fromJson(json['points']), + icone: serializer.fromJson(json['icone']), + actif: serializer.fromJson(json['actif']), + ordre: serializer.fromJson(json['ordre']), + famille: serializer.fromJson(json['famille']), + familleOrdre: serializer.fromJson(json['familleOrdre']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'libelle': serializer.toJson(libelle), + 'points': serializer.toJson(points), + 'icone': serializer.toJson(icone), + 'actif': serializer.toJson(actif), + 'ordre': serializer.toJson(ordre), + 'famille': serializer.toJson(famille), + 'familleOrdre': serializer.toJson(familleOrdre), + }; + } + + Regle copyWith({ + int? id, + String? libelle, + int? points, + String? icone, + bool? actif, + int? ordre, + String? famille, + int? familleOrdre, + }) => Regle( + id: id ?? this.id, + libelle: libelle ?? this.libelle, + points: points ?? this.points, + icone: icone ?? this.icone, + actif: actif ?? this.actif, + ordre: ordre ?? this.ordre, + famille: famille ?? this.famille, + familleOrdre: familleOrdre ?? this.familleOrdre, + ); + Regle copyWithCompanion(ReglesCompanion data) { + return Regle( + id: data.id.present ? data.id.value : this.id, + libelle: data.libelle.present ? data.libelle.value : this.libelle, + points: data.points.present ? data.points.value : this.points, + icone: data.icone.present ? data.icone.value : this.icone, + actif: data.actif.present ? data.actif.value : this.actif, + ordre: data.ordre.present ? data.ordre.value : this.ordre, + famille: data.famille.present ? data.famille.value : this.famille, + familleOrdre: data.familleOrdre.present + ? data.familleOrdre.value + : this.familleOrdre, + ); + } + + @override + String toString() { + return (StringBuffer('Regle(') + ..write('id: $id, ') + ..write('libelle: $libelle, ') + ..write('points: $points, ') + ..write('icone: $icone, ') + ..write('actif: $actif, ') + ..write('ordre: $ordre, ') + ..write('famille: $famille, ') + ..write('familleOrdre: $familleOrdre') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + libelle, + points, + icone, + actif, + ordre, + famille, + familleOrdre, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Regle && + other.id == this.id && + other.libelle == this.libelle && + other.points == this.points && + other.icone == this.icone && + other.actif == this.actif && + other.ordre == this.ordre && + other.famille == this.famille && + other.familleOrdre == this.familleOrdre); +} + +class ReglesCompanion extends UpdateCompanion { + final Value id; + final Value libelle; + final Value points; + final Value icone; + final Value actif; + final Value ordre; + final Value famille; + final Value familleOrdre; + const ReglesCompanion({ + this.id = const Value.absent(), + this.libelle = const Value.absent(), + this.points = const Value.absent(), + this.icone = const Value.absent(), + this.actif = const Value.absent(), + this.ordre = const Value.absent(), + this.famille = const Value.absent(), + this.familleOrdre = const Value.absent(), + }); + ReglesCompanion.insert({ + this.id = const Value.absent(), + required String libelle, + required int points, + this.icone = const Value.absent(), + this.actif = const Value.absent(), + this.ordre = const Value.absent(), + this.famille = const Value.absent(), + this.familleOrdre = const Value.absent(), + }) : libelle = Value(libelle), + points = Value(points); + static Insertable custom({ + Expression? id, + Expression? libelle, + Expression? points, + Expression? icone, + Expression? actif, + Expression? ordre, + Expression? famille, + Expression? familleOrdre, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (libelle != null) 'libelle': libelle, + if (points != null) 'points': points, + if (icone != null) 'icone': icone, + if (actif != null) 'actif': actif, + if (ordre != null) 'ordre': ordre, + if (famille != null) 'famille': famille, + if (familleOrdre != null) 'famille_ordre': familleOrdre, + }); + } + + ReglesCompanion copyWith({ + Value? id, + Value? libelle, + Value? points, + Value? icone, + Value? actif, + Value? ordre, + Value? famille, + Value? familleOrdre, + }) { + return ReglesCompanion( + id: id ?? this.id, + libelle: libelle ?? this.libelle, + points: points ?? this.points, + icone: icone ?? this.icone, + actif: actif ?? this.actif, + ordre: ordre ?? this.ordre, + famille: famille ?? this.famille, + familleOrdre: familleOrdre ?? this.familleOrdre, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (libelle.present) { + map['libelle'] = Variable(libelle.value); + } + if (points.present) { + map['points'] = Variable(points.value); + } + if (icone.present) { + map['icone'] = Variable(icone.value); + } + if (actif.present) { + map['actif'] = Variable(actif.value); + } + if (ordre.present) { + map['ordre'] = Variable(ordre.value); + } + if (famille.present) { + map['famille'] = Variable(famille.value); + } + if (familleOrdre.present) { + map['famille_ordre'] = Variable(familleOrdre.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ReglesCompanion(') + ..write('id: $id, ') + ..write('libelle: $libelle, ') + ..write('points: $points, ') + ..write('icone: $icone, ') + ..write('actif: $actif, ') + ..write('ordre: $ordre, ') + ..write('famille: $famille, ') + ..write('familleOrdre: $familleOrdre') + ..write(')')) + .toString(); + } +} + +class $RecompensesTable extends Recompenses + with TableInfo<$RecompensesTable, Recompense> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $RecompensesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _libelleMeta = const VerificationMeta( + 'libelle', + ); + @override + late final GeneratedColumn libelle = GeneratedColumn( + 'libelle', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _coutPointsMeta = const VerificationMeta( + 'coutPoints', + ); + @override + late final GeneratedColumn coutPoints = GeneratedColumn( + 'cout_points', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _iconeMeta = const VerificationMeta('icone'); + @override + late final GeneratedColumn icone = GeneratedColumn( + 'icone', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('🎁'), + ); + static const VerificationMeta _actifMeta = const VerificationMeta('actif'); + @override + late final GeneratedColumn actif = GeneratedColumn( + 'actif', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("actif" IN (0, 1))', + ), + defaultValue: const Constant(true), + ); + static const VerificationMeta _ordreMeta = const VerificationMeta('ordre'); + @override + late final GeneratedColumn ordre = GeneratedColumn( + 'ordre', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); + @override + List get $columns => [ + id, + libelle, + coutPoints, + icone, + actif, + ordre, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'recompenses'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('libelle')) { + context.handle( + _libelleMeta, + libelle.isAcceptableOrUnknown(data['libelle']!, _libelleMeta), + ); + } else if (isInserting) { + context.missing(_libelleMeta); + } + if (data.containsKey('cout_points')) { + context.handle( + _coutPointsMeta, + coutPoints.isAcceptableOrUnknown(data['cout_points']!, _coutPointsMeta), + ); + } else if (isInserting) { + context.missing(_coutPointsMeta); + } + if (data.containsKey('icone')) { + context.handle( + _iconeMeta, + icone.isAcceptableOrUnknown(data['icone']!, _iconeMeta), + ); + } + if (data.containsKey('actif')) { + context.handle( + _actifMeta, + actif.isAcceptableOrUnknown(data['actif']!, _actifMeta), + ); + } + if (data.containsKey('ordre')) { + context.handle( + _ordreMeta, + ordre.isAcceptableOrUnknown(data['ordre']!, _ordreMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Recompense map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Recompense( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + libelle: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}libelle'], + )!, + coutPoints: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}cout_points'], + )!, + icone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}icone'], + )!, + actif: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}actif'], + )!, + ordre: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}ordre'], + )!, + ); + } + + @override + $RecompensesTable createAlias(String alias) { + return $RecompensesTable(attachedDatabase, alias); + } +} + +class Recompense extends DataClass implements Insertable { + final int id; + final String libelle; + final int coutPoints; + final String icone; + final bool actif; + final int ordre; + const Recompense({ + required this.id, + required this.libelle, + required this.coutPoints, + required this.icone, + required this.actif, + required this.ordre, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['libelle'] = Variable(libelle); + map['cout_points'] = Variable(coutPoints); + map['icone'] = Variable(icone); + map['actif'] = Variable(actif); + map['ordre'] = Variable(ordre); + return map; + } + + RecompensesCompanion toCompanion(bool nullToAbsent) { + return RecompensesCompanion( + id: Value(id), + libelle: Value(libelle), + coutPoints: Value(coutPoints), + icone: Value(icone), + actif: Value(actif), + ordre: Value(ordre), + ); + } + + factory Recompense.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Recompense( + id: serializer.fromJson(json['id']), + libelle: serializer.fromJson(json['libelle']), + coutPoints: serializer.fromJson(json['coutPoints']), + icone: serializer.fromJson(json['icone']), + actif: serializer.fromJson(json['actif']), + ordre: serializer.fromJson(json['ordre']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'libelle': serializer.toJson(libelle), + 'coutPoints': serializer.toJson(coutPoints), + 'icone': serializer.toJson(icone), + 'actif': serializer.toJson(actif), + 'ordre': serializer.toJson(ordre), + }; + } + + Recompense copyWith({ + int? id, + String? libelle, + int? coutPoints, + String? icone, + bool? actif, + int? ordre, + }) => Recompense( + id: id ?? this.id, + libelle: libelle ?? this.libelle, + coutPoints: coutPoints ?? this.coutPoints, + icone: icone ?? this.icone, + actif: actif ?? this.actif, + ordre: ordre ?? this.ordre, + ); + Recompense copyWithCompanion(RecompensesCompanion data) { + return Recompense( + id: data.id.present ? data.id.value : this.id, + libelle: data.libelle.present ? data.libelle.value : this.libelle, + coutPoints: data.coutPoints.present + ? data.coutPoints.value + : this.coutPoints, + icone: data.icone.present ? data.icone.value : this.icone, + actif: data.actif.present ? data.actif.value : this.actif, + ordre: data.ordre.present ? data.ordre.value : this.ordre, + ); + } + + @override + String toString() { + return (StringBuffer('Recompense(') + ..write('id: $id, ') + ..write('libelle: $libelle, ') + ..write('coutPoints: $coutPoints, ') + ..write('icone: $icone, ') + ..write('actif: $actif, ') + ..write('ordre: $ordre') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, libelle, coutPoints, icone, actif, ordre); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Recompense && + other.id == this.id && + other.libelle == this.libelle && + other.coutPoints == this.coutPoints && + other.icone == this.icone && + other.actif == this.actif && + other.ordre == this.ordre); +} + +class RecompensesCompanion extends UpdateCompanion { + final Value id; + final Value libelle; + final Value coutPoints; + final Value icone; + final Value actif; + final Value ordre; + const RecompensesCompanion({ + this.id = const Value.absent(), + this.libelle = const Value.absent(), + this.coutPoints = const Value.absent(), + this.icone = const Value.absent(), + this.actif = const Value.absent(), + this.ordre = const Value.absent(), + }); + RecompensesCompanion.insert({ + this.id = const Value.absent(), + required String libelle, + required int coutPoints, + this.icone = const Value.absent(), + this.actif = const Value.absent(), + this.ordre = const Value.absent(), + }) : libelle = Value(libelle), + coutPoints = Value(coutPoints); + static Insertable custom({ + Expression? id, + Expression? libelle, + Expression? coutPoints, + Expression? icone, + Expression? actif, + Expression? ordre, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (libelle != null) 'libelle': libelle, + if (coutPoints != null) 'cout_points': coutPoints, + if (icone != null) 'icone': icone, + if (actif != null) 'actif': actif, + if (ordre != null) 'ordre': ordre, + }); + } + + RecompensesCompanion copyWith({ + Value? id, + Value? libelle, + Value? coutPoints, + Value? icone, + Value? actif, + Value? ordre, + }) { + return RecompensesCompanion( + id: id ?? this.id, + libelle: libelle ?? this.libelle, + coutPoints: coutPoints ?? this.coutPoints, + icone: icone ?? this.icone, + actif: actif ?? this.actif, + ordre: ordre ?? this.ordre, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (libelle.present) { + map['libelle'] = Variable(libelle.value); + } + if (coutPoints.present) { + map['cout_points'] = Variable(coutPoints.value); + } + if (icone.present) { + map['icone'] = Variable(icone.value); + } + if (actif.present) { + map['actif'] = Variable(actif.value); + } + if (ordre.present) { + map['ordre'] = Variable(ordre.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RecompensesCompanion(') + ..write('id: $id, ') + ..write('libelle: $libelle, ') + ..write('coutPoints: $coutPoints, ') + ..write('icone: $icone, ') + ..write('actif: $actif, ') + ..write('ordre: $ordre') + ..write(')')) + .toString(); + } +} + +class $MouvementsTable extends Mouvements + with TableInfo<$MouvementsTable, Mouvement> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $MouvementsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const VerificationMeta _enfantIdMeta = const VerificationMeta( + 'enfantId', + ); + @override + late final GeneratedColumn enfantId = GeneratedColumn( + 'enfant_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES enfants (id)', + ), + ); + static const VerificationMeta _regleIdMeta = const VerificationMeta( + 'regleId', + ); + @override + late final GeneratedColumn regleId = GeneratedColumn( + 'regle_id', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES regles (id)', + ), + ); + static const VerificationMeta _recompenseIdMeta = const VerificationMeta( + 'recompenseId', + ); + @override + late final GeneratedColumn recompenseId = GeneratedColumn( + 'recompense_id', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES recompenses (id)', + ), + ); + static const VerificationMeta _deltaMeta = const VerificationMeta('delta'); + @override + late final GeneratedColumn delta = GeneratedColumn( + 'delta', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _noteMeta = const VerificationMeta('note'); + @override + late final GeneratedColumn note = GeneratedColumn( + 'note', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _creeLeMeta = const VerificationMeta('creeLe'); + @override + late final GeneratedColumn creeLe = GeneratedColumn( + 'cree_le', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime, + ); + static const VerificationMeta _annuleMeta = const VerificationMeta('annule'); + @override + late final GeneratedColumn annule = GeneratedColumn( + 'annule', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("annule" IN (0, 1))', + ), + defaultValue: const Constant(false), + ); + @override + List get $columns => [ + id, + enfantId, + regleId, + recompenseId, + delta, + note, + creeLe, + annule, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'mouvements'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('enfant_id')) { + context.handle( + _enfantIdMeta, + enfantId.isAcceptableOrUnknown(data['enfant_id']!, _enfantIdMeta), + ); + } else if (isInserting) { + context.missing(_enfantIdMeta); + } + if (data.containsKey('regle_id')) { + context.handle( + _regleIdMeta, + regleId.isAcceptableOrUnknown(data['regle_id']!, _regleIdMeta), + ); + } + if (data.containsKey('recompense_id')) { + context.handle( + _recompenseIdMeta, + recompenseId.isAcceptableOrUnknown( + data['recompense_id']!, + _recompenseIdMeta, + ), + ); + } + if (data.containsKey('delta')) { + context.handle( + _deltaMeta, + delta.isAcceptableOrUnknown(data['delta']!, _deltaMeta), + ); + } else if (isInserting) { + context.missing(_deltaMeta); + } + if (data.containsKey('note')) { + context.handle( + _noteMeta, + note.isAcceptableOrUnknown(data['note']!, _noteMeta), + ); + } + if (data.containsKey('cree_le')) { + context.handle( + _creeLeMeta, + creeLe.isAcceptableOrUnknown(data['cree_le']!, _creeLeMeta), + ); + } + if (data.containsKey('annule')) { + context.handle( + _annuleMeta, + annule.isAcceptableOrUnknown(data['annule']!, _annuleMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Mouvement map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Mouvement( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + enfantId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}enfant_id'], + )!, + regleId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}regle_id'], + ), + recompenseId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}recompense_id'], + ), + delta: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}delta'], + )!, + note: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}note'], + ), + creeLe: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}cree_le'], + )!, + annule: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}annule'], + )!, + ); + } + + @override + $MouvementsTable createAlias(String alias) { + return $MouvementsTable(attachedDatabase, alias); + } +} + +class Mouvement extends DataClass implements Insertable { + final int id; + final int enfantId; + final int? regleId; + final int? recompenseId; + final int delta; + final String? note; + final DateTime creeLe; + final bool annule; + const Mouvement({ + required this.id, + required this.enfantId, + this.regleId, + this.recompenseId, + required this.delta, + this.note, + required this.creeLe, + required this.annule, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['enfant_id'] = Variable(enfantId); + if (!nullToAbsent || regleId != null) { + map['regle_id'] = Variable(regleId); + } + if (!nullToAbsent || recompenseId != null) { + map['recompense_id'] = Variable(recompenseId); + } + map['delta'] = Variable(delta); + if (!nullToAbsent || note != null) { + map['note'] = Variable(note); + } + map['cree_le'] = Variable(creeLe); + map['annule'] = Variable(annule); + return map; + } + + MouvementsCompanion toCompanion(bool nullToAbsent) { + return MouvementsCompanion( + id: Value(id), + enfantId: Value(enfantId), + regleId: regleId == null && nullToAbsent + ? const Value.absent() + : Value(regleId), + recompenseId: recompenseId == null && nullToAbsent + ? const Value.absent() + : Value(recompenseId), + delta: Value(delta), + note: note == null && nullToAbsent ? const Value.absent() : Value(note), + creeLe: Value(creeLe), + annule: Value(annule), + ); + } + + factory Mouvement.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Mouvement( + id: serializer.fromJson(json['id']), + enfantId: serializer.fromJson(json['enfantId']), + regleId: serializer.fromJson(json['regleId']), + recompenseId: serializer.fromJson(json['recompenseId']), + delta: serializer.fromJson(json['delta']), + note: serializer.fromJson(json['note']), + creeLe: serializer.fromJson(json['creeLe']), + annule: serializer.fromJson(json['annule']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'enfantId': serializer.toJson(enfantId), + 'regleId': serializer.toJson(regleId), + 'recompenseId': serializer.toJson(recompenseId), + 'delta': serializer.toJson(delta), + 'note': serializer.toJson(note), + 'creeLe': serializer.toJson(creeLe), + 'annule': serializer.toJson(annule), + }; + } + + Mouvement copyWith({ + int? id, + int? enfantId, + Value regleId = const Value.absent(), + Value recompenseId = const Value.absent(), + int? delta, + Value note = const Value.absent(), + DateTime? creeLe, + bool? annule, + }) => Mouvement( + id: id ?? this.id, + enfantId: enfantId ?? this.enfantId, + regleId: regleId.present ? regleId.value : this.regleId, + recompenseId: recompenseId.present ? recompenseId.value : this.recompenseId, + delta: delta ?? this.delta, + note: note.present ? note.value : this.note, + creeLe: creeLe ?? this.creeLe, + annule: annule ?? this.annule, + ); + Mouvement copyWithCompanion(MouvementsCompanion data) { + return Mouvement( + id: data.id.present ? data.id.value : this.id, + enfantId: data.enfantId.present ? data.enfantId.value : this.enfantId, + regleId: data.regleId.present ? data.regleId.value : this.regleId, + recompenseId: data.recompenseId.present + ? data.recompenseId.value + : this.recompenseId, + delta: data.delta.present ? data.delta.value : this.delta, + note: data.note.present ? data.note.value : this.note, + creeLe: data.creeLe.present ? data.creeLe.value : this.creeLe, + annule: data.annule.present ? data.annule.value : this.annule, + ); + } + + @override + String toString() { + return (StringBuffer('Mouvement(') + ..write('id: $id, ') + ..write('enfantId: $enfantId, ') + ..write('regleId: $regleId, ') + ..write('recompenseId: $recompenseId, ') + ..write('delta: $delta, ') + ..write('note: $note, ') + ..write('creeLe: $creeLe, ') + ..write('annule: $annule') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + enfantId, + regleId, + recompenseId, + delta, + note, + creeLe, + annule, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Mouvement && + other.id == this.id && + other.enfantId == this.enfantId && + other.regleId == this.regleId && + other.recompenseId == this.recompenseId && + other.delta == this.delta && + other.note == this.note && + other.creeLe == this.creeLe && + other.annule == this.annule); +} + +class MouvementsCompanion extends UpdateCompanion { + final Value id; + final Value enfantId; + final Value regleId; + final Value recompenseId; + final Value delta; + final Value note; + final Value creeLe; + final Value annule; + const MouvementsCompanion({ + this.id = const Value.absent(), + this.enfantId = const Value.absent(), + this.regleId = const Value.absent(), + this.recompenseId = const Value.absent(), + this.delta = const Value.absent(), + this.note = const Value.absent(), + this.creeLe = const Value.absent(), + this.annule = const Value.absent(), + }); + MouvementsCompanion.insert({ + this.id = const Value.absent(), + required int enfantId, + this.regleId = const Value.absent(), + this.recompenseId = const Value.absent(), + required int delta, + this.note = const Value.absent(), + this.creeLe = const Value.absent(), + this.annule = const Value.absent(), + }) : enfantId = Value(enfantId), + delta = Value(delta); + static Insertable custom({ + Expression? id, + Expression? enfantId, + Expression? regleId, + Expression? recompenseId, + Expression? delta, + Expression? note, + Expression? creeLe, + Expression? annule, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (enfantId != null) 'enfant_id': enfantId, + if (regleId != null) 'regle_id': regleId, + if (recompenseId != null) 'recompense_id': recompenseId, + if (delta != null) 'delta': delta, + if (note != null) 'note': note, + if (creeLe != null) 'cree_le': creeLe, + if (annule != null) 'annule': annule, + }); + } + + MouvementsCompanion copyWith({ + Value? id, + Value? enfantId, + Value? regleId, + Value? recompenseId, + Value? delta, + Value? note, + Value? creeLe, + Value? annule, + }) { + return MouvementsCompanion( + id: id ?? this.id, + enfantId: enfantId ?? this.enfantId, + regleId: regleId ?? this.regleId, + recompenseId: recompenseId ?? this.recompenseId, + delta: delta ?? this.delta, + note: note ?? this.note, + creeLe: creeLe ?? this.creeLe, + annule: annule ?? this.annule, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (enfantId.present) { + map['enfant_id'] = Variable(enfantId.value); + } + if (regleId.present) { + map['regle_id'] = Variable(regleId.value); + } + if (recompenseId.present) { + map['recompense_id'] = Variable(recompenseId.value); + } + if (delta.present) { + map['delta'] = Variable(delta.value); + } + if (note.present) { + map['note'] = Variable(note.value); + } + if (creeLe.present) { + map['cree_le'] = Variable(creeLe.value); + } + if (annule.present) { + map['annule'] = Variable(annule.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MouvementsCompanion(') + ..write('id: $id, ') + ..write('enfantId: $enfantId, ') + ..write('regleId: $regleId, ') + ..write('recompenseId: $recompenseId, ') + ..write('delta: $delta, ') + ..write('note: $note, ') + ..write('creeLe: $creeLe, ') + ..write('annule: $annule') + ..write(')')) + .toString(); + } +} + +class $ConfigTable extends Config with TableInfo<$ConfigTable, ConfigData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ConfigTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _cleMeta = const VerificationMeta('cle'); + @override + late final GeneratedColumn cle = GeneratedColumn( + 'cle', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _valeurMeta = const VerificationMeta('valeur'); + @override + late final GeneratedColumn valeur = GeneratedColumn( + 'valeur', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [cle, valeur]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'config'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('cle')) { + context.handle( + _cleMeta, + cle.isAcceptableOrUnknown(data['cle']!, _cleMeta), + ); + } else if (isInserting) { + context.missing(_cleMeta); + } + if (data.containsKey('valeur')) { + context.handle( + _valeurMeta, + valeur.isAcceptableOrUnknown(data['valeur']!, _valeurMeta), + ); + } else if (isInserting) { + context.missing(_valeurMeta); + } + return context; + } + + @override + Set get $primaryKey => {cle}; + @override + ConfigData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ConfigData( + cle: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cle'], + )!, + valeur: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}valeur'], + )!, + ); + } + + @override + $ConfigTable createAlias(String alias) { + return $ConfigTable(attachedDatabase, alias); + } +} + +class ConfigData extends DataClass implements Insertable { + final String cle; + final String valeur; + const ConfigData({required this.cle, required this.valeur}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['cle'] = Variable(cle); + map['valeur'] = Variable(valeur); + return map; + } + + ConfigCompanion toCompanion(bool nullToAbsent) { + return ConfigCompanion(cle: Value(cle), valeur: Value(valeur)); + } + + factory ConfigData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ConfigData( + cle: serializer.fromJson(json['cle']), + valeur: serializer.fromJson(json['valeur']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'cle': serializer.toJson(cle), + 'valeur': serializer.toJson(valeur), + }; + } + + ConfigData copyWith({String? cle, String? valeur}) => + ConfigData(cle: cle ?? this.cle, valeur: valeur ?? this.valeur); + ConfigData copyWithCompanion(ConfigCompanion data) { + return ConfigData( + cle: data.cle.present ? data.cle.value : this.cle, + valeur: data.valeur.present ? data.valeur.value : this.valeur, + ); + } + + @override + String toString() { + return (StringBuffer('ConfigData(') + ..write('cle: $cle, ') + ..write('valeur: $valeur') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(cle, valeur); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ConfigData && + other.cle == this.cle && + other.valeur == this.valeur); +} + +class ConfigCompanion extends UpdateCompanion { + final Value cle; + final Value valeur; + final Value rowid; + const ConfigCompanion({ + this.cle = const Value.absent(), + this.valeur = const Value.absent(), + this.rowid = const Value.absent(), + }); + ConfigCompanion.insert({ + required String cle, + required String valeur, + this.rowid = const Value.absent(), + }) : cle = Value(cle), + valeur = Value(valeur); + static Insertable custom({ + Expression? cle, + Expression? valeur, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (cle != null) 'cle': cle, + if (valeur != null) 'valeur': valeur, + if (rowid != null) 'rowid': rowid, + }); + } + + ConfigCompanion copyWith({ + Value? cle, + Value? valeur, + Value? rowid, + }) { + return ConfigCompanion( + cle: cle ?? this.cle, + valeur: valeur ?? this.valeur, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (cle.present) { + map['cle'] = Variable(cle.value); + } + if (valeur.present) { + map['valeur'] = Variable(valeur.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ConfigCompanion(') + ..write('cle: $cle, ') + ..write('valeur: $valeur, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $EnfantsTable enfants = $EnfantsTable(this); + late final $ReglesTable regles = $ReglesTable(this); + late final $RecompensesTable recompenses = $RecompensesTable(this); + late final $MouvementsTable mouvements = $MouvementsTable(this); + late final $ConfigTable config = $ConfigTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + enfants, + regles, + recompenses, + mouvements, + config, + ]; +} + +typedef $$EnfantsTableCreateCompanionBuilder = + EnfantsCompanion Function({ + Value id, + required String prenom, + Value dateNaissance, + Value score, + Value couleur, + Value ordre, + }); +typedef $$EnfantsTableUpdateCompanionBuilder = + EnfantsCompanion Function({ + Value id, + Value prenom, + Value dateNaissance, + Value score, + Value couleur, + Value ordre, + }); + +final class $$EnfantsTableReferences + extends BaseReferences<_$AppDatabase, $EnfantsTable, Enfant> { + $$EnfantsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$MouvementsTable, List> + _mouvementsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.mouvements, + aliasName: 'enfants__id__mouvements__enfant_id', + ); + + $$MouvementsTableProcessedTableManager get mouvementsRefs { + final manager = $$MouvementsTableTableManager( + $_db, + $_db.mouvements, + ).filter((f) => f.enfantId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_mouvementsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$EnfantsTableFilterComposer + extends Composer<_$AppDatabase, $EnfantsTable> { + $$EnfantsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get prenom => $composableBuilder( + column: $table.prenom, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get dateNaissance => $composableBuilder( + column: $table.dateNaissance, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get score => $composableBuilder( + column: $table.score, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get couleur => $composableBuilder( + column: $table.couleur, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get ordre => $composableBuilder( + column: $table.ordre, + builder: (column) => ColumnFilters(column), + ); + + Expression mouvementsRefs( + Expression Function($$MouvementsTableFilterComposer f) f, + ) { + final $$MouvementsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.mouvements, + getReferencedColumn: (t) => t.enfantId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MouvementsTableFilterComposer( + $db: $db, + $table: $db.mouvements, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$EnfantsTableOrderingComposer + extends Composer<_$AppDatabase, $EnfantsTable> { + $$EnfantsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get prenom => $composableBuilder( + column: $table.prenom, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get dateNaissance => $composableBuilder( + column: $table.dateNaissance, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get score => $composableBuilder( + column: $table.score, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get couleur => $composableBuilder( + column: $table.couleur, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get ordre => $composableBuilder( + column: $table.ordre, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$EnfantsTableAnnotationComposer + extends Composer<_$AppDatabase, $EnfantsTable> { + $$EnfantsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get prenom => + $composableBuilder(column: $table.prenom, builder: (column) => column); + + GeneratedColumn get dateNaissance => $composableBuilder( + column: $table.dateNaissance, + builder: (column) => column, + ); + + GeneratedColumn get score => + $composableBuilder(column: $table.score, builder: (column) => column); + + GeneratedColumn get couleur => + $composableBuilder(column: $table.couleur, builder: (column) => column); + + GeneratedColumn get ordre => + $composableBuilder(column: $table.ordre, builder: (column) => column); + + Expression mouvementsRefs( + Expression Function($$MouvementsTableAnnotationComposer a) f, + ) { + final $$MouvementsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.mouvements, + getReferencedColumn: (t) => t.enfantId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MouvementsTableAnnotationComposer( + $db: $db, + $table: $db.mouvements, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$EnfantsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $EnfantsTable, + Enfant, + $$EnfantsTableFilterComposer, + $$EnfantsTableOrderingComposer, + $$EnfantsTableAnnotationComposer, + $$EnfantsTableCreateCompanionBuilder, + $$EnfantsTableUpdateCompanionBuilder, + (Enfant, $$EnfantsTableReferences), + Enfant, + PrefetchHooks Function({bool mouvementsRefs}) + > { + $$EnfantsTableTableManager(_$AppDatabase db, $EnfantsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$EnfantsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$EnfantsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$EnfantsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value prenom = const Value.absent(), + Value dateNaissance = const Value.absent(), + Value score = const Value.absent(), + Value couleur = const Value.absent(), + Value ordre = const Value.absent(), + }) => EnfantsCompanion( + id: id, + prenom: prenom, + dateNaissance: dateNaissance, + score: score, + couleur: couleur, + ordre: ordre, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String prenom, + Value dateNaissance = const Value.absent(), + Value score = const Value.absent(), + Value couleur = const Value.absent(), + Value ordre = const Value.absent(), + }) => EnfantsCompanion.insert( + id: id, + prenom: prenom, + dateNaissance: dateNaissance, + score: score, + couleur: couleur, + ordre: ordre, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$EnfantsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({mouvementsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (mouvementsRefs) db.mouvements], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (mouvementsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$EnfantsTableReferences + ._mouvementsRefsTable(db), + managerFromTypedResult: (p0) => $$EnfantsTableReferences( + db, + table, + p0, + ).mouvementsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.enfantId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$EnfantsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $EnfantsTable, + Enfant, + $$EnfantsTableFilterComposer, + $$EnfantsTableOrderingComposer, + $$EnfantsTableAnnotationComposer, + $$EnfantsTableCreateCompanionBuilder, + $$EnfantsTableUpdateCompanionBuilder, + (Enfant, $$EnfantsTableReferences), + Enfant, + PrefetchHooks Function({bool mouvementsRefs}) + >; +typedef $$ReglesTableCreateCompanionBuilder = + ReglesCompanion Function({ + Value id, + required String libelle, + required int points, + Value icone, + Value actif, + Value ordre, + Value famille, + Value familleOrdre, + }); +typedef $$ReglesTableUpdateCompanionBuilder = + ReglesCompanion Function({ + Value id, + Value libelle, + Value points, + Value icone, + Value actif, + Value ordre, + Value famille, + Value familleOrdre, + }); + +final class $$ReglesTableReferences + extends BaseReferences<_$AppDatabase, $ReglesTable, Regle> { + $$ReglesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$MouvementsTable, List> + _mouvementsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.mouvements, + aliasName: 'regles__id__mouvements__regle_id', + ); + + $$MouvementsTableProcessedTableManager get mouvementsRefs { + final manager = $$MouvementsTableTableManager( + $_db, + $_db.mouvements, + ).filter((f) => f.regleId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_mouvementsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$ReglesTableFilterComposer + extends Composer<_$AppDatabase, $ReglesTable> { + $$ReglesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get libelle => $composableBuilder( + column: $table.libelle, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get points => $composableBuilder( + column: $table.points, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get icone => $composableBuilder( + column: $table.icone, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get actif => $composableBuilder( + column: $table.actif, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get ordre => $composableBuilder( + column: $table.ordre, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get famille => $composableBuilder( + column: $table.famille, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get familleOrdre => $composableBuilder( + column: $table.familleOrdre, + builder: (column) => ColumnFilters(column), + ); + + Expression mouvementsRefs( + Expression Function($$MouvementsTableFilterComposer f) f, + ) { + final $$MouvementsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.mouvements, + getReferencedColumn: (t) => t.regleId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MouvementsTableFilterComposer( + $db: $db, + $table: $db.mouvements, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ReglesTableOrderingComposer + extends Composer<_$AppDatabase, $ReglesTable> { + $$ReglesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get libelle => $composableBuilder( + column: $table.libelle, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get points => $composableBuilder( + column: $table.points, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get icone => $composableBuilder( + column: $table.icone, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get actif => $composableBuilder( + column: $table.actif, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get ordre => $composableBuilder( + column: $table.ordre, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get famille => $composableBuilder( + column: $table.famille, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get familleOrdre => $composableBuilder( + column: $table.familleOrdre, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ReglesTableAnnotationComposer + extends Composer<_$AppDatabase, $ReglesTable> { + $$ReglesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get libelle => + $composableBuilder(column: $table.libelle, builder: (column) => column); + + GeneratedColumn get points => + $composableBuilder(column: $table.points, builder: (column) => column); + + GeneratedColumn get icone => + $composableBuilder(column: $table.icone, builder: (column) => column); + + GeneratedColumn get actif => + $composableBuilder(column: $table.actif, builder: (column) => column); + + GeneratedColumn get ordre => + $composableBuilder(column: $table.ordre, builder: (column) => column); + + GeneratedColumn get famille => + $composableBuilder(column: $table.famille, builder: (column) => column); + + GeneratedColumn get familleOrdre => $composableBuilder( + column: $table.familleOrdre, + builder: (column) => column, + ); + + Expression mouvementsRefs( + Expression Function($$MouvementsTableAnnotationComposer a) f, + ) { + final $$MouvementsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.mouvements, + getReferencedColumn: (t) => t.regleId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MouvementsTableAnnotationComposer( + $db: $db, + $table: $db.mouvements, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$ReglesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ReglesTable, + Regle, + $$ReglesTableFilterComposer, + $$ReglesTableOrderingComposer, + $$ReglesTableAnnotationComposer, + $$ReglesTableCreateCompanionBuilder, + $$ReglesTableUpdateCompanionBuilder, + (Regle, $$ReglesTableReferences), + Regle, + PrefetchHooks Function({bool mouvementsRefs}) + > { + $$ReglesTableTableManager(_$AppDatabase db, $ReglesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ReglesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ReglesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ReglesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value libelle = const Value.absent(), + Value points = const Value.absent(), + Value icone = const Value.absent(), + Value actif = const Value.absent(), + Value ordre = const Value.absent(), + Value famille = const Value.absent(), + Value familleOrdre = const Value.absent(), + }) => ReglesCompanion( + id: id, + libelle: libelle, + points: points, + icone: icone, + actif: actif, + ordre: ordre, + famille: famille, + familleOrdre: familleOrdre, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String libelle, + required int points, + Value icone = const Value.absent(), + Value actif = const Value.absent(), + Value ordre = const Value.absent(), + Value famille = const Value.absent(), + Value familleOrdre = const Value.absent(), + }) => ReglesCompanion.insert( + id: id, + libelle: libelle, + points: points, + icone: icone, + actif: actif, + ordre: ordre, + famille: famille, + familleOrdre: familleOrdre, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => + (e.readTable(table), $$ReglesTableReferences(db, table, e)), + ) + .toList(), + prefetchHooksCallback: ({mouvementsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (mouvementsRefs) db.mouvements], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (mouvementsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ReglesTableReferences + ._mouvementsRefsTable(db), + managerFromTypedResult: (p0) => + $$ReglesTableReferences(db, table, p0).mouvementsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.regleId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$ReglesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ReglesTable, + Regle, + $$ReglesTableFilterComposer, + $$ReglesTableOrderingComposer, + $$ReglesTableAnnotationComposer, + $$ReglesTableCreateCompanionBuilder, + $$ReglesTableUpdateCompanionBuilder, + (Regle, $$ReglesTableReferences), + Regle, + PrefetchHooks Function({bool mouvementsRefs}) + >; +typedef $$RecompensesTableCreateCompanionBuilder = + RecompensesCompanion Function({ + Value id, + required String libelle, + required int coutPoints, + Value icone, + Value actif, + Value ordre, + }); +typedef $$RecompensesTableUpdateCompanionBuilder = + RecompensesCompanion Function({ + Value id, + Value libelle, + Value coutPoints, + Value icone, + Value actif, + Value ordre, + }); + +final class $$RecompensesTableReferences + extends BaseReferences<_$AppDatabase, $RecompensesTable, Recompense> { + $$RecompensesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$MouvementsTable, List> + _mouvementsRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.mouvements, + aliasName: 'recompenses__id__mouvements__recompense_id', + ); + + $$MouvementsTableProcessedTableManager get mouvementsRefs { + final manager = $$MouvementsTableTableManager( + $_db, + $_db.mouvements, + ).filter((f) => f.recompenseId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_mouvementsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$RecompensesTableFilterComposer + extends Composer<_$AppDatabase, $RecompensesTable> { + $$RecompensesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get libelle => $composableBuilder( + column: $table.libelle, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get coutPoints => $composableBuilder( + column: $table.coutPoints, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get icone => $composableBuilder( + column: $table.icone, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get actif => $composableBuilder( + column: $table.actif, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get ordre => $composableBuilder( + column: $table.ordre, + builder: (column) => ColumnFilters(column), + ); + + Expression mouvementsRefs( + Expression Function($$MouvementsTableFilterComposer f) f, + ) { + final $$MouvementsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.mouvements, + getReferencedColumn: (t) => t.recompenseId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MouvementsTableFilterComposer( + $db: $db, + $table: $db.mouvements, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$RecompensesTableOrderingComposer + extends Composer<_$AppDatabase, $RecompensesTable> { + $$RecompensesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get libelle => $composableBuilder( + column: $table.libelle, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get coutPoints => $composableBuilder( + column: $table.coutPoints, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get icone => $composableBuilder( + column: $table.icone, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get actif => $composableBuilder( + column: $table.actif, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get ordre => $composableBuilder( + column: $table.ordre, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$RecompensesTableAnnotationComposer + extends Composer<_$AppDatabase, $RecompensesTable> { + $$RecompensesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get libelle => + $composableBuilder(column: $table.libelle, builder: (column) => column); + + GeneratedColumn get coutPoints => $composableBuilder( + column: $table.coutPoints, + builder: (column) => column, + ); + + GeneratedColumn get icone => + $composableBuilder(column: $table.icone, builder: (column) => column); + + GeneratedColumn get actif => + $composableBuilder(column: $table.actif, builder: (column) => column); + + GeneratedColumn get ordre => + $composableBuilder(column: $table.ordre, builder: (column) => column); + + Expression mouvementsRefs( + Expression Function($$MouvementsTableAnnotationComposer a) f, + ) { + final $$MouvementsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.mouvements, + getReferencedColumn: (t) => t.recompenseId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MouvementsTableAnnotationComposer( + $db: $db, + $table: $db.mouvements, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$RecompensesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $RecompensesTable, + Recompense, + $$RecompensesTableFilterComposer, + $$RecompensesTableOrderingComposer, + $$RecompensesTableAnnotationComposer, + $$RecompensesTableCreateCompanionBuilder, + $$RecompensesTableUpdateCompanionBuilder, + (Recompense, $$RecompensesTableReferences), + Recompense, + PrefetchHooks Function({bool mouvementsRefs}) + > { + $$RecompensesTableTableManager(_$AppDatabase db, $RecompensesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$RecompensesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$RecompensesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$RecompensesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value libelle = const Value.absent(), + Value coutPoints = const Value.absent(), + Value icone = const Value.absent(), + Value actif = const Value.absent(), + Value ordre = const Value.absent(), + }) => RecompensesCompanion( + id: id, + libelle: libelle, + coutPoints: coutPoints, + icone: icone, + actif: actif, + ordre: ordre, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required String libelle, + required int coutPoints, + Value icone = const Value.absent(), + Value actif = const Value.absent(), + Value ordre = const Value.absent(), + }) => RecompensesCompanion.insert( + id: id, + libelle: libelle, + coutPoints: coutPoints, + icone: icone, + actif: actif, + ordre: ordre, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$RecompensesTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({mouvementsRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (mouvementsRefs) db.mouvements], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (mouvementsRefs) + await $_getPrefetchedData< + Recompense, + $RecompensesTable, + Mouvement + >( + currentTable: table, + referencedTable: $$RecompensesTableReferences + ._mouvementsRefsTable(db), + managerFromTypedResult: (p0) => + $$RecompensesTableReferences( + db, + table, + p0, + ).mouvementsRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where( + (e) => e.recompenseId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$RecompensesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $RecompensesTable, + Recompense, + $$RecompensesTableFilterComposer, + $$RecompensesTableOrderingComposer, + $$RecompensesTableAnnotationComposer, + $$RecompensesTableCreateCompanionBuilder, + $$RecompensesTableUpdateCompanionBuilder, + (Recompense, $$RecompensesTableReferences), + Recompense, + PrefetchHooks Function({bool mouvementsRefs}) + >; +typedef $$MouvementsTableCreateCompanionBuilder = + MouvementsCompanion Function({ + Value id, + required int enfantId, + Value regleId, + Value recompenseId, + required int delta, + Value note, + Value creeLe, + Value annule, + }); +typedef $$MouvementsTableUpdateCompanionBuilder = + MouvementsCompanion Function({ + Value id, + Value enfantId, + Value regleId, + Value recompenseId, + Value delta, + Value note, + Value creeLe, + Value annule, + }); + +final class $$MouvementsTableReferences + extends BaseReferences<_$AppDatabase, $MouvementsTable, Mouvement> { + $$MouvementsTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $EnfantsTable _enfantIdTable(_$AppDatabase db) => + db.enfants.createAlias('mouvements__enfant_id__enfants__id'); + + $$EnfantsTableProcessedTableManager get enfantId { + final $_column = $_itemColumn('enfant_id')!; + + final manager = $$EnfantsTableTableManager( + $_db, + $_db.enfants, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_enfantIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $ReglesTable _regleIdTable(_$AppDatabase db) => + db.regles.createAlias('mouvements__regle_id__regles__id'); + + $$ReglesTableProcessedTableManager? get regleId { + final $_column = $_itemColumn('regle_id'); + if ($_column == null) return null; + final manager = $$ReglesTableTableManager( + $_db, + $_db.regles, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_regleIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $RecompensesTable _recompenseIdTable(_$AppDatabase db) => + db.recompenses.createAlias('mouvements__recompense_id__recompenses__id'); + + $$RecompensesTableProcessedTableManager? get recompenseId { + final $_column = $_itemColumn('recompense_id'); + if ($_column == null) return null; + final manager = $$RecompensesTableTableManager( + $_db, + $_db.recompenses, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_recompenseIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$MouvementsTableFilterComposer + extends Composer<_$AppDatabase, $MouvementsTable> { + $$MouvementsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get delta => $composableBuilder( + column: $table.delta, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get note => $composableBuilder( + column: $table.note, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get creeLe => $composableBuilder( + column: $table.creeLe, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get annule => $composableBuilder( + column: $table.annule, + builder: (column) => ColumnFilters(column), + ); + + $$EnfantsTableFilterComposer get enfantId { + final $$EnfantsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.enfantId, + referencedTable: $db.enfants, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$EnfantsTableFilterComposer( + $db: $db, + $table: $db.enfants, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ReglesTableFilterComposer get regleId { + final $$ReglesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.regleId, + referencedTable: $db.regles, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ReglesTableFilterComposer( + $db: $db, + $table: $db.regles, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$RecompensesTableFilterComposer get recompenseId { + final $$RecompensesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.recompenseId, + referencedTable: $db.recompenses, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$RecompensesTableFilterComposer( + $db: $db, + $table: $db.recompenses, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MouvementsTableOrderingComposer + extends Composer<_$AppDatabase, $MouvementsTable> { + $$MouvementsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get delta => $composableBuilder( + column: $table.delta, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get note => $composableBuilder( + column: $table.note, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get creeLe => $composableBuilder( + column: $table.creeLe, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get annule => $composableBuilder( + column: $table.annule, + builder: (column) => ColumnOrderings(column), + ); + + $$EnfantsTableOrderingComposer get enfantId { + final $$EnfantsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.enfantId, + referencedTable: $db.enfants, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$EnfantsTableOrderingComposer( + $db: $db, + $table: $db.enfants, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ReglesTableOrderingComposer get regleId { + final $$ReglesTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.regleId, + referencedTable: $db.regles, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ReglesTableOrderingComposer( + $db: $db, + $table: $db.regles, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$RecompensesTableOrderingComposer get recompenseId { + final $$RecompensesTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.recompenseId, + referencedTable: $db.recompenses, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$RecompensesTableOrderingComposer( + $db: $db, + $table: $db.recompenses, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MouvementsTableAnnotationComposer + extends Composer<_$AppDatabase, $MouvementsTable> { + $$MouvementsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get delta => + $composableBuilder(column: $table.delta, builder: (column) => column); + + GeneratedColumn get note => + $composableBuilder(column: $table.note, builder: (column) => column); + + GeneratedColumn get creeLe => + $composableBuilder(column: $table.creeLe, builder: (column) => column); + + GeneratedColumn get annule => + $composableBuilder(column: $table.annule, builder: (column) => column); + + $$EnfantsTableAnnotationComposer get enfantId { + final $$EnfantsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.enfantId, + referencedTable: $db.enfants, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$EnfantsTableAnnotationComposer( + $db: $db, + $table: $db.enfants, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$ReglesTableAnnotationComposer get regleId { + final $$ReglesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.regleId, + referencedTable: $db.regles, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ReglesTableAnnotationComposer( + $db: $db, + $table: $db.regles, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$RecompensesTableAnnotationComposer get recompenseId { + final $$RecompensesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.recompenseId, + referencedTable: $db.recompenses, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$RecompensesTableAnnotationComposer( + $db: $db, + $table: $db.recompenses, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MouvementsTableTableManager + extends + RootTableManager< + _$AppDatabase, + $MouvementsTable, + Mouvement, + $$MouvementsTableFilterComposer, + $$MouvementsTableOrderingComposer, + $$MouvementsTableAnnotationComposer, + $$MouvementsTableCreateCompanionBuilder, + $$MouvementsTableUpdateCompanionBuilder, + (Mouvement, $$MouvementsTableReferences), + Mouvement, + PrefetchHooks Function({ + bool enfantId, + bool regleId, + bool recompenseId, + }) + > { + $$MouvementsTableTableManager(_$AppDatabase db, $MouvementsTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$MouvementsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$MouvementsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$MouvementsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value enfantId = const Value.absent(), + Value regleId = const Value.absent(), + Value recompenseId = const Value.absent(), + Value delta = const Value.absent(), + Value note = const Value.absent(), + Value creeLe = const Value.absent(), + Value annule = const Value.absent(), + }) => MouvementsCompanion( + id: id, + enfantId: enfantId, + regleId: regleId, + recompenseId: recompenseId, + delta: delta, + note: note, + creeLe: creeLe, + annule: annule, + ), + createCompanionCallback: + ({ + Value id = const Value.absent(), + required int enfantId, + Value regleId = const Value.absent(), + Value recompenseId = const Value.absent(), + required int delta, + Value note = const Value.absent(), + Value creeLe = const Value.absent(), + Value annule = const Value.absent(), + }) => MouvementsCompanion.insert( + id: id, + enfantId: enfantId, + regleId: regleId, + recompenseId: recompenseId, + delta: delta, + note: note, + creeLe: creeLe, + annule: annule, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$MouvementsTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({enfantId = false, regleId = false, recompenseId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (enfantId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.enfantId, + referencedTable: $$MouvementsTableReferences + ._enfantIdTable(db), + referencedColumn: + $$MouvementsTableReferences + ._enfantIdTable(db) + .id, + ) + as T; + } + if (regleId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.regleId, + referencedTable: $$MouvementsTableReferences + ._regleIdTable(db), + referencedColumn: + $$MouvementsTableReferences + ._regleIdTable(db) + .id, + ) + as T; + } + if (recompenseId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.recompenseId, + referencedTable: $$MouvementsTableReferences + ._recompenseIdTable(db), + referencedColumn: + $$MouvementsTableReferences + ._recompenseIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$MouvementsTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $MouvementsTable, + Mouvement, + $$MouvementsTableFilterComposer, + $$MouvementsTableOrderingComposer, + $$MouvementsTableAnnotationComposer, + $$MouvementsTableCreateCompanionBuilder, + $$MouvementsTableUpdateCompanionBuilder, + (Mouvement, $$MouvementsTableReferences), + Mouvement, + PrefetchHooks Function({bool enfantId, bool regleId, bool recompenseId}) + >; +typedef $$ConfigTableCreateCompanionBuilder = + ConfigCompanion Function({ + required String cle, + required String valeur, + Value rowid, + }); +typedef $$ConfigTableUpdateCompanionBuilder = + ConfigCompanion Function({ + Value cle, + Value valeur, + Value rowid, + }); + +class $$ConfigTableFilterComposer + extends Composer<_$AppDatabase, $ConfigTable> { + $$ConfigTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get cle => $composableBuilder( + column: $table.cle, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get valeur => $composableBuilder( + column: $table.valeur, + builder: (column) => ColumnFilters(column), + ); +} + +class $$ConfigTableOrderingComposer + extends Composer<_$AppDatabase, $ConfigTable> { + $$ConfigTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get cle => $composableBuilder( + column: $table.cle, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get valeur => $composableBuilder( + column: $table.valeur, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ConfigTableAnnotationComposer + extends Composer<_$AppDatabase, $ConfigTable> { + $$ConfigTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get cle => + $composableBuilder(column: $table.cle, builder: (column) => column); + + GeneratedColumn get valeur => + $composableBuilder(column: $table.valeur, builder: (column) => column); +} + +class $$ConfigTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ConfigTable, + ConfigData, + $$ConfigTableFilterComposer, + $$ConfigTableOrderingComposer, + $$ConfigTableAnnotationComposer, + $$ConfigTableCreateCompanionBuilder, + $$ConfigTableUpdateCompanionBuilder, + (ConfigData, BaseReferences<_$AppDatabase, $ConfigTable, ConfigData>), + ConfigData, + PrefetchHooks Function() + > { + $$ConfigTableTableManager(_$AppDatabase db, $ConfigTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$ConfigTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$ConfigTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$ConfigTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value cle = const Value.absent(), + Value valeur = const Value.absent(), + Value rowid = const Value.absent(), + }) => ConfigCompanion(cle: cle, valeur: valeur, rowid: rowid), + createCompanionCallback: + ({ + required String cle, + required String valeur, + Value rowid = const Value.absent(), + }) => ConfigCompanion.insert( + cle: cle, + valeur: valeur, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ConfigTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ConfigTable, + ConfigData, + $$ConfigTableFilterComposer, + $$ConfigTableOrderingComposer, + $$ConfigTableAnnotationComposer, + $$ConfigTableCreateCompanionBuilder, + $$ConfigTableUpdateCompanionBuilder, + (ConfigData, BaseReferences<_$AppDatabase, $ConfigTable, ConfigData>), + ConfigData, + PrefetchHooks Function() + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$EnfantsTableTableManager get enfants => + $$EnfantsTableTableManager(_db, _db.enfants); + $$ReglesTableTableManager get regles => + $$ReglesTableTableManager(_db, _db.regles); + $$RecompensesTableTableManager get recompenses => + $$RecompensesTableTableManager(_db, _db.recompenses); + $$MouvementsTableTableManager get mouvements => + $$MouvementsTableTableManager(_db, _db.mouvements); + $$ConfigTableTableManager get config => + $$ConfigTableTableManager(_db, _db.config); +} diff --git a/mobile/lib/data/database/tables.dart b/mobile/lib/data/database/tables.dart new file mode 100644 index 0000000..85a0d0f --- /dev/null +++ b/mobile/lib/data/database/tables.dart @@ -0,0 +1,54 @@ +import 'package:drift/drift.dart'; + +class Enfants extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get prenom => text()(); + TextColumn get dateNaissance => text().nullable()(); + IntColumn get score => integer().withDefault(const Constant(0))(); + TextColumn get couleur => text().withDefault(const Constant('#6C63FF'))(); + IntColumn get ordre => integer().withDefault(const Constant(0))(); + + @override + List>>? get uniqueKeys => [ + {prenom}, + ]; +} + +class Regles extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get libelle => text()(); + IntColumn get points => integer()(); + TextColumn get icone => text().withDefault(const Constant('⭐'))(); + BoolColumn get actif => boolean().withDefault(const Constant(true))(); + IntColumn get ordre => integer().withDefault(const Constant(0))(); + TextColumn get famille => text().withDefault(const Constant('📋 Autre'))(); + IntColumn get familleOrdre => integer().withDefault(const Constant(99))(); +} + +class Mouvements extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get enfantId => integer().references(Enfants, #id)(); + IntColumn get regleId => integer().nullable().references(Regles, #id)(); + IntColumn get recompenseId => integer().nullable().references(Recompenses, #id)(); + IntColumn get delta => integer()(); + TextColumn get note => text().nullable()(); + DateTimeColumn get creeLe => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get annule => boolean().withDefault(const Constant(false))(); +} + +class Recompenses extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get libelle => text()(); + IntColumn get coutPoints => integer()(); + TextColumn get icone => text().withDefault(const Constant('🎁'))(); + BoolColumn get actif => boolean().withDefault(const Constant(true))(); + IntColumn get ordre => integer().withDefault(const Constant(0))(); +} + +class Config extends Table { + TextColumn get cle => text()(); + TextColumn get valeur => text()(); + + @override + Set>? get primaryKey => {cle}; +} diff --git a/mobile/lib/data/repositories/onboarding_repository.dart b/mobile/lib/data/repositories/onboarding_repository.dart new file mode 100644 index 0000000..831e832 --- /dev/null +++ b/mobile/lib/data/repositories/onboarding_repository.dart @@ -0,0 +1,29 @@ +import 'package:bcrypt/bcrypt.dart'; + +import '../database/app_database.dart'; + +class OnboardingRepository { + OnboardingRepository(this._db); + + final AppDatabase _db; + + Future isComplete() => _db.isOnboardingComplete(); + + Future finish({ + required String childName, + required String color, + required String pin, + required bool importDefaultRules, + }) async { + await _db.transaction(() async { + await _db.insertChild(prenom: childName.trim(), couleur: color); + await _db.setPinHash(BCrypt.hashpw(pin, BCrypt.gensalt())); + + if (importDefaultRules) { + await _db.importDefaultPack(); + } + + await _db.completeOnboarding(); + }); + } +} diff --git a/mobile/lib/data/seed/default_rewards.dart b/mobile/lib/data/seed/default_rewards.dart new file mode 100644 index 0000000..be86606 --- /dev/null +++ b/mobile/lib/data/seed/default_rewards.dart @@ -0,0 +1,31 @@ +// Récompenses par défaut — portées depuis web/src/db.js (version famille-v2). + +class DefaultRewardSeed { + const DefaultRewardSeed({ + required this.libelle, + required this.coutPoints, + required this.icone, + required this.ordre, + }); + + final String libelle; + final int coutPoints; + final String icone; + final int ordre; +} + +const rewardsVersion = 'famille-v2'; + +const defaultRewards = [ + DefaultRewardSeed(libelle: 'Un bonbon', coutPoints: 1, icone: '🍬', ordre: 1), + DefaultRewardSeed(libelle: '10 minutes de télé', coutPoints: 1, icone: '📺', ordre: 2), + DefaultRewardSeed(libelle: 'Une sucette', coutPoints: 2, icone: '🍭', ordre: 3), + DefaultRewardSeed(libelle: 'Neige', coutPoints: 3, icone: '🍫', ordre: 4), + DefaultRewardSeed(libelle: '10 minutes de jeu vidéo', coutPoints: 3, icone: '🎮', ordre: 5), + DefaultRewardSeed(libelle: 'Cahier de dessin neuf', coutPoints: 10, icone: '📓', ordre: 6), + DefaultRewardSeed(libelle: 'Jouet pas cher (< 10 €)', coutPoints: 50, icone: '🧸', ordre: 7), + DefaultRewardSeed(libelle: 'Un biscuit / cookie', coutPoints: 1, icone: '🍪', ordre: 8), + DefaultRewardSeed(libelle: 'Choisir la musique dans la voiture', coutPoints: 1, icone: '🎵', ordre: 9), + DefaultRewardSeed(libelle: 'Choisir le menu du repas', coutPoints: 5, icone: '🍕', ordre: 10), + DefaultRewardSeed(libelle: 'Une glace', coutPoints: 5, icone: '🍦', ordre: 11), +]; diff --git a/mobile/lib/data/seed/default_rules.dart b/mobile/lib/data/seed/default_rules.dart new file mode 100644 index 0000000..81d81a1 --- /dev/null +++ b/mobile/lib/data/seed/default_rules.dart @@ -0,0 +1,75 @@ +/// Règles par défaut — portées depuis web/src/db.js (version famille-v5), +/// généralisées pour l'app publique (sans références famille prototype). +class DefaultRuleSeed { + const DefaultRuleSeed({ + required this.libelle, + required this.points, + required this.icone, + required this.ordre, + required this.famille, + required this.familleOrdre, + }); + + final String libelle; + final int points; + final String icone; + final int ordre; + final String famille; + final int familleOrdre; +} + +const rulesVersion = 'famille-v5'; + +class RuleFamilies { + static const maison = '🏠 Maison & rangement'; + static const routine = '⏰ Matin, soir & école'; + static const fratrie = '👫 Fratrie & entraide'; + static const respect = '🙏 Respect & écoute'; + static const ecrans = '📱 Écrans'; + static const bonus = '🌟 Bonus'; +} + +const defaultRules = [ + // Maison & rangement + DefaultRuleSeed(libelle: 'Je mets la table', points: 1, icone: '🍽️', ordre: 1, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Débarrasser la table', points: 1, icone: '🧹', ordre: 2, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger le lave-vaisselle', points: 1, icone: '🫧', ordre: 3, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Nettoyer la table du séjour', points: 1, icone: '✨', ordre: 4, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger la salle de jeu', points: 2, icone: '🎮', ordre: 5, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger sa chambre', points: 1, icone: '🛏️', ordre: 6, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger ses habits propres', points: 1, icone: '👕', ordre: 7, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger la cabane', points: 1, icone: '🏕️', ordre: 8, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger les vélos (abri jardin)', points: 1, icone: '🚲', ordre: 9, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Ranger sur ordre des parents', points: 1, icone: '👨‍👩‍👧', ordre: 10, famille: RuleFamilies.maison, familleOrdre: 1), + DefaultRuleSeed(libelle: 'Piquer dans les placards', points: -2, icone: '🗄️', ordre: 11, famille: RuleFamilies.maison, familleOrdre: 1), + // Matin, soir & école + DefaultRuleSeed(libelle: "Habits prêts + s'habiller seul (à l'heure)", points: 1, icone: '⏰', ordre: 1, famille: RuleFamilies.routine, familleOrdre: 2), + DefaultRuleSeed(libelle: 'Se préparer pour le lit sans bagarre (< 15 min)', points: 1, icone: '🌙', ordre: 2, famille: RuleFamilies.routine, familleOrdre: 2), + DefaultRuleSeed(libelle: 'Devoirs faits sans rappel', points: 2, icone: '📚', ordre: 3, famille: RuleFamilies.routine, familleOrdre: 2), + DefaultRuleSeed(libelle: 'Activité calme manuelle 1h', points: 2, icone: '🎨', ordre: 4, famille: RuleFamilies.routine, familleOrdre: 2), + DefaultRuleSeed(libelle: 'Retard le matin', points: -1, icone: '⏰', ordre: 5, famille: RuleFamilies.routine, familleOrdre: 2), + // Fratrie & entraide + DefaultRuleSeed(libelle: "Aider son frère/sa sœur (sans qu'on demande)", points: 2, icone: '🤝', ordre: 1, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: 'Partager un jouet sans bagarre', points: 1, icone: '🎁', ordre: 2, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: 'Dire « pardon » tout seul', points: 1, icone: '🗣️', ordre: 3, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: 'Aider le plus jeune (lire, jouer calmement)', points: 1, icone: '👶', ordre: 4, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: 'Taper, frapper', points: -2, icone: '👊', ordre: 5, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: "Ne pas s'excuser auprès de frère/sœur", points: -2, icone: '😤', ordre: 6, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: 'Provocation volontaire de frère/sœur', points: -2, icone: '😈', ordre: 7, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: "Accuser l'autre à tort", points: -2, icone: '🗣️', ordre: 8, famille: RuleFamilies.fratrie, familleOrdre: 3), + DefaultRuleSeed(libelle: 'Casser / abîmer un jouet (volontairement)', points: -3, icone: '💔', ordre: 9, famille: RuleFamilies.fratrie, familleOrdre: 3), + // Respect & écoute + DefaultRuleSeed(libelle: 'Mentir à ses parents', points: -2, icone: '🤥', ordre: 1, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Dire des gros mots ou insultes', points: -1, icone: '🤬', ordre: 2, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Répondre mal / faire la tête', points: -1, icone: '🙄', ordre: 3, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Crier / hurler', points: -1, icone: '🗯️', ordre: 4, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Faire claquer une porte', points: -1, icone: '🚪', ordre: 5, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Tirer la langue / geste irrespectueux', points: -1, icone: '👅', ordre: 6, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Ne pas écouter une consigne (plusieurs reprises)', points: -2, icone: '👂', ordre: 7, famille: RuleFamilies.respect, familleOrdre: 4), + DefaultRuleSeed(libelle: 'Partir sans permission (jardin, rue)', points: -3, icone: '🏃', ordre: 8, famille: RuleFamilies.respect, familleOrdre: 4), + // Écrans + DefaultRuleSeed(libelle: 'Se lever pour la télé sans autorisation (semaine)', points: -3, icone: '📺', ordre: 1, famille: RuleFamilies.ecrans, familleOrdre: 5), + DefaultRuleSeed(libelle: 'Utiliser un téléphone sans autorisation', points: -5, icone: '📱', ordre: 2, famille: RuleFamilies.ecrans, familleOrdre: 5), + // Bonus + DefaultRuleSeed(libelle: 'Bon point bonus — je suis content !', points: 1, icone: '🌟', ordre: 1, famille: RuleFamilies.bonus, familleOrdre: 6), +]; diff --git a/mobile/lib/features/dashboard/dashboard_screen.dart b/mobile/lib/features/dashboard/dashboard_screen.dart new file mode 100644 index 0000000..289d6e9 --- /dev/null +++ b/mobile/lib/features/dashboard/dashboard_screen.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../core/theme/app_theme.dart'; +import '../../providers/database_provider.dart'; + +class DashboardScreen extends ConsumerWidget { + const DashboardScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final childrenAsync = ref.watch(childrenProvider); + + return DecoratedBox( + decoration: const BoxDecoration(gradient: AppTheme.primaryGradient), + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + title: const Text('Bons Points'), + actions: [ + IconButton( + icon: const Icon(Icons.lock_outline), + tooltip: 'Espace parent', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Espace parent — bientôt disponible'), + ), + ); + }, + ), + ], + ), + body: SafeArea( + child: childrenAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: Colors.white), + ), + error: (e, _) => Center( + child: Text( + 'Erreur de chargement', + style: TextStyle(color: Colors.white.withValues(alpha: 0.9)), + ), + ), + data: (children) { + if (children.isEmpty) { + return const Center( + child: Text( + 'Aucun enfant', + style: TextStyle(color: Colors.white), + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: children.length, + itemBuilder: (context, index) { + final child = children[index]; + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + leading: CircleAvatar( + radius: 28, + backgroundColor: AppTheme.parseColor(child.couleur), + child: Text( + child.prenom[0].toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + title: Text( + child.prenom, + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 18, + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${child.score}', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: AppTheme.parseColor(child.couleur), + ), + ), + const SizedBox(width: 4), + const Text('⭐', style: TextStyle(fontSize: 20)), + const SizedBox(width: 8), + Icon( + Icons.chevron_right, + color: Colors.grey.shade400, + ), + ], + ), + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Fiche ${child.prenom} — bientôt disponible', + ), + ), + ); + }, + ), + ); + }, + ); + }, + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/onboarding/onboarding_flow.dart b/mobile/lib/features/onboarding/onboarding_flow.dart new file mode 100644 index 0000000..85ea8f2 --- /dev/null +++ b/mobile/lib/features/onboarding/onboarding_flow.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../providers/database_provider.dart'; +import '../dashboard/dashboard_screen.dart'; +import 'onboarding_state.dart'; +import 'steps/child_step.dart'; +import 'steps/pin_step.dart'; +import 'steps/rules_step.dart'; +import 'steps/welcome_step.dart'; + +class OnboardingFlow extends ConsumerStatefulWidget { + const OnboardingFlow({super.key}); + + @override + ConsumerState createState() => _OnboardingFlowState(); +} + +class _OnboardingFlowState extends ConsumerState { + final _pageController = PageController(); + bool _isSaving = false; + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + void _goTo(int page) { + _pageController.animateToPage( + page, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + Future _finish() async { + final draft = ref.read(onboardingDraftProvider); + if (draft.childName.trim().isEmpty) { + _showError('Indiquez le prénom de l\'enfant.'); + _goTo(1); + return; + } + if (draft.pin.length < 4) { + _showError('Le PIN doit contenir au moins 4 chiffres.'); + _goTo(2); + return; + } + if (draft.pin != draft.pinConfirm) { + _showError('Les deux PIN ne correspondent pas.'); + _goTo(2); + return; + } + + setState(() => _isSaving = true); + try { + final repo = ref.read(onboardingRepositoryProvider); + await repo.finish( + childName: draft.childName, + color: draft.color, + pin: draft.pin, + importDefaultRules: draft.importDefaultRules, + ); + ref.invalidate(onboardingCompleteProvider); + ref.invalidate(childrenProvider); + + if (!mounted) return; + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const DashboardScreen()), + ); + } catch (e) { + _showError('Erreur lors de l\'enregistrement. Réessayez.'); + } finally { + if (mounted) setState(() => _isSaving = false); + } + } + + void _showError(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), backgroundColor: Colors.red.shade700), + ); + } + + @override + Widget build(BuildContext context) { + return PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (_) {}, + children: [ + WelcomeStep(onNext: () => _goTo(1)), + ChildStep( + step: 2, + onBack: () => _goTo(0), + onNext: () { + final name = ref.read(onboardingDraftProvider).childName.trim(); + if (name.isEmpty) { + _showError('Indiquez le prénom de l\'enfant.'); + return; + } + _goTo(2); + }, + ), + PinStep( + step: 3, + onBack: () => _goTo(1), + onNext: () { + final draft = ref.read(onboardingDraftProvider); + if (draft.pin.length < 4) { + _showError('Le PIN doit contenir au moins 4 chiffres.'); + return; + } + if (draft.pin != draft.pinConfirm) { + _showError('Les deux PIN ne correspondent pas.'); + return; + } + _goTo(3); + }, + ), + RulesStep( + step: 4, + isSaving: _isSaving, + onBack: () => _goTo(2), + onFinish: _finish, + ), + ], + ); + } +} diff --git a/mobile/lib/features/onboarding/onboarding_state.dart b/mobile/lib/features/onboarding/onboarding_state.dart new file mode 100644 index 0000000..1ccc317 --- /dev/null +++ b/mobile/lib/features/onboarding/onboarding_state.dart @@ -0,0 +1,49 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class OnboardingDraft { + const OnboardingDraft({ + this.childName = '', + this.color = '#6C63FF', + this.pin = '', + this.pinConfirm = '', + this.importDefaultRules = true, + }); + + final String childName; + final String color; + final String pin; + final String pinConfirm; + final bool importDefaultRules; + + OnboardingDraft copyWith({ + String? childName, + String? color, + String? pin, + String? pinConfirm, + bool? importDefaultRules, + }) { + return OnboardingDraft( + childName: childName ?? this.childName, + color: color ?? this.color, + pin: pin ?? this.pin, + pinConfirm: pinConfirm ?? this.pinConfirm, + importDefaultRules: importDefaultRules ?? this.importDefaultRules, + ); + } +} + +class OnboardingDraftNotifier extends StateNotifier { + OnboardingDraftNotifier() : super(const OnboardingDraft()); + + void setChildName(String value) => state = state.copyWith(childName: value); + void setColor(String value) => state = state.copyWith(color: value); + void setPin(String value) => state = state.copyWith(pin: value); + void setPinConfirm(String value) => state = state.copyWith(pinConfirm: value); + void setImportDefaultRules(bool value) => + state = state.copyWith(importDefaultRules: value); +} + +final onboardingDraftProvider = + StateNotifierProvider( + (ref) => OnboardingDraftNotifier(), +); diff --git a/mobile/lib/features/onboarding/steps/child_step.dart b/mobile/lib/features/onboarding/steps/child_step.dart new file mode 100644 index 0000000..35f7065 --- /dev/null +++ b/mobile/lib/features/onboarding/steps/child_step.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../core/theme/app_theme.dart'; +import '../onboarding_state.dart'; +import '../widgets/onboarding_scaffold.dart'; + +class ChildStep extends ConsumerStatefulWidget { + const ChildStep({ + super.key, + required this.step, + required this.onBack, + required this.onNext, + }); + + final int step; + final VoidCallback onBack; + final VoidCallback onNext; + + @override + ConsumerState createState() => _ChildStepState(); +} + +class _ChildStepState extends ConsumerState { + late final TextEditingController _nameController; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController( + text: ref.read(onboardingDraftProvider).childName, + ); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final draft = ref.watch(onboardingDraftProvider); + final notifier = ref.read(onboardingDraftProvider.notifier); + + return OnboardingScaffold( + step: widget.step, + title: 'Premier enfant', + subtitle: 'Créez le profil de votre enfant', + child: Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + autofocus: true, + controller: _nameController, + textCapitalization: TextCapitalization.words, + decoration: const InputDecoration( + labelText: 'Prénom', + hintText: 'Ex. Léa', + ), + onChanged: notifier.setChildName, + ), + const SizedBox(height: 24), + Text( + 'Couleur', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 12), + Wrap( + spacing: 12, + runSpacing: 12, + children: AppTheme.childColors.map((hex) { + final selected = draft.color == hex; + return GestureDetector( + onTap: () => notifier.setColor(hex), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + width: 48, + height: 48, + decoration: BoxDecoration( + color: AppTheme.parseColor(hex), + shape: BoxShape.circle, + border: Border.all( + color: selected ? Colors.black87 : Colors.transparent, + width: 3, + ), + boxShadow: selected + ? [ + BoxShadow( + color: AppTheme.parseColor(hex) + .withValues(alpha: 0.5), + blurRadius: 8, + ), + ] + : null, + ), + child: selected + ? const Icon(Icons.check, color: Colors.white) + : null, + ), + ); + }).toList(), + ), + const Spacer(), + Center( + child: CircleAvatar( + radius: 40, + backgroundColor: AppTheme.parseColor(draft.color), + child: Text( + draft.childName.isNotEmpty + ? draft.childName[0].toUpperCase() + : '?', + style: const TextStyle( + color: Colors.white, + fontSize: 32, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + bottom: Row( + children: [ + TextButton(onPressed: widget.onBack, child: const Text('Retour')), + const Spacer(), + FilledButton(onPressed: widget.onNext, child: const Text('Suivant')), + ], + ), + ); + } +} diff --git a/mobile/lib/features/onboarding/steps/pin_step.dart b/mobile/lib/features/onboarding/steps/pin_step.dart new file mode 100644 index 0000000..619d36b --- /dev/null +++ b/mobile/lib/features/onboarding/steps/pin_step.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../onboarding_state.dart'; +import '../widgets/onboarding_scaffold.dart'; + +class PinStep extends ConsumerStatefulWidget { + const PinStep({ + super.key, + required this.step, + required this.onBack, + required this.onNext, + }); + + final int step; + final VoidCallback onBack; + final VoidCallback onNext; + + @override + ConsumerState createState() => _PinStepState(); +} + +class _PinStepState extends ConsumerState { + final _pinController = TextEditingController(); + final _confirmController = TextEditingController(); + bool _obscurePin = true; + bool _obscureConfirm = true; + + @override + void dispose() { + _pinController.dispose(); + _confirmController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final notifier = ref.read(onboardingDraftProvider.notifier); + + return OnboardingScaffold( + step: widget.step, + title: 'PIN parent', + subtitle: 'Protège l\'espace parent (4 chiffres minimum)', + child: Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + controller: _pinController, + obscureText: _obscurePin, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(8), + ], + decoration: InputDecoration( + labelText: 'Choisir un PIN', + suffixIcon: IconButton( + icon: Icon( + _obscurePin ? Icons.visibility : Icons.visibility_off, + ), + onPressed: () => + setState(() => _obscurePin = !_obscurePin), + ), + ), + onChanged: notifier.setPin, + ), + const SizedBox(height: 16), + TextField( + controller: _confirmController, + obscureText: _obscureConfirm, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(8), + ], + decoration: InputDecoration( + labelText: 'Confirmer le PIN', + suffixIcon: IconButton( + icon: Icon( + _obscureConfirm + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () => + setState(() => _obscureConfirm = !_obscureConfirm), + ), + ), + onChanged: notifier.setPinConfirm, + ), + const SizedBox(height: 20), + Text( + 'Ce PIN sera demandé pour accéder à l\'espace parent ' + '(appliquer des règles, modifier les réglages).', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + ), + bottom: Row( + children: [ + TextButton(onPressed: widget.onBack, child: const Text('Retour')), + const Spacer(), + FilledButton(onPressed: widget.onNext, child: const Text('Suivant')), + ], + ), + ); + } +} diff --git a/mobile/lib/features/onboarding/steps/rules_step.dart b/mobile/lib/features/onboarding/steps/rules_step.dart new file mode 100644 index 0000000..19f174b --- /dev/null +++ b/mobile/lib/features/onboarding/steps/rules_step.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../data/seed/default_rewards.dart'; +import '../../../data/seed/default_rules.dart'; +import '../onboarding_state.dart'; +import '../widgets/onboarding_scaffold.dart'; + +class RulesStep extends ConsumerWidget { + const RulesStep({ + super.key, + required this.step, + required this.isSaving, + required this.onBack, + required this.onFinish, + }); + + final int step; + final bool isSaving; + final VoidCallback onBack; + final VoidCallback onFinish; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final draft = ref.watch(onboardingDraftProvider); + final notifier = ref.read(onboardingDraftProvider.notifier); + + return OnboardingScaffold( + step: step, + title: 'Règles par défaut', + subtitle: 'Pack « Maison & école » prêt à l\'emploi', + child: Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Importer le pack par défaut'), + subtitle: Text( + '${defaultRules.length} règles et ' + '${defaultRewards.length} récompenses', + ), + value: draft.importDefaultRules, + onChanged: isSaving ? null : notifier.setImportDefaultRules, + ), + const Divider(), + Expanded( + child: draft.importDefaultRules + ? ListView( + children: [ + Text( + 'Familles de règles incluses :', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + ..._ruleFamilies.map( + (f) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text('• $f'), + ), + ), + const SizedBox(height: 16), + Text( + 'Vous pourrez modifier les règles plus tard ' + 'depuis l\'espace parent (Premium pour ' + 'l\'édition complète).', + style: Theme.of(context).textTheme.bodySmall + ?.copyWith(color: Colors.grey.shade600), + ), + ], + ) + : Center( + child: Text( + 'Vous partirez sans règles ni récompenses. ' + 'Vous pourrez les ajouter manuellement plus tard.', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ), + ], + ), + ), + ), + bottom: Row( + children: [ + TextButton( + onPressed: isSaving ? null : onBack, + child: const Text('Retour'), + ), + const Spacer(), + FilledButton( + onPressed: isSaving ? null : onFinish, + child: isSaving + ? const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('C\'est parti !'), + ), + ], + ), + ); + } +} + +const _ruleFamilies = [ + RuleFamilies.maison, + RuleFamilies.routine, + RuleFamilies.fratrie, + RuleFamilies.respect, + RuleFamilies.ecrans, + RuleFamilies.bonus, +]; diff --git a/mobile/lib/features/onboarding/steps/welcome_step.dart b/mobile/lib/features/onboarding/steps/welcome_step.dart new file mode 100644 index 0000000..95ab432 --- /dev/null +++ b/mobile/lib/features/onboarding/steps/welcome_step.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; + +import '../widgets/onboarding_scaffold.dart'; + +class WelcomeStep extends StatelessWidget { + const WelcomeStep({super.key, required this.onNext}); + + final VoidCallback onNext; + + @override + Widget build(BuildContext context) { + return OnboardingScaffold( + step: 1, + title: 'Bons Points', + subtitle: 'Simple, privé, 100 % sur votre téléphone', + child: Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('⭐', style: TextStyle(fontSize: 56)), + const SizedBox(height: 20), + _FeatureRow( + icon: Icons.phone_android, + text: 'Aucun compte, aucun cloud', + ), + const SizedBox(height: 12), + _FeatureRow( + icon: Icons.wifi_off, + text: 'Fonctionne hors ligne', + ), + const SizedBox(height: 12), + _FeatureRow( + icon: Icons.family_restroom, + text: 'Bons points pour toute la famille', + ), + const SizedBox(height: 12), + _FeatureRow( + icon: Icons.lock_outline, + text: 'Espace parent protégé par PIN', + ), + ], + ), + ), + ), + bottom: FilledButton( + onPressed: onNext, + child: const Text('Commencer'), + ), + ); + } +} + +class _FeatureRow extends StatelessWidget { + const _FeatureRow({required this.icon, required this.text}); + + final IconData icon; + final String text; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon(icon, color: const Color(0xFF667EEA)), + const SizedBox(width: 12), + Expanded( + child: Text( + text, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/features/onboarding/widgets/onboarding_scaffold.dart b/mobile/lib/features/onboarding/widgets/onboarding_scaffold.dart new file mode 100644 index 0000000..fb1dae9 --- /dev/null +++ b/mobile/lib/features/onboarding/widgets/onboarding_scaffold.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +import '../../../core/theme/app_theme.dart'; + +class OnboardingScaffold extends StatelessWidget { + const OnboardingScaffold({ + super.key, + required this.title, + required this.subtitle, + required this.child, + this.bottom, + this.step, + this.totalSteps = 4, + }); + + final String title; + final String subtitle; + final Widget child; + final Widget? bottom; + final int? step; + final int totalSteps; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: const BoxDecoration(gradient: AppTheme.primaryGradient), + child: Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (step != null) ...[ + const SizedBox(height: 8), + Row( + children: List.generate(totalSteps, (index) { + final active = index < step!; + return Expanded( + child: Container( + height: 4, + margin: EdgeInsets.only( + right: index < totalSteps - 1 ? 6 : 0, + ), + decoration: BoxDecoration( + color: active + ? Colors.white + : Colors.white.withValues(alpha: 0.35), + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + ), + ], + const SizedBox(height: 24), + Text( + title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + subtitle, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.white.withValues(alpha: 0.9), + ), + ), + const SizedBox(height: 24), + Expanded(child: child), + if (bottom != null) ...[ + const SizedBox(height: 16), + bottom!, + const SizedBox(height: 16), + ], + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart new file mode 100644 index 0000000..3e3f5f6 --- /dev/null +++ b/mobile/lib/main.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'app.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + runApp( + const ProviderScope( + child: BonpointApp(), + ), + ); +} diff --git a/mobile/lib/providers/database_provider.dart b/mobile/lib/providers/database_provider.dart new file mode 100644 index 0000000..48bd0f8 --- /dev/null +++ b/mobile/lib/providers/database_provider.dart @@ -0,0 +1,24 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../data/database/app_database.dart'; +import '../data/repositories/onboarding_repository.dart'; + +final databaseProvider = Provider((ref) { + final db = AppDatabase(); + ref.onDispose(db.close); + return db; +}); + +final onboardingRepositoryProvider = Provider((ref) { + return OnboardingRepository(ref.watch(databaseProvider)); +}); + +final onboardingCompleteProvider = FutureProvider((ref) async { + final repo = ref.watch(onboardingRepositoryProvider); + return repo.isComplete(); +}); + +final childrenProvider = FutureProvider>((ref) async { + final db = ref.watch(databaseProvider); + return db.listChildren(); +}); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock new file mode 100644 index 0000000..76dcc63 --- /dev/null +++ b/mobile/pubspec.lock @@ -0,0 +1,741 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c + url: "https://pub.dev" + source: hosted + version: "99.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + url: "https://pub.dev" + source: hosted + version: "12.1.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + bcrypt: + dependency: "direct main" + description: + name: bcrypt + sha256: "6073a700cbbc59f1d4ab27cd532755e3de5e676c4941f535f351374df849270b" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + url: "https://pub.dev" + source: hosted + version: "4.0.6" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + url: "https://pub.dev" + source: hosted + version: "2.15.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98 + url: "https://pub.dev" + source: hosted + version: "3.1.8" + drift: + dependency: "direct main" + description: + name: drift + sha256: "6cc0b623c0e83f7080524d8396e9301b1d78b9c66a4fdceeb0f798211303254c" + url: "https://pub.dev" + source: hosted + version: "2.34.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: "9cfff1576b49725da0d32c040651a41ae195e8c4af8d8da301593e41d7abc2f7" + url: "https://pub.dev" + source: hosted + version: "2.34.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" + source: hosted + version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 + url: "https://pub.dev" + source: hosted + version: "0.19.1" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189" + url: "https://pub.dev" + source: hosted + version: "3.3.2" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad + url: "https://pub.dev" + source: hosted + version: "0.5.42" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "40bdddb306a727be9ce510bd2d2b9a6c9db6c586d846ef7b22e3990a2b24f02d" + url: "https://pub.dev" + source: hosted + version: "0.44.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.12.2 <4.0.0" + flutter: ">=3.38.4" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml new file mode 100644 index 0000000..147cf46 --- /dev/null +++ b/mobile/pubspec.yaml @@ -0,0 +1,30 @@ +name: bonpoint +description: "Bons Points — système de bons points familial, 100 % local." +publish_to: 'none' + +version: 1.0.0+1 + +environment: + sdk: ^3.12.2 + +dependencies: + flutter: + sdk: flutter + + cupertino_icons: ^1.0.8 + flutter_riverpod: ^2.6.1 + drift: ^2.26.0 + sqlite3_flutter_libs: ^0.5.31 + path_provider: ^2.1.5 + path: ^1.9.1 + bcrypt: ^1.1.3 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + drift_dev: ^2.26.0 + build_runner: ^2.4.15 + +flutter: + uses-material-design: true diff --git a/mobile/test/score_logic_test.dart b/mobile/test/score_logic_test.dart new file mode 100644 index 0000000..d669052 --- /dev/null +++ b/mobile/test/score_logic_test.dart @@ -0,0 +1,60 @@ +import 'package:bonpoint/data/database/app_database.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late AppDatabase db; + + setUp(() async { + db = AppDatabase.forTesting(NativeDatabase.memory()); + await db.importDefaultPack(); + }); + + tearDown(() async { + await db.close(); + }); + + test('appliquerRegle respecte le plancher à 0', () async { + final childId = await db.insertChild( + prenom: 'Test', + couleur: '#6C63FF', + ); + + final rules = await db.select(db.regles).get(); + final positiveRule = rules.firstWhere((r) => r.points == 1); + final negativeRule = rules.firstWhere((r) => r.points == -2); + + await db.applyRule(childId, positiveRule.id); + // Score = 1, puis règle -2 → score 0, delta réel -1 + await db.applyRule(childId, negativeRule.id); + // Score déjà 0, règle -2 → score 0, delta réel 0 + await db.applyRule(childId, negativeRule.id); + + final child = await (db.select(db.enfants) + ..where((e) => e.id.equals(childId))) + .getSingle(); + + expect(child.score, 0); + + final movements = await db.select(db.mouvements).get(); + expect(movements.length, 3); + expect(movements[0].delta, 1); + expect(movements[1].delta, -1); + expect(movements[2].delta, 0); + }); + + test('delta enregistré est le delta réel (pas le delta théorique)', () async { + final childId = await db.insertChild( + prenom: 'Test', + couleur: '#6C63FF', + ); + + final rules = await db.select(db.regles).get(); + final rule = rules.firstWhere((r) => r.points == -2); + + await db.applyRule(childId, rule.id); + + final movement = await db.select(db.mouvements).getSingle(); + expect(movement.delta, 0); + }); +} diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart new file mode 100644 index 0000000..6cd3bec --- /dev/null +++ b/mobile/test/widget_test.dart @@ -0,0 +1,23 @@ +import 'package:bonpoint/app.dart'; +import 'package:bonpoint/providers/database_provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Affiche l\'écran de bienvenue au premier lancement', + (tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + onboardingCompleteProvider.overrideWith((ref) async => false), + ], + child: const BonpointApp(), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(find.text('Bons Points'), findsWidgets); + expect(find.text('Commencer'), findsOneWidget); + }); +}