From 35a2ddf4f16f5410614b0153ee26d7f453461747 Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 21 Jan 2024 14:14:56 +0300 Subject: [PATCH 01/18] init mobile_app directory --- mobile_app/.gitignore | 43 ++ mobile_app/.metadata | 33 + mobile_app/README.md | 16 + mobile_app/analysis_options.yaml | 28 + mobile_app/android/.gitignore | 13 + mobile_app/android/app/build.gradle | 67 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 33 + .../com/example/mobile_app/MainActivity.kt | 6 + .../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_app/android/build.gradle | 30 + mobile_app/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + mobile_app/android/settings.gradle | 29 + mobile_app/ios/.gitignore | 34 + mobile_app/ios/Flutter/AppFrameworkInfo.plist | 26 + mobile_app/ios/Flutter/Debug.xcconfig | 1 + mobile_app/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 614 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + mobile_app/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + mobile_app/ios/Runner/Info.plist | 49 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + mobile_app/ios/RunnerTests/RunnerTests.swift | 12 + mobile_app/lib/main.dart | 125 ++++ mobile_app/pubspec.lock | 188 ++++++ mobile_app/pubspec.yaml | 90 +++ mobile_app/test/widget_test.dart | 30 + 66 files changed, 1921 insertions(+) create mode 100644 mobile_app/.gitignore create mode 100644 mobile_app/.metadata create mode 100644 mobile_app/README.md create mode 100644 mobile_app/analysis_options.yaml create mode 100644 mobile_app/android/.gitignore create mode 100644 mobile_app/android/app/build.gradle create mode 100644 mobile_app/android/app/src/debug/AndroidManifest.xml create mode 100644 mobile_app/android/app/src/main/AndroidManifest.xml create mode 100644 mobile_app/android/app/src/main/kotlin/com/example/mobile_app/MainActivity.kt create mode 100644 mobile_app/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 mobile_app/android/app/src/main/res/drawable/launch_background.xml create mode 100644 mobile_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 mobile_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 mobile_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 mobile_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 mobile_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 mobile_app/android/app/src/main/res/values-night/styles.xml create mode 100644 mobile_app/android/app/src/main/res/values/styles.xml create mode 100644 mobile_app/android/app/src/profile/AndroidManifest.xml create mode 100644 mobile_app/android/build.gradle create mode 100644 mobile_app/android/gradle.properties create mode 100644 mobile_app/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 mobile_app/android/settings.gradle create mode 100644 mobile_app/ios/.gitignore create mode 100644 mobile_app/ios/Flutter/AppFrameworkInfo.plist create mode 100644 mobile_app/ios/Flutter/Debug.xcconfig create mode 100644 mobile_app/ios/Flutter/Release.xcconfig create mode 100644 mobile_app/ios/Runner.xcodeproj/project.pbxproj create mode 100644 mobile_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 mobile_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 mobile_app/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 mobile_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 mobile_app/ios/Runner/AppDelegate.swift create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 mobile_app/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 mobile_app/ios/Runner/Base.lproj/Main.storyboard create mode 100644 mobile_app/ios/Runner/Info.plist create mode 100644 mobile_app/ios/Runner/Runner-Bridging-Header.h create mode 100644 mobile_app/ios/RunnerTests/RunnerTests.swift create mode 100644 mobile_app/lib/main.dart create mode 100644 mobile_app/pubspec.lock create mode 100644 mobile_app/pubspec.yaml create mode 100644 mobile_app/test/widget_test.dart diff --git a/mobile_app/.gitignore b/mobile_app/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/mobile_app/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +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 +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# 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_app/.metadata b/mobile_app/.metadata new file mode 100644 index 0000000..9b1ffbe --- /dev/null +++ b/mobile_app/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + - platform: android + create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + - platform: ios + create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 + + # 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_app/README.md b/mobile_app/README.md new file mode 100644 index 0000000..1dfe888 --- /dev/null +++ b/mobile_app/README.md @@ -0,0 +1,16 @@ +# mobile_app + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/mobile_app/analysis_options.yaml b/mobile_app/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/mobile_app/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_app/android/.gitignore b/mobile_app/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/mobile_app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/mobile_app/android/app/build.gradle b/mobile_app/android/app/build.gradle new file mode 100644 index 0000000..178fffd --- /dev/null +++ b/mobile_app/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.mobile_app" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.mobile_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + 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.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/mobile_app/android/app/src/debug/AndroidManifest.xml b/mobile_app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile_app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile_app/android/app/src/main/AndroidManifest.xml b/mobile_app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..aaf04d5 --- /dev/null +++ b/mobile_app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/mobile_app/android/app/src/main/kotlin/com/example/mobile_app/MainActivity.kt b/mobile_app/android/app/src/main/kotlin/com/example/mobile_app/MainActivity.kt new file mode 100644 index 0000000..ee1330d --- /dev/null +++ b/mobile_app/android/app/src/main/kotlin/com/example/mobile_app/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.mobile_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/mobile_app/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile_app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/mobile_app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile_app/android/app/src/main/res/drawable/launch_background.xml b/mobile_app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/mobile_app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile_app/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_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile_app/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_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile_app/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_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile_app/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_app/android/app/src/main/res/values-night/styles.xml b/mobile_app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/mobile_app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile_app/android/app/src/main/res/values/styles.xml b/mobile_app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/mobile_app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile_app/android/app/src/profile/AndroidManifest.xml b/mobile_app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile_app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile_app/android/build.gradle b/mobile_app/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/mobile_app/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/mobile_app/android/gradle.properties b/mobile_app/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/mobile_app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/mobile_app/android/gradle/wrapper/gradle-wrapper.properties b/mobile_app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/mobile_app/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-7.5-all.zip diff --git a/mobile_app/android/settings.gradle b/mobile_app/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/mobile_app/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/mobile_app/ios/.gitignore b/mobile_app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/mobile_app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobile_app/ios/Flutter/AppFrameworkInfo.plist b/mobile_app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/mobile_app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/mobile_app/ios/Flutter/Debug.xcconfig b/mobile_app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile_app/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile_app/ios/Flutter/Release.xcconfig b/mobile_app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile_app/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile_app/ios/Runner.xcodeproj/project.pbxproj b/mobile_app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7039396 --- /dev/null +++ b/mobile_app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobileApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobileApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobileApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobileApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobileApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.mobileApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/mobile_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile_app/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile_app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/mobile_app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile_app/ios/Runner/AppDelegate.swift b/mobile_app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/mobile_app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_xN#0001NP)t-s|Ns9~ z#rXRE|M&d=0au&!`~QyF`q}dRnBDt}*!qXo`c{v z{Djr|@Adh0(D_%#_&mM$D6{kE_x{oE{l@J5@%H*?%=t~i_`ufYOPkAEn!pfkr2$fs z652Tz0001XNklqeeKN4RM4i{jKqmiC$?+xN>3Apn^ z0QfuZLym_5b<*QdmkHjHlj811{If)dl(Z2K0A+ekGtrFJb?g|wt#k#pV-#A~bK=OT ts8>{%cPtyC${m|1#B1A6#u!Q;umknL1chzTM$P~L002ovPDHLkV1lTfnu!1a literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 zcmV;526g#~P){YQnis^a@{&-nmRmq)<&%Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b zE>q9${Lb$D&h7k=+4=!ek^n+`0zq>LL1O?lVyea53S5x`Nqqo2YyeuIrQrJj9XjOp z{;T5qbj3}&1vg1VK~#9!?b~^C5-}JC@Pyrv-6dSEqJqT}#j9#dJ@GzT@B8}x zU&J@bBI>f6w6en+CeI)3^kC*U?}X%OD8$Fd$H&LV$H&LV$H&LV#|K5~mLYf|VqzOc zkc7qL~0sOYuM{tG`rYEDV{DWY`Z8&)kW*hc2VkBuY+^Yx&92j&StN}Wp=LD zxoGxXw6f&8sB^u})h@b@z0RBeD`K7RMR9deyL(ZJu#39Z>rT)^>v}Khq8U-IbIvT> z?4pV9qGj=2)TNH3d)=De<+^w;>S7m_eFKTvzeaBeir45xY!^m!FmxnljbSS_3o=g( z->^wC9%qkR{kbGnW8MfFew_o9h3(r55Is`L$8KI@d+*%{=Nx+FXJ98L0PjFIu;rGnnfY zn1R5Qnp<{Jq0M1vX=X&F8gtLmcWv$1*M@4ZfF^9``()#hGTeKeP`1!iED ztNE(TN}M5}3Bbc*d=FIv`DNv&@|C6yYj{sSqUj5oo$#*0$7pu|Dd2TLI>t5%I zIa4Dvr(iayb+5x=j*Vum9&irk)xV1`t509lnPO0%skL8_1c#Xbamh(2@f?4yUI zhhuT5<#8RJhGz4%b$`PJwKPAudsm|at?u;*hGgnA zU1;9gnxVBC)wA(BsB`AW54N{|qmikJR*%x0c`{LGsSfa|NK61pYH(r-UQ4_JXd!Rsz)=k zL{GMc5{h138)fF5CzHEDM>+FqY)$pdN3}Ml+riTgJOLN0F*Vh?{9ESR{SVVg>*>=# zix;VJHPtvFFCRY$Ks*F;VX~%*r9F)W`PmPE9F!(&s#x07n2<}?S{(ygpXgX-&B&OM zONY&BRQ(#%0%jeQs?oJ4P!p*R98>qCy5p8w>_gpuh39NcOlp)(wOoz0sY-Qz55eB~ z7OC-fKBaD1sE3$l-6QgBJO!n?QOTza`!S_YK z_v-lm^7{VO^8Q@M_^8F)09Ki6%=s?2_5eupee(w1FB%aqSweusQ-T+CH0Xt{` zFjMvW{@C&TB)k25()nh~_yJ9coBRL(0oO@HK~z}7?bm5j;y@69;bvlHb2tf!$ReA~x{22wTq550 z?f?Hnw(;m3ip30;QzdV~7pi!wyMYhDtXW#cO7T>|f=bdFhu+F!zMZ2UFj;GUKX7tI z;hv3{q~!*pMj75WP_c}>6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FaF{8 z;u`Mw0ly(uE>*CgQYv{be6ab2LWhlaH1^iLIM{olnag$78^Fd}%dR7;JECQ+hmk|o z!u2&!3MqPfP5ChDSkFSH8F2WVOEf0(E_M(JL17G}Y+fg0_IuW%WQ zG(mG&u?|->YSdk0;8rc{yw2@2Z&GA}z{Wb91Ooz9VhA{b2DYE7RmG zjL}?eq#iX%3#k;JWMx_{^2nNax`xPhByFiDX+a7uTGU|otOvIAUy|dEKkXOm-`aWS z27pUzD{a)Ct<6p{{3)+lq@i`t@%>-wT4r?*S}k)58e09WZYP0{{R3FC5Sl00039P)t-s|Ns9~ z#rP?<_5oL$Q^olD{r_0T`27C={r>*`|Nj71npVa5OTzc(_WfbW_({R{p56NV{r*M2 z_xt?)2V0#0NsfV0u>{42ctGP(8vQj-Btk1n|O0ZD=YLwd&R{Ko41Gr9H= zY@z@@bOAMB5Ltl$E>bJJ{>JP30ZxkmI%?eW{k`b?Wy<&gOo;dS`~CR$Vwb@XWtR|N zi~t=w02?-0&j0TD{>bb6sNwsK*!p?V`RMQUl(*DVjk-9Cx+-z1KXab|Ka2oXhX5f% z`$|e!000AhNklrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`? zTG`AHia671e^vgmp!llKp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?hz91 z7p83F3%LVu9;S$tSI$C^%^yud1dfTM_6p2|+5Ejp$bd`GDvbR|xit>i!ZD&F>@CJrPmu*UjD&?DfZs=$@e3FQA(vNiU+$A*%a} z?`XcG2jDxJ_ZQ#Md`H{4Lpf6QBDp81_KWZ6Tk#yCy1)32zO#3<7>b`eT7UyYH1eGz z;O(rH$=QR*L%%ZcBpc=eGua?N55nD^K(8<#gl2+pN_j~b2MHs4#mcLmv%DkspS-3< zpI1F=^9siI0s-;IN_IrA;5xm~3?3!StX}pUv0vkxMaqm+zxrg7X7(I&*N~&dEd0kD z-FRV|g=|QuUsuh>-xCI}vD2imzYIOIdcCVV=$Bz@*u0+Bs<|L^)32nN*=wu3n%Ynw z@1|eLG>!8ruU1pFXUfb`j>(=Gy~?Rn4QJ-c3%3T|(Frd!bI`9u&zAnyFYTqlG#&J7 zAkD(jpw|oZLNiA>;>hgp1KX7-wxC~31II47gc zHcehD6Uxlf%+M^^uN5Wc*G%^;>D5qT{>=uxUhX%WJu^Z*(_Wq9y}npFO{Hhb>s6<9 zNi0pHXWFaVZnb)1+RS&F)xOv6&aeILcI)`k#0YE+?e)5&#r7J#c`3Z7x!LpTc01dx zrdC3{Z;joZ^KN&))zB_i)I9fWedoN>Zl-6_Iz+^G&*ak2jpF07*qoM6N<$f;w%0(f|Me literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/mobile_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobile_app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile_app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/mobile_app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile_app/ios/Runner/Base.lproj/Main.storyboard b/mobile_app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/mobile_app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile_app/ios/Runner/Info.plist b/mobile_app/ios/Runner/Info.plist new file mode 100644 index 0000000..1ba2500 --- /dev/null +++ b/mobile_app/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Mobile App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + mobile_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/mobile_app/ios/Runner/Runner-Bridging-Header.h b/mobile_app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/mobile_app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobile_app/ios/RunnerTests/RunnerTests.swift b/mobile_app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/mobile_app/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart new file mode 100644 index 0000000..8e94089 --- /dev/null +++ b/mobile_app/lib/main.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a purple toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // TRY THIS: Try changing the color here to a specific color (to + // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // change color while the other colors stay the same. + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + // + // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" + // action in the IDE, or press "p" in the console), to see the + // wireframe for each widget. + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/mobile_app/pubspec.lock b/mobile_app/pubspec.lock new file mode 100644 index 0000000..4de467f --- /dev/null +++ b/mobile_app/pubspec.lock @@ -0,0 +1,188 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" +sdks: + dart: ">=3.2.3 <4.0.0" diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml new file mode 100644 index 0000000..f592b7f --- /dev/null +++ b/mobile_app/pubspec.yaml @@ -0,0 +1,90 @@ +name: mobile_app +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.2.3 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/mobile_app/test/widget_test.dart b/mobile_app/test/widget_test.dart new file mode 100644 index 0000000..a0e7a4e --- /dev/null +++ b/mobile_app/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:mobile_app/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} From adb7b0e5692f076b6ef0bfc050dbf09607cd20de Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 21 Jan 2024 18:05:33 +0300 Subject: [PATCH 02/18] delete nested README --- mobile_app/README.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 mobile_app/README.md diff --git a/mobile_app/README.md b/mobile_app/README.md deleted file mode 100644 index 1dfe888..0000000 --- a/mobile_app/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# mobile_app - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. From 53f5ee6eb9a2e06145715bcd9bcd684d07fbb027 Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 4 Feb 2024 19:37:31 +0300 Subject: [PATCH 03/18] first version of UI --- mobile_app/lib/main.dart | 143 ++------------- .../home_page/home_page_widget.dart | 29 +++ .../home_page/widgets/bottom_data_widget.dart | 32 ++++ .../home_page/widgets/graph_widget.dart | 30 ++++ .../home_page/widgets/stack_graph_widget.dart | 38 ++++ mobile_app/pubspec.lock | 17 ++ mobile_app/pubspec.yaml | 166 ++++++++---------- 7 files changed, 240 insertions(+), 215 deletions(-) create mode 100644 mobile_app/lib/presentation/home_page/home_page_widget.dart create mode 100644 mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart create mode 100644 mobile_app/lib/presentation/home_page/widgets/graph_widget.dart create mode 100644 mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart index 8e94089..14a4d70 100644 --- a/mobile_app/lib/main.dart +++ b/mobile_app/lib/main.dart @@ -1,125 +1,18 @@ -import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} +import 'package:flutter/material.dart'; +import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: HomePageWidget(), + ); + } +} + diff --git a/mobile_app/lib/presentation/home_page/home_page_widget.dart b/mobile_app/lib/presentation/home_page/home_page_widget.dart new file mode 100644 index 0000000..05eb4c5 --- /dev/null +++ b/mobile_app/lib/presentation/home_page/home_page_widget.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/presentation/home_page/widgets/bottom_data_widget.dart'; +import 'package:mobile_app/presentation/home_page/widgets/stack_graph_widget.dart'; + +class HomePageWidget extends StatelessWidget { + const HomePageWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Glucometer'), + ), + body: const Flex( + direction: Axis.vertical, + children: [ + Flexible( + flex: 6, + child: StackGraphWidget(), + ), + Flexible( + flex: 4, + child: BottomDataWidget(), + ), + ], + ), + ); + } +} diff --git a/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart b/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart new file mode 100644 index 0000000..aaf5f66 --- /dev/null +++ b/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class BottomDataWidget extends StatelessWidget { + const BottomDataWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const DecoratedBox( + decoration: BoxDecoration(color: Colors.amber), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('156'), + Text('156'), + Text('156'), + ], + ), + Text( + '650.9', + style: TextStyle( + fontSize: 24, + ), + ), + ], + ), + ); + } +} diff --git a/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart b/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart new file mode 100644 index 0000000..93df6fe --- /dev/null +++ b/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart @@ -0,0 +1,30 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +final lineChartBarData = LineChartBarData( + spots: const [ + FlSpot(0, 0), + FlSpot(1, 2), + FlSpot(3, 1), + FlSpot(5, 2), + FlSpot(6, 3), + FlSpot(8, 6), + ], + isCurved: true, + gradient: const LinearGradient(colors: [Colors.blue, Colors.yellow]), +); + +class GraphWidget extends StatelessWidget { + const GraphWidget({super.key}); + + @override + Widget build(BuildContext context) { + return LineChart( + LineChartData( + lineBarsData: [lineChartBarData], + minY: 0, + minX: 0, + ), + ); + } +} diff --git a/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart b/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart new file mode 100644 index 0000000..5e7f53a --- /dev/null +++ b/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/presentation/home_page/widgets/graph_widget.dart'; + +class StackGraphWidget extends StatelessWidget { + const StackGraphWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const Stack( + children: [ + Column( + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [Text('3.5 моль/л')], + ), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Что то'), + Text('Что то'), + Text('Что то'), + ], + ) + ], + ), + Padding( + padding: EdgeInsets.all(20), + child: Center( + child: GraphWidget(), + ), + ), + ], + ); + } +} diff --git a/mobile_app/pubspec.lock b/mobile_app/pubspec.lock index 4de467f..e1b4a96 100644 --- a/mobile_app/pubspec.lock +++ b/mobile_app/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: b5e2b0f13d93f8c532b5a2786bfb44580de1f50b927bf95813fa1af617e9caf8 + url: "https://pub.dev" + source: hosted + version: "0.66.1" flutter: dependency: "direct main" description: flutter @@ -186,3 +202,4 @@ packages: version: "0.3.0" sdks: dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.0" diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index f592b7f..447f7e6 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -1,90 +1,76 @@ -name: mobile_app -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 - -environment: - sdk: '>=3.2.3 <4.0.0' - -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. -dependencies: - flutter: - sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages +name: mobile_app +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.2.3 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + fl_chart: ^0.66.1 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages From 886830b422f4a57c0e3bdeb7d26c29ebd5985677 Mon Sep 17 00:00:00 2001 From: Max Size Date: Thu, 14 Mar 2024 17:57:10 +0300 Subject: [PATCH 04/18] Create Login and Splash screens --- mobile_app/analysis_options.yaml | 209 +++++++++++++++--- mobile_app/assets/glucose.svg | 27 +++ mobile_app/devtools_options.yaml | 1 + mobile_app/lib/config/colors.dart | 3 + mobile_app/lib/config/const.dart | 1 + mobile_app/lib/config/navigation.dart | 39 ++++ mobile_app/lib/config/routes.dart | 5 + mobile_app/lib/config/theme.dart | 5 + mobile_app/lib/main.dart | 7 +- .../home_page/cubit/home_page_cubit.dart | 8 + .../home_page/cubit/home_page_state.dart | 6 + .../home_page/widgets/bottom_data_widget.dart | 1 - .../home_page/widgets/stack_graph_widget.dart | 3 +- .../presentation/login/cubit/login_cubit.dart | 22 ++ .../presentation/login/cubit/login_state.dart | 17 ++ .../lib/presentation/login/login_page.dart | 137 ++++++++++++ .../splash/cubit/splash_cubit.dart | 16 ++ .../splash/cubit/splash_state.dart | 12 + .../presentation/splash/splash_screen.dart | 25 +++ mobile_app/pubspec.lock | 122 +++++++++- mobile_app/pubspec.yaml | 14 +- 21 files changed, 637 insertions(+), 43 deletions(-) create mode 100644 mobile_app/assets/glucose.svg create mode 100644 mobile_app/devtools_options.yaml create mode 100644 mobile_app/lib/config/colors.dart create mode 100644 mobile_app/lib/config/const.dart create mode 100644 mobile_app/lib/config/navigation.dart create mode 100644 mobile_app/lib/config/routes.dart create mode 100644 mobile_app/lib/config/theme.dart create mode 100644 mobile_app/lib/presentation/home_page/cubit/home_page_cubit.dart create mode 100644 mobile_app/lib/presentation/home_page/cubit/home_page_state.dart create mode 100644 mobile_app/lib/presentation/login/cubit/login_cubit.dart create mode 100644 mobile_app/lib/presentation/login/cubit/login_state.dart create mode 100644 mobile_app/lib/presentation/login/login_page.dart create mode 100644 mobile_app/lib/presentation/splash/cubit/splash_cubit.dart create mode 100644 mobile_app/lib/presentation/splash/cubit/splash_state.dart create mode 100644 mobile_app/lib/presentation/splash/splash_screen.dart diff --git a/mobile_app/analysis_options.yaml b/mobile_app/analysis_options.yaml index 0d29021..791151b 100644 --- a/mobile_app/analysis_options.yaml +++ b/mobile_app/analysis_options.yaml @@ -1,28 +1,181 @@ -# 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 +include: package:lints/recommended.yaml + +linter: + rules: + - always_declare_return_types + - always_require_non_null_named_parameters + - always_use_package_imports + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catching_errors + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_final_parameters + - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_multiple_declarations_per_line + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_print + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_future + - avoid_returning_null_for_void + - avoid_returning_this + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + - avoid_web_libraries_in_flutter + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - cast_nullable_to_non_nullable + - comment_references + - conditional_uri_does_not_exist + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - deprecated_consistency + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - eol_at_end_of_file + - exhaustive_cases + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - collection_methods_unrelated_type + - join_return_with_assignment + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + - library_private_types_in_public_api + - lines_longer_than_80_chars + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_default_cases + - no_duplicate_case_values + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers + - no_logic_in_create_state + - no_runtimeType_toString + - non_constant_identifier_names + - noop_primitive_operations + - null_check_on_nullable_type_parameter + - null_closures + - omit_local_variable_types + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_asserts_with_message + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_null_aware_method_calls + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - require_trailing_commas + - secure_pubspec_urls + - sized_box_for_whitespace + - sized_box_shrink_expand + - slash_for_doc_comments + - sort_child_properties_last + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + - type_annotate_public_apis + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_constructor_name + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_late + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_checks + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unnecessary_to_list_in_spreads + - unrelated_type_equality_checks + - use_build_context_synchronously + - use_colored_box + - use_decorated_box + - use_enums + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_if_null_to_convert_nulls_to_bools + - use_is_even_rather_than_modulo + - use_key_in_widget_constructors + - use_late_for_private_fields_and_variables + - use_named_constants + - use_raw_strings + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_super_parameters + - use_test_throws_matchers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks diff --git a/mobile_app/assets/glucose.svg b/mobile_app/assets/glucose.svg new file mode 100644 index 0000000..79fbf0b --- /dev/null +++ b/mobile_app/assets/glucose.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile_app/devtools_options.yaml b/mobile_app/devtools_options.yaml new file mode 100644 index 0000000..7e7e7f6 --- /dev/null +++ b/mobile_app/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/mobile_app/lib/config/colors.dart b/mobile_app/lib/config/colors.dart new file mode 100644 index 0000000..10e1385 --- /dev/null +++ b/mobile_app/lib/config/colors.dart @@ -0,0 +1,3 @@ +import 'package:flutter/material.dart'; + +const green50 = Color(0xFF009E00); diff --git a/mobile_app/lib/config/const.dart b/mobile_app/lib/config/const.dart new file mode 100644 index 0000000..d0ba024 --- /dev/null +++ b/mobile_app/lib/config/const.dart @@ -0,0 +1 @@ +const logoSvg = 'assets/glucose.svg'; diff --git a/mobile_app/lib/config/navigation.dart b/mobile_app/lib/config/navigation.dart new file mode 100644 index 0000000..9e12a52 --- /dev/null +++ b/mobile_app/lib/config/navigation.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/presentation/home_page/cubit/home_page_cubit.dart'; +import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; +import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; +import 'package:mobile_app/presentation/login/login_page.dart'; +import 'package:mobile_app/presentation/splash/cubit/splash_cubit.dart'; +import 'package:mobile_app/presentation/splash/splash_screen.dart'; + +class MainNavigation { + static Route onGenerateRoute(RouteSettings settings) { + Widget page; + switch (settings.name) { + case Routes.splash: + page = BlocProvider( + create: (_) => SplashCubit(), + child: const SplashScreen(), + ); + case Routes.auth: + page = BlocProvider( + create: (_) => LoginCubit(), + child: const LoginPage(), + ); + case Routes.homePage: + page = BlocProvider( + create: (_) => HomePageCubit(), + child: const HomePageWidget(), + ); + default: + page = const Scaffold( + body: Center( + child: Text('error'), + ), + ); + } + return MaterialPageRoute(builder: (context) => page); + } +} diff --git a/mobile_app/lib/config/routes.dart b/mobile_app/lib/config/routes.dart new file mode 100644 index 0000000..91747a7 --- /dev/null +++ b/mobile_app/lib/config/routes.dart @@ -0,0 +1,5 @@ +abstract class Routes { + static const splash = '/'; + static const auth = '/auth'; + static const homePage = '/main'; +} diff --git a/mobile_app/lib/config/theme.dart b/mobile_app/lib/config/theme.dart new file mode 100644 index 0000000..ec70e15 --- /dev/null +++ b/mobile_app/lib/config/theme.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +final theme = ThemeData( + +); diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart index 14a4d70..5a3fbfa 100644 --- a/mobile_app/lib/main.dart +++ b/mobile_app/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; +import 'package:mobile_app/config/navigation.dart'; +import 'package:mobile_app/config/routes.dart'; void main() { runApp(const MyApp()); @@ -11,8 +12,8 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - home: HomePageWidget(), + initialRoute: Routes.splash, + onGenerateRoute: MainNavigation.onGenerateRoute, ); } } - diff --git a/mobile_app/lib/presentation/home_page/cubit/home_page_cubit.dart b/mobile_app/lib/presentation/home_page/cubit/home_page_cubit.dart new file mode 100644 index 0000000..461ecc5 --- /dev/null +++ b/mobile_app/lib/presentation/home_page/cubit/home_page_cubit.dart @@ -0,0 +1,8 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'home_page_state.dart'; + +class HomePageCubit extends Cubit { + HomePageCubit() : super(HomePageInitial()); +} diff --git a/mobile_app/lib/presentation/home_page/cubit/home_page_state.dart b/mobile_app/lib/presentation/home_page/cubit/home_page_state.dart new file mode 100644 index 0000000..c9d02b0 --- /dev/null +++ b/mobile_app/lib/presentation/home_page/cubit/home_page_state.dart @@ -0,0 +1,6 @@ +part of 'home_page_cubit.dart'; + +@immutable +sealed class HomePageState {} + +final class HomePageInitial extends HomePageState {} diff --git a/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart b/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart index aaf5f66..52d6877 100644 --- a/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart +++ b/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart @@ -9,7 +9,6 @@ class BottomDataWidget extends StatelessWidget { decoration: BoxDecoration(color: Colors.amber), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart b/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart index 5e7f53a..d2ed22f 100644 --- a/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart +++ b/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart @@ -11,7 +11,6 @@ class StackGraphWidget extends StatelessWidget { Column( children: [ Row( - mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: [Text('3.5 моль/л')], ), @@ -23,7 +22,7 @@ class StackGraphWidget extends StatelessWidget { Text('Что то'), Text('Что то'), ], - ) + ), ], ), Padding( diff --git a/mobile_app/lib/presentation/login/cubit/login_cubit.dart b/mobile_app/lib/presentation/login/cubit/login_cubit.dart new file mode 100644 index 0000000..198eef0 --- /dev/null +++ b/mobile_app/lib/presentation/login/cubit/login_cubit.dart @@ -0,0 +1,22 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +part 'login_state.dart'; + +class LoginCubit extends Cubit { + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + + LoginCubit() : super(LoginInitial()); + + Future login() async{ + emit(LoginLoading()); + await Future.delayed(const Duration(seconds: 3)); + if(emailController.text == 'test' && passwordController.text == '123'){ + emit(LoginSuccessful()); + } else { + emit(LoginError(error: 'Неверный логин или пароль')); + } + } + +} diff --git a/mobile_app/lib/presentation/login/cubit/login_state.dart b/mobile_app/lib/presentation/login/cubit/login_state.dart new file mode 100644 index 0000000..b9364c7 --- /dev/null +++ b/mobile_app/lib/presentation/login/cubit/login_state.dart @@ -0,0 +1,17 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +part of 'login_cubit.dart'; + +@immutable +sealed class LoginState {} + +final class LoginInitial extends LoginState {} + +final class LoginLoading extends LoginState {} + +final class LoginError extends LoginState { + final String error; + + LoginError({required this.error}); +} + +final class LoginSuccessful extends LoginState {} diff --git a/mobile_app/lib/presentation/login/login_page.dart b/mobile_app/lib/presentation/login/login_page.dart new file mode 100644 index 0000000..e5564e9 --- /dev/null +++ b/mobile_app/lib/presentation/login/login_page.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; +import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; + +class LoginPage extends StatelessWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocConsumer( + listener: (context, state) { + if (state is LoginSuccessful) { + Navigator.of(context).pushReplacementNamed(Routes.homePage); + } + }, + builder: (context, state) { + switch (state.runtimeType) { + case LoginLoading: + return const Center( + child: CircularProgressIndicator(), + ); + default: + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + if (state is LoginError) Text(state.error), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: SvgPicture.asset(logoSvg), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const HomePageWidget(), + ), + ), + ), + ], + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'E-mail', + ), + controller: + context.read().emailController, + ), + const SizedBox( + height: 8, + ), + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Password', + ), + controller: + context.read().passwordController, + ), + const SizedBox( + height: 8, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: TextButton.styleFrom( + foregroundColor: green50, + ), + onPressed: () {}, + child: const Text('Forgot password?'), + ), + ], + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 52), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + backgroundColor: green50, + foregroundColor: Colors.white, + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + onPressed: () => context.read().login(), + child: const Text('Login'), + ), + const SizedBox( + height: 8, + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 52), + side: const BorderSide(color: green50, width: 1.25), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + backgroundColor: Colors.white, + foregroundColor: green50, + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + onPressed: () {}, + child: const Text("I don't have an account"), + ), + ], + ), + ); + } + }, + ), + ); + } +} diff --git a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart new file mode 100644 index 0000000..094366a --- /dev/null +++ b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart @@ -0,0 +1,16 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/routes.dart'; + +part 'splash_state.dart'; + +class SplashCubit extends Cubit { + SplashCubit() : super(SplashInitial()) { + _init(); + } + + Future _init() async { + await Future.delayed(const Duration(seconds: 3)); + emit(SplashInitialized(nextRoute: Routes.auth)); + } +} diff --git a/mobile_app/lib/presentation/splash/cubit/splash_state.dart b/mobile_app/lib/presentation/splash/cubit/splash_state.dart new file mode 100644 index 0000000..fc135f3 --- /dev/null +++ b/mobile_app/lib/presentation/splash/cubit/splash_state.dart @@ -0,0 +1,12 @@ +part of 'splash_cubit.dart'; + +@immutable +sealed class SplashState {} + +final class SplashInitial extends SplashState {} + +final class SplashInitialized extends SplashState { + final String nextRoute; + + SplashInitialized({required this.nextRoute}); +} diff --git a/mobile_app/lib/presentation/splash/splash_screen.dart b/mobile_app/lib/presentation/splash/splash_screen.dart new file mode 100644 index 0000000..f12be94 --- /dev/null +++ b/mobile_app/lib/presentation/splash/splash_screen.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/presentation/splash/cubit/splash_cubit.dart'; + +class SplashScreen extends StatelessWidget { + const SplashScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocListener( + listener: (context, state) { + if (state is SplashInitialized){ + Navigator.of(context).pushReplacementNamed(state.nextRoute); + } + }, + child: Center( + child: SvgPicture.asset(logoSvg), + ), + ), + ); + } +} diff --git a/mobile_app/pubspec.lock b/mobile_app/pubspec.lock index e1b4a96..acfc065 100644 --- a/mobile_app/pubspec.lock +++ b/mobile_app/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" async: dependency: transitive description: @@ -9,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e + url: "https://pub.dev" + source: hosted + version: "8.1.3" boolean_selector: dependency: transitive description: @@ -78,6 +94,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" + url: "https://pub.dev" + source: hosted + version: "8.1.4" flutter_lints: dependency: "direct dev" description: @@ -86,11 +110,35 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + url: "https://pub.dev" + source: hosted + version: "1.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" lints: dependency: transitive description: @@ -116,13 +164,21 @@ packages: source: hosted version: "0.5.0" meta: - dependency: transitive + dependency: "direct main" description: name: meta sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted version: "1.10.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -131,6 +187,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" sky_engine: dependency: transitive description: flutter @@ -184,6 +264,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -200,6 +312,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.2.3 <4.0.0" flutter: ">=3.16.0" diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index 447f7e6..aada730 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -31,10 +31,11 @@ dependencies: flutter: sdk: flutter fl_chart: ^0.66.1 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + flutter_svg: ^2.0.10+1 + flutter_bloc: ^8.1.4 + bloc: ^8.1.3 cupertino_icons: ^1.0.2 + meta: ^1.10.0 dev_dependencies: flutter_test: @@ -43,11 +44,8 @@ dev_dependencies: flutter: uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware From 3c061e71b721adb04354891e9e783d0d5c4f2721 Mon Sep 17 00:00:00 2001 From: Max Size Date: Sat, 16 Mar 2024 11:44:48 +0300 Subject: [PATCH 05/18] Improve UI --- mobile_app/lib/config/theme.dart | 3 ++- mobile_app/lib/main.dart | 4 ++- .../home_page/widgets/graph_widget.dart | 2 +- .../lib/presentation/login/login_page.dart | 27 +++++++++---------- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/mobile_app/lib/config/theme.dart b/mobile_app/lib/config/theme.dart index ec70e15..f6ce261 100644 --- a/mobile_app/lib/config/theme.dart +++ b/mobile_app/lib/config/theme.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; final theme = ThemeData( - + colorSchemeSeed: green50, ); diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart index 5a3fbfa..797340f 100644 --- a/mobile_app/lib/main.dart +++ b/mobile_app/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app/config/navigation.dart'; import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/config/theme.dart'; void main() { runApp(const MyApp()); @@ -11,7 +12,8 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( + theme: theme, initialRoute: Routes.splash, onGenerateRoute: MainNavigation.onGenerateRoute, ); diff --git a/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart b/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart index 93df6fe..012c57d 100644 --- a/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart +++ b/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; final lineChartBarData = LineChartBarData( spots: const [ - FlSpot(0, 0), + FlSpot.zero, FlSpot(1, 2), FlSpot(3, 1), FlSpot(5, 2), diff --git a/mobile_app/lib/presentation/login/login_page.dart b/mobile_app/lib/presentation/login/login_page.dart index e5564e9..d65c4c6 100644 --- a/mobile_app/lib/presentation/login/login_page.dart +++ b/mobile_app/lib/presentation/login/login_page.dart @@ -4,7 +4,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/routes.dart'; -import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; class LoginPage extends StatelessWidget { @@ -13,6 +12,7 @@ class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( + resizeToAvoidBottomInset: false, body: BlocConsumer( listener: (context, state) { if (state is LoginSuccessful) { @@ -30,26 +30,28 @@ class LoginPage extends StatelessWidget { padding: const EdgeInsets.all(16), child: Column( children: [ - if (state is LoginError) Text(state.error), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - GestureDetector( - child: SvgPicture.asset(logoSvg), - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => const HomePageWidget(), - ), - ), - ), + SvgPicture.asset(logoSvg), ], ), ), Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ + if (state is LoginError) + Align( + alignment: Alignment.centerLeft, + child: Text( + state.error, + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox( + height: 8, + ), TextFormField( decoration: const InputDecoration( border: OutlineInputBorder(), @@ -76,9 +78,6 @@ class LoginPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - style: TextButton.styleFrom( - foregroundColor: green50, - ), onPressed: () {}, child: const Text('Forgot password?'), ), From 697274bda6e3f4a3e2be3fbad3cb4fa7744b0c19 Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 28 Apr 2024 19:33:07 +0300 Subject: [PATCH 06/18] created models --- mobile_app/.gitignore | 43 ------------ mobile_app/analysis_options.yaml | 4 -- mobile_app/lib/config/const.dart | 1 + mobile_app/lib/config/navigation.dart | 12 +++- mobile_app/lib/config/routes.dart | 1 + .../data_source/remote/auth_data_source.dart | 20 ++++++ .../lib/data/models/request/login_data.dart | 19 +++++ .../models/request/registration_data.dart | 20 ++++++ .../lib/data/models/response/login_data.dart | 15 ++++ .../models/response/registration_data.dart | 13 ++++ .../lib/data/repository/auth_repository.dart | 39 +++++++++++ mobile_app/lib/di/di_container.dart | 15 ++++ .../domain/repository/auth_repository.dart | 11 +++ .../presentation/login/cubit/login_cubit.dart | 28 +++++--- .../presentation/login/cubit/login_state.dart | 2 + .../lib/presentation/login/login_page.dart | 4 +- .../cubit/registration_cubit.dart | 27 ++++++++ .../cubit/registration_state.dart | 16 +++++ .../registration/registration_page.dart | 69 +++++++++++++++++++ .../splash/cubit/splash_cubit.dart | 6 +- .../presentation/splash/splash_screen.dart | 3 + mobile_app/pubspec.yaml | 11 +++ 22 files changed, 319 insertions(+), 60 deletions(-) delete mode 100644 mobile_app/.gitignore create mode 100644 mobile_app/lib/data/data_source/remote/auth_data_source.dart create mode 100644 mobile_app/lib/data/models/request/login_data.dart create mode 100644 mobile_app/lib/data/models/request/registration_data.dart create mode 100644 mobile_app/lib/data/models/response/login_data.dart create mode 100644 mobile_app/lib/data/models/response/registration_data.dart create mode 100644 mobile_app/lib/data/repository/auth_repository.dart create mode 100644 mobile_app/lib/di/di_container.dart create mode 100644 mobile_app/lib/domain/repository/auth_repository.dart create mode 100644 mobile_app/lib/presentation/registration/cubit/registration_cubit.dart create mode 100644 mobile_app/lib/presentation/registration/cubit/registration_state.dart create mode 100644 mobile_app/lib/presentation/registration/registration_page.dart diff --git a/mobile_app/.gitignore b/mobile_app/.gitignore deleted file mode 100644 index 29a3a50..0000000 --- a/mobile_app/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -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 -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ - -# 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_app/analysis_options.yaml b/mobile_app/analysis_options.yaml index 791151b..f7cfaef 100644 --- a/mobile_app/analysis_options.yaml +++ b/mobile_app/analysis_options.yaml @@ -3,7 +3,6 @@ include: package:lints/recommended.yaml linter: rules: - always_declare_return_types - - always_require_non_null_named_parameters - always_use_package_imports - annotate_overrides - avoid_bool_literals_in_conditional_expressions @@ -27,8 +26,6 @@ linter: - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_future - avoid_returning_null_for_void - avoid_returning_this - avoid_setters_without_getters @@ -143,7 +140,6 @@ linter: - unnecessary_const - unnecessary_constructor_name - unnecessary_getters_setters - - unnecessary_lambdas - unnecessary_late - unnecessary_new - unnecessary_null_aware_assignments diff --git a/mobile_app/lib/config/const.dart b/mobile_app/lib/config/const.dart index d0ba024..eba4c78 100644 --- a/mobile_app/lib/config/const.dart +++ b/mobile_app/lib/config/const.dart @@ -1 +1,2 @@ const logoSvg = 'assets/glucose.svg'; +const baseUrl = 'http://31.129.107.206:5000'; \ No newline at end of file diff --git a/mobile_app/lib/config/navigation.dart b/mobile_app/lib/config/navigation.dart index 9e12a52..5dbc05e 100644 --- a/mobile_app/lib/config/navigation.dart +++ b/mobile_app/lib/config/navigation.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/data/repository/auth_repository.dart'; import 'package:mobile_app/presentation/home_page/cubit/home_page_cubit.dart'; import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; import 'package:mobile_app/presentation/login/login_page.dart'; +import 'package:mobile_app/presentation/registration/cubit/registration_cubit.dart'; +import 'package:mobile_app/presentation/registration/registration_page.dart'; import 'package:mobile_app/presentation/splash/cubit/splash_cubit.dart'; import 'package:mobile_app/presentation/splash/splash_screen.dart'; @@ -19,7 +22,7 @@ class MainNavigation { ); case Routes.auth: page = BlocProvider( - create: (_) => LoginCubit(), + create: (_) => LoginCubit(authRepo: IAuthRepository()), child: const LoginPage(), ); case Routes.homePage: @@ -27,6 +30,13 @@ class MainNavigation { create: (_) => HomePageCubit(), child: const HomePageWidget(), ); + case Routes.registration: + page = BlocProvider( + create: (_) => RegistrationCubit( + authRepo: IAuthRepository(), + ), + child: const RegistrationPage(), + ); default: page = const Scaffold( body: Center( diff --git a/mobile_app/lib/config/routes.dart b/mobile_app/lib/config/routes.dart index 91747a7..a306776 100644 --- a/mobile_app/lib/config/routes.dart +++ b/mobile_app/lib/config/routes.dart @@ -2,4 +2,5 @@ abstract class Routes { static const splash = '/'; static const auth = '/auth'; static const homePage = '/main'; + static const registration = '/registration'; } diff --git a/mobile_app/lib/data/data_source/remote/auth_data_source.dart b/mobile_app/lib/data/data_source/remote/auth_data_source.dart new file mode 100644 index 0000000..19c38aa --- /dev/null +++ b/mobile_app/lib/data/data_source/remote/auth_data_source.dart @@ -0,0 +1,20 @@ +import 'package:dio/dio.dart'; +import 'package:mobile_app/data/models/request/login_data.dart'; +import 'package:mobile_app/data/models/request/registration_data.dart'; +import 'package:mobile_app/data/models/response/login_data.dart'; +import 'package:mobile_app/data/models/response/registration_data.dart'; +import 'package:retrofit/http.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'auth_data_source.g.dart'; + +@RestApi() +abstract class AuthClient { + factory AuthClient(Dio dio, {String baseUrl}) = _AuthClient; + + @POST('/register') + Future register(@Body() RegistrationData data); + + @POST('/login') + Future login(@Body() LoginData data); +} diff --git a/mobile_app/lib/data/models/request/login_data.dart b/mobile_app/lib/data/models/request/login_data.dart new file mode 100644 index 0000000..e55490c --- /dev/null +++ b/mobile_app/lib/data/models/request/login_data.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'login_data.g.dart'; + +@JsonSerializable() +class LoginData { + final String username; + final String password; + + LoginData({ + required this.username, + required this.password, + }); + + Map toJson() => _$LoginDataToJson(this); + + factory LoginData.fromJson(Map json) => + _$LoginDataFromJson(json); +} diff --git a/mobile_app/lib/data/models/request/registration_data.dart b/mobile_app/lib/data/models/request/registration_data.dart new file mode 100644 index 0000000..3bcf818 --- /dev/null +++ b/mobile_app/lib/data/models/request/registration_data.dart @@ -0,0 +1,20 @@ + +import 'package:json_annotation/json_annotation.dart'; + +part 'registration_data.g.dart'; + +@JsonSerializable() +class RegistrationData { + final String username; + final String password; + + RegistrationData({ + required this.username, + required this.password, + }); + + Map toJson() => _$RegistrationDataToJson(this); + + factory RegistrationData.fromJson(Map json) => + _$RegistrationDataFromJson(json); +} diff --git a/mobile_app/lib/data/models/response/login_data.dart b/mobile_app/lib/data/models/response/login_data.dart new file mode 100644 index 0000000..ac61c71 --- /dev/null +++ b/mobile_app/lib/data/models/response/login_data.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'login_data.g.dart'; + +@JsonSerializable() +class LoginDataResponse{ + final String message; + + LoginDataResponse({required this.message}); + + Map toJson() => _$LoginDataResponseToJson(this); + + factory LoginDataResponse.fromJson(Map json) => + _$LoginDataResponseFromJson(json); +} diff --git a/mobile_app/lib/data/models/response/registration_data.dart b/mobile_app/lib/data/models/response/registration_data.dart new file mode 100644 index 0000000..46331cd --- /dev/null +++ b/mobile_app/lib/data/models/response/registration_data.dart @@ -0,0 +1,13 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'registration_data.g.dart'; + +@JsonSerializable(createToJson: false) +class RegistrationDataResponse{ + final String message; + + RegistrationDataResponse({required this.message}); + + factory RegistrationDataResponse.fromJson(Map json) => + _$RegistrationDataResponseFromJson(json); +} diff --git a/mobile_app/lib/data/repository/auth_repository.dart b/mobile_app/lib/data/repository/auth_repository.dart new file mode 100644 index 0000000..50ee1af --- /dev/null +++ b/mobile_app/lib/data/repository/auth_repository.dart @@ -0,0 +1,39 @@ +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:mobile_app/data/data_source/remote/auth_data_source.dart'; +import 'package:mobile_app/data/models/request/login_data.dart'; +import 'package:mobile_app/data/models/request/registration_data.dart'; +import 'package:mobile_app/data/models/response/login_data.dart'; +import 'package:mobile_app/data/models/response/registration_data.dart'; +import 'package:mobile_app/di/di_container.dart'; +import 'package:mobile_app/domain/repository/auth_repository.dart'; + +class IAuthRepository implements AuthRepository { + final client = AuthClient(sl()); + + @override + Future> login(LoginData data) async { + try { + final response = await client.login(data); + return right(response); + } catch (exception) { + if(exception is DioException){ + final error = exception.response?.data['error']; + print(error); + return left(UnitMonoid().zero()); + } + return left(UnitMonoid().zero()); + } + } + + @override + Future> register( + RegistrationData data) async { + try { + final response = await client.register(data); + return right(response); + } catch (exception) { + return left(UnitMonoid().zero()); + } + } +} diff --git a/mobile_app/lib/di/di_container.dart b/mobile_app/lib/di/di_container.dart new file mode 100644 index 0000000..fd0f74c --- /dev/null +++ b/mobile_app/lib/di/di_container.dart @@ -0,0 +1,15 @@ +import 'package:dio/dio.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mobile_app/config/const.dart'; + +final sl = GetIt.instance; +final regLs = sl.registerLazySingleton; +final regS = sl.registerSingleton; + +void init() { + regLs( + () => Dio() + ..options.baseUrl = baseUrl + ..options.headers['Content-Type'] = 'application/json', + ); +} diff --git a/mobile_app/lib/domain/repository/auth_repository.dart b/mobile_app/lib/domain/repository/auth_repository.dart new file mode 100644 index 0000000..a8f7716 --- /dev/null +++ b/mobile_app/lib/domain/repository/auth_repository.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import 'package:mobile_app/data/models/request/login_data.dart'; +import 'package:mobile_app/data/models/request/registration_data.dart'; +import 'package:mobile_app/data/models/response/login_data.dart'; +import 'package:mobile_app/data/models/response/registration_data.dart'; + +abstract class AuthRepository{ + Future> register(RegistrationData data); + + Future> login(LoginData data); +} diff --git a/mobile_app/lib/presentation/login/cubit/login_cubit.dart b/mobile_app/lib/presentation/login/cubit/login_cubit.dart index 198eef0..691b2e6 100644 --- a/mobile_app/lib/presentation/login/cubit/login_cubit.dart +++ b/mobile_app/lib/presentation/login/cubit/login_cubit.dart @@ -1,22 +1,32 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:mobile_app/data/models/request/login_data.dart'; +import 'package:mobile_app/domain/repository/auth_repository.dart'; part 'login_state.dart'; class LoginCubit extends Cubit { + final AuthRepository authRepo; final emailController = TextEditingController(); final passwordController = TextEditingController(); - LoginCubit() : super(LoginInitial()); - - Future login() async{ + LoginCubit({required this.authRepo}) : super(LoginInitial()); + + Future login() async { emit(LoginLoading()); - await Future.delayed(const Duration(seconds: 3)); - if(emailController.text == 'test' && passwordController.text == '123'){ - emit(LoginSuccessful()); - } else { - emit(LoginError(error: 'Неверный логин или пароль')); - } + final loginData = LoginData( + username: emailController.text, + password: passwordController.text, + ); + final result = await authRepo.login(loginData); + result.fold( + (error) => emit(LoginError(error: 'Неверный логин или пароль')), + (data) => emit(LoginSuccessful()), + ); + } + + void goRegistration() { + emit(NotRegisteredYet()); } } diff --git a/mobile_app/lib/presentation/login/cubit/login_state.dart b/mobile_app/lib/presentation/login/cubit/login_state.dart index b9364c7..7caf4bb 100644 --- a/mobile_app/lib/presentation/login/cubit/login_state.dart +++ b/mobile_app/lib/presentation/login/cubit/login_state.dart @@ -15,3 +15,5 @@ final class LoginError extends LoginState { } final class LoginSuccessful extends LoginState {} + +final class NotRegisteredYet extends LoginState {} diff --git a/mobile_app/lib/presentation/login/login_page.dart b/mobile_app/lib/presentation/login/login_page.dart index d65c4c6..48170d9 100644 --- a/mobile_app/lib/presentation/login/login_page.dart +++ b/mobile_app/lib/presentation/login/login_page.dart @@ -17,6 +17,8 @@ class LoginPage extends StatelessWidget { listener: (context, state) { if (state is LoginSuccessful) { Navigator.of(context).pushReplacementNamed(Routes.homePage); + } else if (state is NotRegisteredYet) { + Navigator.of(context).pushReplacementNamed(Routes.registration); } }, builder: (context, state) { @@ -122,7 +124,7 @@ class LoginPage extends StatelessWidget { fontWeight: FontWeight.w500, ), ), - onPressed: () {}, + onPressed: context.read().goRegistration, child: const Text("I don't have an account"), ), ], diff --git a/mobile_app/lib/presentation/registration/cubit/registration_cubit.dart b/mobile_app/lib/presentation/registration/cubit/registration_cubit.dart new file mode 100644 index 0000000..614fe2e --- /dev/null +++ b/mobile_app/lib/presentation/registration/cubit/registration_cubit.dart @@ -0,0 +1,27 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app/data/models/request/registration_data.dart'; +import 'package:mobile_app/domain/repository/auth_repository.dart'; + +part 'registration_state.dart'; + +class RegistrationCubit extends Cubit { + final AuthRepository authRepo; + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + + RegistrationCubit({required this.authRepo}) : super(RegistrationInitial()); + + Future register() async { + emit(RegistrationLoading()); + final registerData = RegistrationData( + username: emailController.text, + password: passwordController.text, + ); + final result = await authRepo.register(registerData); + result.fold( + (error) => emit(RegistrationError(error: 'Неверный логин или пароль')), + (data) => emit(RegistrationSuccessful()), + ); + } +} diff --git a/mobile_app/lib/presentation/registration/cubit/registration_state.dart b/mobile_app/lib/presentation/registration/cubit/registration_state.dart new file mode 100644 index 0000000..77b540d --- /dev/null +++ b/mobile_app/lib/presentation/registration/cubit/registration_state.dart @@ -0,0 +1,16 @@ +part of 'registration_cubit.dart'; + +@immutable +sealed class RegistrationState {} + +final class RegistrationInitial extends RegistrationState {} + +final class RegistrationLoading extends RegistrationState {} + +final class RegistrationError extends RegistrationState { + final String error; + + RegistrationError({required this.error}); +} + +final class RegistrationSuccessful extends RegistrationState {} diff --git a/mobile_app/lib/presentation/registration/registration_page.dart b/mobile_app/lib/presentation/registration/registration_page.dart new file mode 100644 index 0000000..fda9dde --- /dev/null +++ b/mobile_app/lib/presentation/registration/registration_page.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/presentation/registration/cubit/registration_cubit.dart'; + +class RegistrationPage extends StatelessWidget { + const RegistrationPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8), + child: BlocConsumer( + listener: (context, state) { + if (state is RegistrationSuccessful) { + Navigator.of(context).pushReplacementNamed(Routes.homePage); + } + }, builder: (_, state) { + return Column( + children: [ + const SizedBox( + height: 8, + ), + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'E-mail', + ), + controller: context.read().emailController, + ), + const SizedBox( + height: 8, + ), + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Password', + ), + controller: + context.read().passwordController, + ), + Spacer(), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 52), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + backgroundColor: green50, + foregroundColor: Colors.white, + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + onPressed: () => context.read().register(), + child: const Text('Register'), + ), + ], + ); + }), + ), + ), + ); + } +} diff --git a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart index 094366a..e9ad322 100644 --- a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart +++ b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/di/di_container.dart'; part 'splash_state.dart'; @@ -9,8 +10,9 @@ class SplashCubit extends Cubit { _init(); } - Future _init() async { - await Future.delayed(const Duration(seconds: 3)); + Future _init() async{ + init(); + await Future.delayed(const Duration(seconds: 2)); emit(SplashInitialized(nextRoute: Routes.auth)); } } diff --git a/mobile_app/lib/presentation/splash/splash_screen.dart b/mobile_app/lib/presentation/splash/splash_screen.dart index f12be94..d240c0a 100644 --- a/mobile_app/lib/presentation/splash/splash_screen.dart +++ b/mobile_app/lib/presentation/splash/splash_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/const.dart'; @@ -9,6 +10,8 @@ class SplashScreen extends StatelessWidget { @override Widget build(BuildContext context) { + // SchedulerBinding.instance.addPostFrameCallback + // WidgetsBinding.instance.addPostFrameCallback return Scaffold( body: BlocListener( listener: (context, state) { diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index aada730..afdd811 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -36,11 +36,22 @@ dependencies: bloc: ^8.1.3 cupertino_icons: ^1.0.2 meta: ^1.10.0 + retrofit: '>=4.0.0 <5.0.0' + logger: any #for logging purpose + json_annotation: ^4.8.1 + dio: ^5.4.1 + dartz: ^0.10.1 + get_it: ^7.6.7 + + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 + retrofit_generator: '>=7.0.0 <8.0.0' + build_runner: '>=2.3.0 <4.0.0' + json_serializable: ^6.6.2 flutter: uses-material-design: true From b80212767aa9628c22174fa6b4fe05cdda969f1a Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 28 Apr 2024 21:26:31 +0300 Subject: [PATCH 07/18] Created main screen --- mobile_app/assets/arrow-up.svg | 3 + mobile_app/assets/blood.svg | 3 + mobile_app/lib/config/colors.dart | 3 + mobile_app/lib/config/const.dart | 4 +- mobile_app/lib/config/navigation.dart | 6 +- mobile_app/lib/config/styles.dart | 23 ++++++ .../components/bottom_nav_bar.dart | 0 .../lib/presentation/components/card.dart | 65 +++++++++++++++ .../history/cubit/history_cubit.dart | 8 ++ .../history/cubit/history_state.dart | 6 ++ .../presentation/history/history_page.dart | 12 +++ .../presentation/home/cubit/home_cubit.dart | 8 ++ .../presentation/home/cubit/home_state.dart | 6 ++ .../lib/presentation/home/home_page.dart | 57 +++++++++++++ .../home/widgets/gauge_indicator.dart | 76 ++++++++++++++++++ .../home_page/home_page_widget.dart | 29 ------- .../cubit/home_page_cubit.dart | 0 .../cubit/home_page_state.dart | 0 .../lib/presentation/main/main_page.dart | 79 +++++++++++++++++++ .../widgets/bottom_data_widget.dart | 0 .../widgets/graph_widget.dart | 0 .../widgets/stack_graph_widget.dart | 2 +- .../splash/cubit/splash_cubit.dart | 2 +- mobile_app/pubspec.yaml | 48 +---------- 24 files changed, 358 insertions(+), 82 deletions(-) create mode 100644 mobile_app/assets/arrow-up.svg create mode 100644 mobile_app/assets/blood.svg create mode 100644 mobile_app/lib/config/styles.dart create mode 100644 mobile_app/lib/presentation/components/bottom_nav_bar.dart create mode 100644 mobile_app/lib/presentation/components/card.dart create mode 100644 mobile_app/lib/presentation/history/cubit/history_cubit.dart create mode 100644 mobile_app/lib/presentation/history/cubit/history_state.dart create mode 100644 mobile_app/lib/presentation/history/history_page.dart create mode 100644 mobile_app/lib/presentation/home/cubit/home_cubit.dart create mode 100644 mobile_app/lib/presentation/home/cubit/home_state.dart create mode 100644 mobile_app/lib/presentation/home/home_page.dart create mode 100644 mobile_app/lib/presentation/home/widgets/gauge_indicator.dart delete mode 100644 mobile_app/lib/presentation/home_page/home_page_widget.dart rename mobile_app/lib/presentation/{home_page => main}/cubit/home_page_cubit.dart (100%) rename mobile_app/lib/presentation/{home_page => main}/cubit/home_page_state.dart (100%) create mode 100644 mobile_app/lib/presentation/main/main_page.dart rename mobile_app/lib/presentation/{home_page => main}/widgets/bottom_data_widget.dart (100%) rename mobile_app/lib/presentation/{home_page => main}/widgets/graph_widget.dart (100%) rename mobile_app/lib/presentation/{home_page => main}/widgets/stack_graph_widget.dart (88%) diff --git a/mobile_app/assets/arrow-up.svg b/mobile_app/assets/arrow-up.svg new file mode 100644 index 0000000..eb3fc47 --- /dev/null +++ b/mobile_app/assets/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/assets/blood.svg b/mobile_app/assets/blood.svg new file mode 100644 index 0000000..fff128c --- /dev/null +++ b/mobile_app/assets/blood.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/lib/config/colors.dart b/mobile_app/lib/config/colors.dart index 10e1385..b5a9a59 100644 --- a/mobile_app/lib/config/colors.dart +++ b/mobile_app/lib/config/colors.dart @@ -1,3 +1,6 @@ import 'package:flutter/material.dart'; const green50 = Color(0xFF009E00); +const base50 = Color(0xFF5D5D5D); +const shadowColor = Color(0x40000000); +const base30 = Color(0xFF888888); diff --git a/mobile_app/lib/config/const.dart b/mobile_app/lib/config/const.dart index eba4c78..4422cb1 100644 --- a/mobile_app/lib/config/const.dart +++ b/mobile_app/lib/config/const.dart @@ -1,2 +1,4 @@ const logoSvg = 'assets/glucose.svg'; -const baseUrl = 'http://31.129.107.206:5000'; \ No newline at end of file +const arrowUpSvg = 'assets/arrow-up.svg'; +const bloodSvg = 'assets/blood.svg'; +const baseUrl = 'http://31.129.107.206:5000'; diff --git a/mobile_app/lib/config/navigation.dart b/mobile_app/lib/config/navigation.dart index 5dbc05e..4b41cf2 100644 --- a/mobile_app/lib/config/navigation.dart +++ b/mobile_app/lib/config/navigation.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/routes.dart'; import 'package:mobile_app/data/repository/auth_repository.dart'; -import 'package:mobile_app/presentation/home_page/cubit/home_page_cubit.dart'; -import 'package:mobile_app/presentation/home_page/home_page_widget.dart'; +import 'package:mobile_app/presentation/main/cubit/home_page_cubit.dart'; +import 'package:mobile_app/presentation/main/main_page.dart'; import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; import 'package:mobile_app/presentation/login/login_page.dart'; import 'package:mobile_app/presentation/registration/cubit/registration_cubit.dart'; @@ -28,7 +28,7 @@ class MainNavigation { case Routes.homePage: page = BlocProvider( create: (_) => HomePageCubit(), - child: const HomePageWidget(), + child: const MainPageWidget(), ); case Routes.registration: page = BlocProvider( diff --git a/mobile_app/lib/config/styles.dart b/mobile_app/lib/config/styles.dart new file mode 100644 index 0000000..4da65c6 --- /dev/null +++ b/mobile_app/lib/config/styles.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; + +const h4 = TextStyle( + fontSize: 26, + fontWeight: FontWeight.w500, +); + +const bodyMMedium = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, +); + +const bodyMRegular= TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, +); + +const bodySRegular = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: base30, +); diff --git a/mobile_app/lib/presentation/components/bottom_nav_bar.dart b/mobile_app/lib/presentation/components/bottom_nav_bar.dart new file mode 100644 index 0000000..e69de29 diff --git a/mobile_app/lib/presentation/components/card.dart b/mobile_app/lib/presentation/components/card.dart new file mode 100644 index 0000000..db61f64 --- /dev/null +++ b/mobile_app/lib/presentation/components/card.dart @@ -0,0 +1,65 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; + +class InfoCard extends StatelessWidget { + final String iconPath; + final String title; + final String value; + final String? time; + + const InfoCard({ + super.key, + required this.iconPath, + required this.title, + required this.value, + this.time, + }); + + @override + Widget build(BuildContext context) { + return Card( + surfaceTintColor: Colors.white, + color: Colors.white, + elevation: 20, + shadowColor: shadowColor, + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset(iconPath), + Text( + title, + style: bodyMMedium, + ), + ], + ), + Row( + children: [ + Text( + value, + style: h4, + ), + const Expanded( + child: Text( + ' mmol/L', + style: bodySRegular, + ), + ), + Visibility( + visible: time != null, + child: Text(time ?? 'dfdf'), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/mobile_app/lib/presentation/history/cubit/history_cubit.dart b/mobile_app/lib/presentation/history/cubit/history_cubit.dart new file mode 100644 index 0000000..af8d39a --- /dev/null +++ b/mobile_app/lib/presentation/history/cubit/history_cubit.dart @@ -0,0 +1,8 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'history_state.dart'; + +class HistoryCubit extends Cubit { + HistoryCubit() : super(HistoryInitial()); +} diff --git a/mobile_app/lib/presentation/history/cubit/history_state.dart b/mobile_app/lib/presentation/history/cubit/history_state.dart new file mode 100644 index 0000000..ce65197 --- /dev/null +++ b/mobile_app/lib/presentation/history/cubit/history_state.dart @@ -0,0 +1,6 @@ +part of 'history_cubit.dart'; + +@immutable +sealed class HistoryState {} + +final class HistoryInitial extends HistoryState {} diff --git a/mobile_app/lib/presentation/history/history_page.dart b/mobile_app/lib/presentation/history/history_page.dart new file mode 100644 index 0000000..96148cf --- /dev/null +++ b/mobile_app/lib/presentation/history/history_page.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class HistoryPage extends StatelessWidget { + const HistoryPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text('History'), + ); + } +} diff --git a/mobile_app/lib/presentation/home/cubit/home_cubit.dart b/mobile_app/lib/presentation/home/cubit/home_cubit.dart new file mode 100644 index 0000000..0774ec6 --- /dev/null +++ b/mobile_app/lib/presentation/home/cubit/home_cubit.dart @@ -0,0 +1,8 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'home_state.dart'; + +class HomeCubit extends Cubit { + HomeCubit() : super(HomeInitial()); +} diff --git a/mobile_app/lib/presentation/home/cubit/home_state.dart b/mobile_app/lib/presentation/home/cubit/home_state.dart new file mode 100644 index 0000000..588d960 --- /dev/null +++ b/mobile_app/lib/presentation/home/cubit/home_state.dart @@ -0,0 +1,6 @@ +part of 'home_cubit.dart'; + +@immutable +sealed class HomeState {} + +final class HomeInitial extends HomeState {} diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart new file mode 100644 index 0000000..2bb561e --- /dev/null +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/presentation/components/card.dart'; +import 'package:mobile_app/presentation/home/widgets/gauge_indicator.dart'; + +class HomePageWidget extends StatelessWidget { + const HomePageWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Hello, Maxim', + style: h4, + ), + Text( + 'Your glucose data today are', + style: bodyMRegular, + ), + SizedBox( + height: 200, + width: double.infinity, + child: GaugeIndicator( + value: 2, + ), + ), + Row( + children: [ + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: '5.7', + ), + ), + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: '5.7', + ), + ), + ], + ), + InfoCard(iconPath: arrowUpSvg, title: 'Highest', value: '5.7'), + InfoCard(iconPath: arrowUpSvg, title: 'Highest', value: '5.7') + ], + ), + ); + } +} diff --git a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart new file mode 100644 index 0000000..1f9dd41 --- /dev/null +++ b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart @@ -0,0 +1,76 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/config/styles.dart'; + +class GaugeIndicator extends StatelessWidget { + final int value; + const GaugeIndicator({super.key, required this.value}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: GaugePainter(), + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only( + bottom: 12, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(bloodSvg), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + value.toString(), + style: h4, + ), + const Text( + 'mmol/L', + style: h4, + ) + ], + ), + ], + ), + ), + ), + ); + } +} + +class GaugePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final width = size.width; + final height = size.height; + const strokeWidth = 20.0; + const halfOfStrokeWidth = strokeWidth / 2; + final paint = Paint() + ..color = Colors.green + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth + ..strokeCap = StrokeCap.round; + final paint2 = Paint() + ..color = Colors.grey[200]! + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth + ..strokeCap = StrokeCap.round; + final drawRect = Rect.fromLTRB( + 0 + halfOfStrokeWidth, + 0 + halfOfStrokeWidth, + width - halfOfStrokeWidth, + height * 2 - halfOfStrokeWidth * 2, + ); + canvas.drawArc(drawRect, pi, pi, false, paint2); + canvas.drawArc(drawRect, pi, pi * 3 / 4, false, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/mobile_app/lib/presentation/home_page/home_page_widget.dart b/mobile_app/lib/presentation/home_page/home_page_widget.dart deleted file mode 100644 index 05eb4c5..0000000 --- a/mobile_app/lib/presentation/home_page/home_page_widget.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile_app/presentation/home_page/widgets/bottom_data_widget.dart'; -import 'package:mobile_app/presentation/home_page/widgets/stack_graph_widget.dart'; - -class HomePageWidget extends StatelessWidget { - const HomePageWidget({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Glucometer'), - ), - body: const Flex( - direction: Axis.vertical, - children: [ - Flexible( - flex: 6, - child: StackGraphWidget(), - ), - Flexible( - flex: 4, - child: BottomDataWidget(), - ), - ], - ), - ); - } -} diff --git a/mobile_app/lib/presentation/home_page/cubit/home_page_cubit.dart b/mobile_app/lib/presentation/main/cubit/home_page_cubit.dart similarity index 100% rename from mobile_app/lib/presentation/home_page/cubit/home_page_cubit.dart rename to mobile_app/lib/presentation/main/cubit/home_page_cubit.dart diff --git a/mobile_app/lib/presentation/home_page/cubit/home_page_state.dart b/mobile_app/lib/presentation/main/cubit/home_page_state.dart similarity index 100% rename from mobile_app/lib/presentation/home_page/cubit/home_page_state.dart rename to mobile_app/lib/presentation/main/cubit/home_page_state.dart diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart new file mode 100644 index 0000000..92d1d5d --- /dev/null +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/presentation/history/history_page.dart'; +import 'package:mobile_app/presentation/home/home_page.dart'; + + +class MainPageWidget extends StatefulWidget { + const MainPageWidget({super.key}); + + @override + State createState() => _MainPageWidgetState(); +} + +class _MainPageWidgetState extends State { + int _selectedIndex = 0; + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + title: const Text('Home'), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: IndexedStack( + index: _selectedIndex, + children: const [ + HomePageWidget(), + HistoryPage(), + HomePageWidget(), + HomePageWidget(), + HomePageWidget(), + ], + ), + ), + bottomNavigationBar: BottomNavigationBar( + backgroundColor: Colors.white, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home_outlined), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.history), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.add), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.auto_graph), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.settings), + label: 'fdsf', + ), + ], + onTap: _onItemTapped, + selectedItemColor: green50, + currentIndex: _selectedIndex, + unselectedItemColor: base50, + showSelectedLabels: false, + showUnselectedLabels: false, + iconSize: 30, + type: BottomNavigationBarType.fixed, + ), + ); + } +} diff --git a/mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart b/mobile_app/lib/presentation/main/widgets/bottom_data_widget.dart similarity index 100% rename from mobile_app/lib/presentation/home_page/widgets/bottom_data_widget.dart rename to mobile_app/lib/presentation/main/widgets/bottom_data_widget.dart diff --git a/mobile_app/lib/presentation/home_page/widgets/graph_widget.dart b/mobile_app/lib/presentation/main/widgets/graph_widget.dart similarity index 100% rename from mobile_app/lib/presentation/home_page/widgets/graph_widget.dart rename to mobile_app/lib/presentation/main/widgets/graph_widget.dart diff --git a/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart b/mobile_app/lib/presentation/main/widgets/stack_graph_widget.dart similarity index 88% rename from mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart rename to mobile_app/lib/presentation/main/widgets/stack_graph_widget.dart index d2ed22f..895a8b4 100644 --- a/mobile_app/lib/presentation/home_page/widgets/stack_graph_widget.dart +++ b/mobile_app/lib/presentation/main/widgets/stack_graph_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app/presentation/home_page/widgets/graph_widget.dart'; +import 'package:mobile_app/presentation/main/widgets/graph_widget.dart'; class StackGraphWidget extends StatelessWidget { const StackGraphWidget({super.key}); diff --git a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart index e9ad322..3e433bd 100644 --- a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart +++ b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart @@ -13,6 +13,6 @@ class SplashCubit extends Cubit { Future _init() async{ init(); await Future.delayed(const Duration(seconds: 2)); - emit(SplashInitialized(nextRoute: Routes.auth)); + emit(SplashInitialized(nextRoute: Routes.homePage)); } } diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index afdd811..f0f3d18 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -1,32 +1,12 @@ name: mobile_app description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: '>=3.2.3 <4.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter @@ -57,29 +37,3 @@ flutter: uses-material-design: true assets: - assets/ - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages From a25399cdc3755874b836d4eb8e474fcfdaef1e0e Mon Sep 17 00:00:00 2001 From: Max Size Date: Tue, 30 Apr 2024 23:31:02 +0300 Subject: [PATCH 08/18] Implemented history page --- .../lib/domain/entities/glucose_event.dart | 21 +++++ .../lib/domain/glucose_event_types.dart | 5 + .../lib/presentation/components/card.dart | 5 +- .../presentation/history/history_page.dart | 10 +- .../history/widgets/day_column.dart | 69 ++++++++++++++ .../lib/presentation/home/home_page.dart | 92 +++++++++++-------- .../home/widgets/gauge_indicator.dart | 87 ++++++++++++++++-- .../lib/presentation/main/main_page.dart | 16 +++- 8 files changed, 252 insertions(+), 53 deletions(-) create mode 100644 mobile_app/lib/domain/entities/glucose_event.dart create mode 100644 mobile_app/lib/domain/glucose_event_types.dart create mode 100644 mobile_app/lib/presentation/history/widgets/day_column.dart diff --git a/mobile_app/lib/domain/entities/glucose_event.dart b/mobile_app/lib/domain/entities/glucose_event.dart new file mode 100644 index 0000000..7967303 --- /dev/null +++ b/mobile_app/lib/domain/entities/glucose_event.dart @@ -0,0 +1,21 @@ +import 'package:mobile_app/domain/glucose_event_types.dart'; + +class GlucoseEvent { + final GlucoseEventType eventType; + final DateTime dateTime; + final double value; + + GlucoseEvent({ + required this.eventType, + required this.dateTime, + required this.value, + }); + + String get title => switch (eventType) { + GlucoseEventType.sport => 'Activity', + GlucoseEventType.insulin => 'Insulin', + GlucoseEventType.food => 'Food', + }; + + String get time => '${dateTime.hour}:${dateTime.minute}'; +} diff --git a/mobile_app/lib/domain/glucose_event_types.dart b/mobile_app/lib/domain/glucose_event_types.dart new file mode 100644 index 0000000..200e399 --- /dev/null +++ b/mobile_app/lib/domain/glucose_event_types.dart @@ -0,0 +1,5 @@ +enum GlucoseEventType { + sport, + insulin, + food, +} diff --git a/mobile_app/lib/presentation/components/card.dart b/mobile_app/lib/presentation/components/card.dart index db61f64..02bac5b 100644 --- a/mobile_app/lib/presentation/components/card.dart +++ b/mobile_app/lib/presentation/components/card.dart @@ -1,6 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; @@ -8,7 +7,7 @@ import 'package:mobile_app/config/styles.dart'; class InfoCard extends StatelessWidget { final String iconPath; final String title; - final String value; + final double value; final String? time; const InfoCard({ @@ -42,7 +41,7 @@ class InfoCard extends StatelessWidget { Row( children: [ Text( - value, + value.toString(), style: h4, ), const Expanded( diff --git a/mobile_app/lib/presentation/history/history_page.dart b/mobile_app/lib/presentation/history/history_page.dart index 96148cf..9d5097e 100644 --- a/mobile_app/lib/presentation/history/history_page.dart +++ b/mobile_app/lib/presentation/history/history_page.dart @@ -1,12 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:mobile_app/presentation/history/widgets/day_column.dart'; class HistoryPage extends StatelessWidget { const HistoryPage({super.key}); @override Widget build(BuildContext context) { - return const Center( - child: Text('History'), + return Scrollbar( + child: ListView.builder( + itemBuilder: (context, index) { + return DayColumn(day: 'February 14th, today', glucoseEvents: mockList); + }, + itemCount: 4, + ), ); } } diff --git a/mobile_app/lib/presentation/history/widgets/day_column.dart b/mobile_app/lib/presentation/history/widgets/day_column.dart new file mode 100644 index 0000000..ff4c95c --- /dev/null +++ b/mobile_app/lib/presentation/history/widgets/day_column.dart @@ -0,0 +1,69 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/config/styles.dart'; + +import 'package:mobile_app/domain/entities/glucose_event.dart'; +import 'package:mobile_app/domain/glucose_event_types.dart'; +import 'package:mobile_app/presentation/components/card.dart'; + +class DayColumn extends StatelessWidget { + final String day; + final List glucoseEvents; + const DayColumn({ + super.key, + required this.day, + required this.glucoseEvents, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: Text( + day, + style: h4, + ), + ), + for (final event in glucoseEvents) + InfoCard( + iconPath: arrowUpSvg, + title: event.title, + value: event.value, + time: event.time, + ) + ], + ); + } +} + +final mockList = [ + GlucoseEvent( + eventType: GlucoseEventType.food, + dateTime: DateTime.now(), + value: 5.4, + ), + GlucoseEvent( + eventType: GlucoseEventType.insulin, + dateTime: DateTime.now(), + value: 3.1, + ), + GlucoseEvent( + eventType: GlucoseEventType.sport, + dateTime: DateTime.now(), + value: 2.6, + ), + GlucoseEvent( + eventType: GlucoseEventType.food, + dateTime: DateTime.now(), + value: 5.4, + ), + GlucoseEvent( + eventType: GlucoseEventType.sport, + dateTime: DateTime.now(), + value: 5.4, + ), +]; diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart index 2bb561e..0770891 100644 --- a/mobile_app/lib/presentation/home/home_page.dart +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -11,47 +11,65 @@ class HomePageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return const SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Hello, Maxim', - style: h4, - ), - Text( - 'Your glucose data today are', - style: bodyMRegular, - ), - SizedBox( - height: 200, - width: double.infinity, - child: GaugeIndicator( - value: 2, + return const Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Hello, Maxim', + style: h4, + ), + Text( + 'Your glucose data today are', + style: bodyMRegular, ), + ], + ), + SizedBox( + height: 20, + ), + SizedBox( + height: 200, + width: double.infinity, + child: GaugeIndicator( + value: 4.3, ), - Row( - children: [ - Expanded( - child: InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: '5.7', - ), + ), + SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, ), - Expanded( - child: InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: '5.7', - ), + ), + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, ), - ], - ), - InfoCard(iconPath: arrowUpSvg, title: 'Highest', value: '5.7'), - InfoCard(iconPath: arrowUpSvg, title: 'Highest', value: '5.7') - ], - ), + ), + ], + ), + InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ], ); } } diff --git a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart index 1f9dd41..ce2ad0b 100644 --- a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart +++ b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart @@ -1,18 +1,25 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; import 'dart:math'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; + import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/styles.dart'; class GaugeIndicator extends StatelessWidget { - final int value; - const GaugeIndicator({super.key, required this.value}); + final double value; + const GaugeIndicator({ + super.key, + required this.value, + }); @override Widget build(BuildContext context) { return CustomPaint( - painter: GaugePainter(), + painter: GaugePainter(value: value), child: Align( alignment: Alignment.bottomCenter, child: Padding( @@ -45,14 +52,38 @@ class GaugeIndicator extends StatelessWidget { } class GaugePainter extends CustomPainter { + final double value; + final _minValue = 0; + final _maxValue = 6; + + GaugePainter({ + required this.value, + }); + @override void paint(Canvas canvas, Size size) { + final valuePercentage = value / (_maxValue - _minValue); + final width = size.width; final height = size.height; const strokeWidth = 20.0; const halfOfStrokeWidth = strokeWidth / 2; + const top = 0 + halfOfStrokeWidth; + final bottom = height * 2 - halfOfStrokeWidth * 2; + const left = 0 + halfOfStrokeWidth; + final right = width - halfOfStrokeWidth; + final radius = (bottom - top) / 2; + final center = Offset((right - left) / 2, radius); final paint = Paint() - ..color = Colors.green + ..shader = ui.Gradient.radial( + center, + radius, + [ + Colors.red, + Colors.yellow, + //Colors.green, + ], + ) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; @@ -62,15 +93,53 @@ class GaugePainter extends CustomPainter { ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; final drawRect = Rect.fromLTRB( - 0 + halfOfStrokeWidth, - 0 + halfOfStrokeWidth, - width - halfOfStrokeWidth, - height * 2 - halfOfStrokeWidth * 2, + left, + top, + right, + bottom, ); canvas.drawArc(drawRect, pi, pi, false, paint2); - canvas.drawArc(drawRect, pi, pi * 3 / 4, false, paint); + canvas.drawArc(drawRect, pi, pi * valuePercentage, false, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; + + GaugePainter copyWith({ + double? value, + }) { + return GaugePainter( + value: value ?? this.value, + ); + } + + Map toMap() { + return { + 'value': value, + }; + } + + factory GaugePainter.fromMap(Map map) { + return GaugePainter( + value: map['value'] as double, + ); + } + + String toJson() => json.encode(toMap()); + + factory GaugePainter.fromJson(String source) => + GaugePainter.fromMap(json.decode(source) as Map); + + @override + String toString() => 'GaugePainter(value: $value)'; + + @override + bool operator ==(covariant GaugePainter other) { + if (identical(this, other)) return true; + + return other.value == value; + } + + @override + int get hashCode => value.hashCode; } diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index 92d1d5d..4bcca35 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -3,7 +3,6 @@ import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/presentation/history/history_page.dart'; import 'package:mobile_app/presentation/home/home_page.dart'; - class MainPageWidget extends StatefulWidget { const MainPageWidget({super.key}); @@ -20,13 +19,26 @@ class _MainPageWidgetState extends State { }); } + String getTitle(int index) { + return switch (index) { + 0 => 'Home', + 1 => 'History', + 2 => 'Add', + 3 => 'Chart', + 4 => 'Settings', + _ => 'Unknown', + }; + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, - title: const Text('Home'), + title: Text( + getTitle(_selectedIndex), + ), ), body: Padding( padding: const EdgeInsets.all(16), From db4f18fe49da6d75df96e1955a3ce16fd976a1e7 Mon Sep 17 00:00:00 2001 From: Max Size Date: Thu, 2 May 2024 10:24:52 +0300 Subject: [PATCH 09/18] Created add note screen --- mobile_app/assets/arrow-down.svg | 3 + mobile_app/assets/fork_knife.svg | 3 + mobile_app/assets/middle.svg | 3 + mobile_app/assets/needle.svg | 3 + mobile_app/assets/run.svg | 3 + mobile_app/assets/variability.svg | 3 + mobile_app/lib/config/const.dart | 3 + .../lib/domain/entities/glucose_event.dart | 11 ++- .../lib/domain/glucose_event_types.dart | 12 ++- .../presentation/add_note/add_note_page.dart | 88 +++++++++++++++++++ .../add_note/cubit/add_note_cubit.dart | 14 +++ .../add_note/cubit/add_note_state.dart | 18 ++++ .../presentation/history/history_page.dart | 16 ++-- .../history/widgets/day_column.dart | 7 +- .../lib/presentation/main/main_page.dart | 18 ++-- 15 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 mobile_app/assets/arrow-down.svg create mode 100644 mobile_app/assets/fork_knife.svg create mode 100644 mobile_app/assets/middle.svg create mode 100644 mobile_app/assets/needle.svg create mode 100644 mobile_app/assets/run.svg create mode 100644 mobile_app/assets/variability.svg create mode 100644 mobile_app/lib/presentation/add_note/add_note_page.dart create mode 100644 mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart create mode 100644 mobile_app/lib/presentation/add_note/cubit/add_note_state.dart diff --git a/mobile_app/assets/arrow-down.svg b/mobile_app/assets/arrow-down.svg new file mode 100644 index 0000000..e06680a --- /dev/null +++ b/mobile_app/assets/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/assets/fork_knife.svg b/mobile_app/assets/fork_knife.svg new file mode 100644 index 0000000..8b62989 --- /dev/null +++ b/mobile_app/assets/fork_knife.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/assets/middle.svg b/mobile_app/assets/middle.svg new file mode 100644 index 0000000..3cdc512 --- /dev/null +++ b/mobile_app/assets/middle.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/assets/needle.svg b/mobile_app/assets/needle.svg new file mode 100644 index 0000000..89b0f5d --- /dev/null +++ b/mobile_app/assets/needle.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/assets/run.svg b/mobile_app/assets/run.svg new file mode 100644 index 0000000..eea3fe3 --- /dev/null +++ b/mobile_app/assets/run.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/assets/variability.svg b/mobile_app/assets/variability.svg new file mode 100644 index 0000000..afbdd47 --- /dev/null +++ b/mobile_app/assets/variability.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile_app/lib/config/const.dart b/mobile_app/lib/config/const.dart index 4422cb1..d25511a 100644 --- a/mobile_app/lib/config/const.dart +++ b/mobile_app/lib/config/const.dart @@ -1,4 +1,7 @@ const logoSvg = 'assets/glucose.svg'; const arrowUpSvg = 'assets/arrow-up.svg'; const bloodSvg = 'assets/blood.svg'; +const activitySvg = 'assets/run.svg'; +const injectionSvg = 'assets/needle.svg'; +const foodSvg = 'assets/fork_knife.svg'; const baseUrl = 'http://31.129.107.206:5000'; diff --git a/mobile_app/lib/domain/entities/glucose_event.dart b/mobile_app/lib/domain/entities/glucose_event.dart index 7967303..3c628f8 100644 --- a/mobile_app/lib/domain/entities/glucose_event.dart +++ b/mobile_app/lib/domain/entities/glucose_event.dart @@ -1,20 +1,23 @@ +import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/domain/glucose_event_types.dart'; class GlucoseEvent { final GlucoseEventType eventType; final DateTime dateTime; final double value; + final String title; GlucoseEvent({ required this.eventType, required this.dateTime, required this.value, + required this.title, }); - String get title => switch (eventType) { - GlucoseEventType.sport => 'Activity', - GlucoseEventType.insulin => 'Insulin', - GlucoseEventType.food => 'Food', + String get iconPath => switch (eventType) { + GlucoseEventType.sport => activitySvg, + GlucoseEventType.insulin => injectionSvg, + GlucoseEventType.food => foodSvg, }; String get time => '${dateTime.hour}:${dateTime.minute}'; diff --git a/mobile_app/lib/domain/glucose_event_types.dart b/mobile_app/lib/domain/glucose_event_types.dart index 200e399..a6debf7 100644 --- a/mobile_app/lib/domain/glucose_event_types.dart +++ b/mobile_app/lib/domain/glucose_event_types.dart @@ -1,5 +1,11 @@ + enum GlucoseEventType { - sport, - insulin, - food, + sport('Sport'), + insulin('Insulin'), + food('Food'); + + const GlucoseEventType(this.title); + final String title; } + + diff --git a/mobile_app/lib/presentation/add_note/add_note_page.dart b/mobile_app/lib/presentation/add_note/add_note_page.dart new file mode 100644 index 0000000..a43f250 --- /dev/null +++ b/mobile_app/lib/presentation/add_note/add_note_page.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/domain/glucose_event_types.dart'; +import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; + +class AddNotePage extends StatelessWidget { + const AddNotePage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + children: [ + DropdownMenu( + width: MediaQuery.of(context).size.width - 16 * 2, + label: const Text('Type of event'), + hintText: 'Select an option', + initialSelection: state.selectedGlucoseEventType, + dropdownMenuEntries: GlucoseEventType.values + .map( + (eventType) => DropdownMenuEntry( + value: eventType, + label: eventType.title, + ), + ) + .toList(), + onSelected: (value) => + context.read().onSelectedEventTypeChange(value), + ), + const SizedBox( + height: 16, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Description', + hintText: 'Some hint...', + border: OutlineInputBorder(), + ), + ), + const SizedBox( + height: 16, + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Level of glucose', + helperText: 'mmol/L', + hintText: 'Some hint...', + border: OutlineInputBorder(), + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: TextFormField( + decoration: const InputDecoration( + labelText: 'Date', + hintText: 'Some hint...', + border: OutlineInputBorder(), + ), + enabled: false, + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: TextFormField( + decoration: const InputDecoration( + labelText: 'Time', + hintText: 'Some hint...', + border: OutlineInputBorder(), + ), + enabled: false, + ), + ), + ], + ) + ], + ); + }, + ); + } +} diff --git a/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart b/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart new file mode 100644 index 0000000..df555fc --- /dev/null +++ b/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart @@ -0,0 +1,14 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:mobile_app/domain/glucose_event_types.dart'; + +part 'add_note_state.dart'; + +class AddNoteCubit extends Cubit { + AddNoteCubit() : super(const AddNoteState()); + + void onSelectedEventTypeChange(GlucoseEventType? pickedEventType){ + emit(state.copyWith(selectedGlucoseEventType: pickedEventType)); + } + +} diff --git a/mobile_app/lib/presentation/add_note/cubit/add_note_state.dart b/mobile_app/lib/presentation/add_note/cubit/add_note_state.dart new file mode 100644 index 0000000..6d3c16c --- /dev/null +++ b/mobile_app/lib/presentation/add_note/cubit/add_note_state.dart @@ -0,0 +1,18 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +part of 'add_note_cubit.dart'; + +@immutable +class AddNoteState { + final GlucoseEventType? selectedGlucoseEventType; + + const AddNoteState({this.selectedGlucoseEventType}); + + AddNoteState copyWith({ + GlucoseEventType? selectedGlucoseEventType, + }) { + return AddNoteState( + selectedGlucoseEventType: + selectedGlucoseEventType ?? this.selectedGlucoseEventType, + ); + } +} diff --git a/mobile_app/lib/presentation/history/history_page.dart b/mobile_app/lib/presentation/history/history_page.dart index 9d5097e..c812b8e 100644 --- a/mobile_app/lib/presentation/history/history_page.dart +++ b/mobile_app/lib/presentation/history/history_page.dart @@ -6,13 +6,15 @@ class HistoryPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scrollbar( - child: ListView.builder( - itemBuilder: (context, index) { - return DayColumn(day: 'February 14th, today', glucoseEvents: mockList); - }, - itemCount: 4, - ), + return ListView.builder( + clipBehavior: Clip.none, + itemBuilder: (context, index) { + return DayColumn( + day: 'February 14th, today', + glucoseEvents: mockList, + ); + }, + itemCount: 4, ); } } diff --git a/mobile_app/lib/presentation/history/widgets/day_column.dart b/mobile_app/lib/presentation/history/widgets/day_column.dart index ff4c95c..498830e 100644 --- a/mobile_app/lib/presentation/history/widgets/day_column.dart +++ b/mobile_app/lib/presentation/history/widgets/day_column.dart @@ -30,7 +30,7 @@ class DayColumn extends StatelessWidget { ), for (final event in glucoseEvents) InfoCard( - iconPath: arrowUpSvg, + iconPath: event.iconPath, title: event.title, value: event.value, time: event.time, @@ -45,25 +45,30 @@ final mockList = [ eventType: GlucoseEventType.food, dateTime: DateTime.now(), value: 5.4, + title: 'Light breakfast', ), GlucoseEvent( eventType: GlucoseEventType.insulin, dateTime: DateTime.now(), value: 3.1, + title: 'Light breakfast', ), GlucoseEvent( eventType: GlucoseEventType.sport, dateTime: DateTime.now(), value: 2.6, + title: 'Light breakfast', ), GlucoseEvent( eventType: GlucoseEventType.food, dateTime: DateTime.now(), value: 5.4, + title: 'Light breakfast', ), GlucoseEvent( eventType: GlucoseEventType.sport, dateTime: DateTime.now(), value: 5.4, + title: 'Light breakfast', ), ]; diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index 4bcca35..ec3879f 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/presentation/add_note/add_note_page.dart'; +import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; import 'package:mobile_app/presentation/history/history_page.dart'; import 'package:mobile_app/presentation/home/home_page.dart'; @@ -44,12 +47,15 @@ class _MainPageWidgetState extends State { padding: const EdgeInsets.all(16), child: IndexedStack( index: _selectedIndex, - children: const [ - HomePageWidget(), - HistoryPage(), - HomePageWidget(), - HomePageWidget(), - HomePageWidget(), + children: [ + const HomePageWidget(), + const HistoryPage(), + BlocProvider( + create: (context) => AddNoteCubit(), + child: const AddNotePage(), + ), + const HomePageWidget(), + const HomePageWidget(), ], ), ), From dfc9176d74a28f59cc9d363a9971a2352c8c7167 Mon Sep 17 00:00:00 2001 From: Max Size Date: Fri, 3 May 2024 22:59:15 +0300 Subject: [PATCH 10/18] created button component and made some ui improvements --- mobile_app/.gitignore | 46 ++ mobile_app/lib/config/colors.dart | 5 + mobile_app/lib/config/styles.dart | 19 +- .../lib/domain/glucose_event_types.dart | 9 +- .../presentation/add_note/add_note_page.dart | 115 +++-- .../lib/presentation/components/card.dart | 6 +- .../components/custom_button.dart | 59 +++ .../components/custom_text_field.dart | 43 ++ .../lib/presentation/home/home_page.dart | 2 +- .../home/widgets/gauge_indicator.dart | 18 +- mobile_app/pubspec.lock | 424 +++++++++++++++++- 11 files changed, 674 insertions(+), 72 deletions(-) create mode 100644 mobile_app/.gitignore create mode 100644 mobile_app/lib/presentation/components/custom_button.dart create mode 100644 mobile_app/lib/presentation/components/custom_text_field.dart diff --git a/mobile_app/.gitignore b/mobile_app/.gitignore new file mode 100644 index 0000000..a214f8e --- /dev/null +++ b/mobile_app/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +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 +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# 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 +*.g.dart +*.lock +pubspec.lock \ No newline at end of file diff --git a/mobile_app/lib/config/colors.dart b/mobile_app/lib/config/colors.dart index b5a9a59..4ed1792 100644 --- a/mobile_app/lib/config/colors.dart +++ b/mobile_app/lib/config/colors.dart @@ -4,3 +4,8 @@ const green50 = Color(0xFF009E00); const base50 = Color(0xFF5D5D5D); const shadowColor = Color(0x40000000); const base30 = Color(0xFF888888); +const base40 = Color(0xFF6D6D6D); +const base70 = Color(0xFF454545); +const base90 = Color(0xFF212121); + + diff --git a/mobile_app/lib/config/styles.dart b/mobile_app/lib/config/styles.dart index 4da65c6..f4b97c7 100644 --- a/mobile_app/lib/config/styles.dart +++ b/mobile_app/lib/config/styles.dart @@ -1,23 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app/config/colors.dart'; const h4 = TextStyle( fontSize: 26, fontWeight: FontWeight.w500, ); -const bodyMMedium = TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, -); - -const bodyMRegular= TextStyle( +const bodyMRegular = TextStyle( fontSize: 16, fontWeight: FontWeight.w400, ); -const bodySRegular = TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: base30, -); + +TextStyle bodyMMedium(Color color) => + TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: color); + +TextStyle bodySRegular(Color color) => + TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: color); diff --git a/mobile_app/lib/domain/glucose_event_types.dart b/mobile_app/lib/domain/glucose_event_types.dart index a6debf7..56212f8 100644 --- a/mobile_app/lib/domain/glucose_event_types.dart +++ b/mobile_app/lib/domain/glucose_event_types.dart @@ -1,11 +1,12 @@ enum GlucoseEventType { - sport('Sport'), - insulin('Insulin'), - food('Food'); + sport('Sport', 'assets/run.svg'), + insulin('Insulin','assets/needle.svg'), + food('Food','assets/fork_knife.svg'); - const GlucoseEventType(this.title); + const GlucoseEventType(this.title, this.iconPath); final String title; + final String iconPath; } diff --git a/mobile_app/lib/presentation/add_note/add_note_page.dart b/mobile_app/lib/presentation/add_note/add_note_page.dart index a43f250..ebe2c07 100644 --- a/mobile_app/lib/presentation/add_note/add_note_page.dart +++ b/mobile_app/lib/presentation/add_note/add_note_page.dart @@ -1,8 +1,14 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/domain/glucose_event_types.dart'; import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; +import 'package:mobile_app/presentation/components/custom_button.dart'; +import 'package:mobile_app/presentation/components/custom_text_field.dart'; class AddNotePage extends StatelessWidget { const AddNotePage({super.key}); @@ -13,42 +19,53 @@ class AddNotePage extends StatelessWidget { builder: (context, state) { return Column( children: [ - DropdownMenu( - width: MediaQuery.of(context).size.width - 16 * 2, - label: const Text('Type of event'), - hintText: 'Select an option', - initialSelection: state.selectedGlucoseEventType, - dropdownMenuEntries: GlucoseEventType.values - .map( - (eventType) => DropdownMenuEntry( - value: eventType, - label: eventType.title, - ), - ) - .toList(), - onSelected: (value) => - context.read().onSelectedEventTypeChange(value), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Type of event', + style: bodySRegular(base40), + ), + const SizedBox( + height: 4, + ), + DropdownMenu( + width: MediaQuery.of(context).size.width - 16 * 2, + hintText: 'Select an option', + initialSelection: state.selectedGlucoseEventType, + leadingIcon: state.selectedGlucoseEventType?.iconPath == null + ? null + : SvgPicture.asset( + state.selectedGlucoseEventType!.iconPath, + fit: BoxFit.scaleDown, + ), + dropdownMenuEntries: GlucoseEventType.values + .map( + (eventType) => DropdownMenuEntry( + value: eventType, + label: eventType.title, + leadingIcon: SvgPicture.asset(eventType.iconPath)), + ) + .toList(), + onSelected: (value) => context + .read() + .onSelectedEventTypeChange(value), + ), + ], ), const SizedBox( height: 16, ), - TextFormField( - decoration: const InputDecoration( - labelText: 'Description', - hintText: 'Some hint...', - border: OutlineInputBorder(), - ), + CustomTextField( + title: 'Description', + controller: TextEditingController(), ), const SizedBox( height: 16, ), - TextFormField( - decoration: const InputDecoration( - labelText: 'Level of glucose', - helperText: 'mmol/L', - hintText: 'Some hint...', - border: OutlineInputBorder(), - ), + CustomTextField( + title: 'Level of glucose', + controller: TextEditingController(), ), const SizedBox( height: 16, @@ -56,12 +73,9 @@ class AddNotePage extends StatelessWidget { Row( children: [ Expanded( - child: TextFormField( - decoration: const InputDecoration( - labelText: 'Date', - hintText: 'Some hint...', - border: OutlineInputBorder(), - ), + child: CustomTextField( + title: 'Date', + controller: TextEditingController(), enabled: false, ), ), @@ -69,17 +83,36 @@ class AddNotePage extends StatelessWidget { width: 12, ), Expanded( - child: TextFormField( - decoration: const InputDecoration( - labelText: 'Time', - hintText: 'Some hint...', - border: OutlineInputBorder(), - ), + child: CustomTextField( + title: 'Time', + controller: TextEditingController(), enabled: false, ), ), ], - ) + ), + const Spacer(), + Row( + children: [ + Expanded( + child: CustomButton( + text: 'Cancel', + buttonType: ButtonType.teritary, + onPressed: () => null, + ), + ), + const SizedBox( + width: 118, + ), + Expanded( + child: CustomButton( + text: 'Add note', + buttonType: ButtonType.primary, + onPressed: () => null, + ), + ), + ], + ), ], ); }, diff --git a/mobile_app/lib/presentation/components/card.dart b/mobile_app/lib/presentation/components/card.dart index 02bac5b..d280cbe 100644 --- a/mobile_app/lib/presentation/components/card.dart +++ b/mobile_app/lib/presentation/components/card.dart @@ -34,7 +34,7 @@ class InfoCard extends StatelessWidget { SvgPicture.asset(iconPath), Text( title, - style: bodyMMedium, + style: bodyMMedium(base90), ), ], ), @@ -44,10 +44,10 @@ class InfoCard extends StatelessWidget { value.toString(), style: h4, ), - const Expanded( + Expanded( child: Text( ' mmol/L', - style: bodySRegular, + style: bodySRegular(base30), ), ), Visibility( diff --git a/mobile_app/lib/presentation/components/custom_button.dart b/mobile_app/lib/presentation/components/custom_button.dart new file mode 100644 index 0000000..16858e8 --- /dev/null +++ b/mobile_app/lib/presentation/components/custom_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; + +enum ButtonType { + teritary, + secondary, + primary; + + TextStyle get textStyle => switch (this) { + ButtonType.teritary => bodyMMedium(base70), + ButtonType.secondary => bodyMMedium(green50), + ButtonType.primary => bodyMMedium(Colors.white), + }; + + Color get backgroundColor => switch (this) { + ButtonType.teritary => Colors.transparent, + ButtonType.secondary => Colors.transparent, + ButtonType.primary => green50, + }; +} + +class CustomButton extends StatelessWidget { + final String text; + final ButtonType buttonType; + final VoidCallback onPressed; + final double width; + const CustomButton({ + super.key, + required this.text, + required this.buttonType, + required this.onPressed, + this.width = double.infinity, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 52, + width: width, + child: ElevatedButton( + onPressed: onPressed, + style: ButtonStyle( + elevation: const MaterialStatePropertyAll(0), + backgroundColor: MaterialStatePropertyAll(buttonType.backgroundColor), + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + ), + child: Text( + text, + style: buttonType.textStyle, + ), + ), + ); + } +} diff --git a/mobile_app/lib/presentation/components/custom_text_field.dart b/mobile_app/lib/presentation/components/custom_text_field.dart new file mode 100644 index 0000000..f4902fd --- /dev/null +++ b/mobile_app/lib/presentation/components/custom_text_field.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; + +class CustomTextField extends StatelessWidget { + final String title; + final TextEditingController controller; + final bool enabled; + final VoidCallback? onTap; + + const CustomTextField({ + super.key, + required this.title, + required this.controller, + this.enabled = true, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: bodySRegular(base40), + ), + const SizedBox( + height: 4, + ), + TextFormField( + onTap: onTap, + readOnly: !enabled, + decoration: InputDecoration( + helperText: title == 'Level of glucose' ? 'mmol/L' : null, + border: const OutlineInputBorder(), + ), + ), + ], + ); + } +} diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart index 0770891..ac95815 100644 --- a/mobile_app/lib/presentation/home/home_page.dart +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -35,7 +35,7 @@ class HomePageWidget extends StatelessWidget { height: 200, width: double.infinity, child: GaugeIndicator( - value: 4.3, + value: 3, ), ), SizedBox( diff --git a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart index ce2ad0b..6823910 100644 --- a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart +++ b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/const.dart'; @@ -75,14 +76,25 @@ class GaugePainter extends CustomPainter { final radius = (bottom - top) / 2; final center = Offset((right - left) / 2, radius); final paint = Paint() - ..shader = ui.Gradient.radial( + ..shader = ui.Gradient.sweep( center, - radius, [ Colors.red, Colors.yellow, - //Colors.green, + Colors.green, + Colors.yellow, + Colors.red, + ], + [ + 0, + 0.2, + 0.5, + 0.8, + 1, ], + TileMode.mirror, + pi, + 2 * pi, ) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth diff --git a/mobile_app/pubspec.lock b/mobile_app/pubspec.lock index acfc065..c92ce6d 100644 --- a/mobile_app/pubspec.lock +++ b/mobile_app/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" args: dependency: transitive description: @@ -33,6 +49,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.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: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e + url: "https://pub.dev" + source: hosted + version: "8.9.1" characters: dependency: transitive description: @@ -41,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -49,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -57,6 +153,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -65,6 +177,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + dio: + dependency: "direct main" + description: + name: dio + sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8" + url: "https://pub.dev" + source: hosted + version: "5.4.1" equatable: dependency: transitive description: @@ -81,6 +217,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" fl_chart: dependency: "direct main" description: @@ -123,6 +275,38 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 + url: "https://pub.dev" + source: hosted + version: "7.6.7" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" http: dependency: transitive description: @@ -131,6 +315,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -139,6 +331,62 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + url: "https://pub.dev" + source: hosted + version: "6.7.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -147,30 +395,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + logger: + dependency: "direct main" + description: + name: logger + sha256: b3ff55aeb08d9d8901b767650285872cb1bb8f508373b3e348d60268b0c7f770 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: "direct main" description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" nested: dependency: transitive description: @@ -179,14 +451,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -203,6 +483,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" provider: dependency: transitive description: @@ -211,11 +499,75 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + retrofit: + dependency: "direct main" + description: + name: retrofit + sha256: "13a2865c0d97da580ea4e3c64d412d81f365fd5b26be2a18fca9582e021da37a" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + retrofit_generator: + dependency: "direct dev" + description: + name: retrofit_generator + sha256: "9499eb46b3657a62192ddbc208ff7e6c6b768b19e83c1ee6f6b119c864b99690" + url: "https://pub.dev" + source: hosted + version: "7.0.8" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" source_span: dependency: transitive description: @@ -240,6 +592,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -264,6 +624,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: @@ -304,6 +680,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -312,6 +704,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" xml: dependency: transitive description: @@ -320,6 +720,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.2.3 <4.0.0" flutter: ">=3.16.0" From af67043392a811f25a73ef2435d3045558bfbfa2 Mon Sep 17 00:00:00 2001 From: Max Size Date: Fri, 3 May 2024 23:19:47 +0300 Subject: [PATCH 11/18] little fixes --- .../lib/domain/entities/glucose_event.dart | 3 ++- .../presentation/add_note/add_note_page.dart | 24 +++++++------------ .../components/custom_button.dart | 6 ++--- .../lib/presentation/main/main_page.dart | 1 + 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/mobile_app/lib/domain/entities/glucose_event.dart b/mobile_app/lib/domain/entities/glucose_event.dart index 3c628f8..cbf9c2c 100644 --- a/mobile_app/lib/domain/entities/glucose_event.dart +++ b/mobile_app/lib/domain/entities/glucose_event.dart @@ -20,5 +20,6 @@ class GlucoseEvent { GlucoseEventType.food => foodSvg, }; - String get time => '${dateTime.hour}:${dateTime.minute}'; + String get time => + '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; } diff --git a/mobile_app/lib/presentation/add_note/add_note_page.dart b/mobile_app/lib/presentation/add_note/add_note_page.dart index ebe2c07..662f7d0 100644 --- a/mobile_app/lib/presentation/add_note/add_note_page.dart +++ b/mobile_app/lib/presentation/add_note/add_note_page.dart @@ -94,22 +94,16 @@ class AddNotePage extends StatelessWidget { const Spacer(), Row( children: [ - Expanded( - child: CustomButton( - text: 'Cancel', - buttonType: ButtonType.teritary, - onPressed: () => null, - ), - ), - const SizedBox( - width: 118, + CustomButton( + text: 'Cancel', + buttonType: ButtonType.teritary, + onPressed: () => null, ), - Expanded( - child: CustomButton( - text: 'Add note', - buttonType: ButtonType.primary, - onPressed: () => null, - ), + const Spacer(), + CustomButton( + text: 'Add note', + buttonType: ButtonType.primary, + onPressed: () => null, ), ], ), diff --git a/mobile_app/lib/presentation/components/custom_button.dart b/mobile_app/lib/presentation/components/custom_button.dart index 16858e8..7ccefa1 100644 --- a/mobile_app/lib/presentation/components/custom_button.dart +++ b/mobile_app/lib/presentation/components/custom_button.dart @@ -24,23 +24,23 @@ class CustomButton extends StatelessWidget { final String text; final ButtonType buttonType; final VoidCallback onPressed; - final double width; const CustomButton({ super.key, required this.text, required this.buttonType, required this.onPressed, - this.width = double.infinity, }); @override Widget build(BuildContext context) { return SizedBox( height: 52, - width: width, child: ElevatedButton( onPressed: onPressed, style: ButtonStyle( + padding: const MaterialStatePropertyAll( + EdgeInsets.symmetric(horizontal: 24), + ), elevation: const MaterialStatePropertyAll(0), backgroundColor: MaterialStatePropertyAll(buttonType.backgroundColor), shape: MaterialStatePropertyAll( diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index ec3879f..4d0b637 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -36,6 +36,7 @@ class _MainPageWidgetState extends State { @override Widget build(BuildContext context) { return Scaffold( + resizeToAvoidBottomInset: false, backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Colors.white, From 567663f826c1fba4517d097ebc58fa57b2419dff Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 5 May 2024 23:20:10 +0300 Subject: [PATCH 12/18] Created chart and settings page, added dark theme --- mobile_app/analysis_options.yaml | 5 + mobile_app/lib/config/colors.dart | 5 +- mobile_app/lib/config/navigation.dart | 6 +- mobile_app/lib/config/routes.dart | 2 +- mobile_app/lib/config/styles.dart | 10 +- mobile_app/lib/config/theme.dart | 27 +++- .../lib/domain/glucose_event_types.dart | 1 + mobile_app/lib/main.dart | 18 ++- .../presentation/add_note/add_note_page.dart | 4 +- .../presentation/chart/cubit/chart_cubit.dart | 15 +++ .../presentation/chart/cubit/chart_state.dart | 15 +++ mobile_app/lib/presentation/chart/screen.dart | 109 +++++++++++++++ .../presentation/chart/widgets/date_card.dart | 36 +++++ .../chart/widgets/date_column.dart | 41 ++++++ .../chart/widgets/line_chart.dart | 125 ++++++++++++++++++ .../lib/presentation/components/card.dart | 5 +- .../components/custom_button.dart | 6 +- .../history/widgets/day_column.dart | 1 - .../lib/presentation/home/home_page.dart | 24 ++-- .../home/widgets/gauge_indicator.dart | 1 - .../lib/presentation/login/login_page.dart | 2 +- .../lib/presentation/main/main_page.dart | 17 ++- .../registration/registration_page.dart | 2 +- .../settings/cubit/settings_cubit.dart | 8 ++ .../settings/cubit/settings_state.dart | 6 + .../lib/presentation/settings/screen.dart | 55 ++++++++ .../settings/widgets/radio_button_row.dart | 37 ++++++ .../settings/widgets/test_text.dart | 15 +++ .../splash/cubit/splash_cubit.dart | 2 +- .../presentation/splash/splash_screen.dart | 5 +- mobile_app/lib/theme/theme_cubit.dart | 26 ++++ mobile_app/lib/theme/theme_state.dart | 8 ++ 32 files changed, 592 insertions(+), 47 deletions(-) create mode 100644 mobile_app/lib/presentation/chart/cubit/chart_cubit.dart create mode 100644 mobile_app/lib/presentation/chart/cubit/chart_state.dart create mode 100644 mobile_app/lib/presentation/chart/screen.dart create mode 100644 mobile_app/lib/presentation/chart/widgets/date_card.dart create mode 100644 mobile_app/lib/presentation/chart/widgets/date_column.dart create mode 100644 mobile_app/lib/presentation/chart/widgets/line_chart.dart create mode 100644 mobile_app/lib/presentation/settings/cubit/settings_cubit.dart create mode 100644 mobile_app/lib/presentation/settings/cubit/settings_state.dart create mode 100644 mobile_app/lib/presentation/settings/screen.dart create mode 100644 mobile_app/lib/presentation/settings/widgets/radio_button_row.dart create mode 100644 mobile_app/lib/presentation/settings/widgets/test_text.dart create mode 100644 mobile_app/lib/theme/theme_cubit.dart create mode 100644 mobile_app/lib/theme/theme_state.dart diff --git a/mobile_app/analysis_options.yaml b/mobile_app/analysis_options.yaml index f7cfaef..f337e6a 100644 --- a/mobile_app/analysis_options.yaml +++ b/mobile_app/analysis_options.yaml @@ -1,4 +1,9 @@ include: package:lints/recommended.yaml +analyzer: + exclude: + - lib/data/data_source/remote/*.g.dart + - lib/data/models/request/*.g.dart + - lib/data/models/response/*.g.dart linter: rules: diff --git a/mobile_app/lib/config/colors.dart b/mobile_app/lib/config/colors.dart index 4ed1792..3221985 100644 --- a/mobile_app/lib/config/colors.dart +++ b/mobile_app/lib/config/colors.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; +const green5 = Color(0xFFEAF8E4); +const green30 = Color(0xFF66C64F); const green50 = Color(0xFF009E00); const base50 = Color(0xFF5D5D5D); const shadowColor = Color(0x40000000); const base30 = Color(0xFF888888); const base40 = Color(0xFF6D6D6D); const base70 = Color(0xFF454545); +const base80 = Color(0xFF383838); const base90 = Color(0xFF212121); - - diff --git a/mobile_app/lib/config/navigation.dart b/mobile_app/lib/config/navigation.dart index 4b41cf2..946275c 100644 --- a/mobile_app/lib/config/navigation.dart +++ b/mobile_app/lib/config/navigation.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/routes.dart'; import 'package:mobile_app/data/repository/auth_repository.dart'; -import 'package:mobile_app/presentation/main/cubit/home_page_cubit.dart'; -import 'package:mobile_app/presentation/main/main_page.dart'; import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; import 'package:mobile_app/presentation/login/login_page.dart'; +import 'package:mobile_app/presentation/main/cubit/home_page_cubit.dart'; +import 'package:mobile_app/presentation/main/main_page.dart'; import 'package:mobile_app/presentation/registration/cubit/registration_cubit.dart'; import 'package:mobile_app/presentation/registration/registration_page.dart'; import 'package:mobile_app/presentation/splash/cubit/splash_cubit.dart'; @@ -25,7 +25,7 @@ class MainNavigation { create: (_) => LoginCubit(authRepo: IAuthRepository()), child: const LoginPage(), ); - case Routes.homePage: + case Routes.main: page = BlocProvider( create: (_) => HomePageCubit(), child: const MainPageWidget(), diff --git a/mobile_app/lib/config/routes.dart b/mobile_app/lib/config/routes.dart index a306776..43e3427 100644 --- a/mobile_app/lib/config/routes.dart +++ b/mobile_app/lib/config/routes.dart @@ -1,6 +1,6 @@ abstract class Routes { static const splash = '/'; static const auth = '/auth'; - static const homePage = '/main'; + static const main = '/main'; static const registration = '/registration'; } diff --git a/mobile_app/lib/config/styles.dart b/mobile_app/lib/config/styles.dart index f4b97c7..66ce35b 100644 --- a/mobile_app/lib/config/styles.dart +++ b/mobile_app/lib/config/styles.dart @@ -5,14 +5,18 @@ const h4 = TextStyle( fontWeight: FontWeight.w500, ); +TextStyle captionRegular(Color color) => + TextStyle(fontSize: 12, fontWeight: FontWeight.w400, color: color); + const bodyMRegular = TextStyle( fontSize: 16, fontWeight: FontWeight.w400, ); - -TextStyle bodyMMedium(Color color) => - TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: color); +const bodyMMedium = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, +); TextStyle bodySRegular(Color color) => TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: color); diff --git a/mobile_app/lib/config/theme.dart b/mobile_app/lib/config/theme.dart index f6ce261..a3d5c2e 100644 --- a/mobile_app/lib/config/theme.dart +++ b/mobile_app/lib/config/theme.dart @@ -1,6 +1,31 @@ import 'package:flutter/material.dart'; import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; -final theme = ThemeData( +final lightTheme = ThemeData( + brightness: Brightness.light, + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Colors.white, + ), + scaffoldBackgroundColor: Colors.white, + appBarTheme: const AppBarTheme(backgroundColor: Colors.white), colorSchemeSeed: green50, + textTheme: textLightTheme, ); + +final textLightTheme = TextTheme( + headlineMedium: h4, + bodyMedium: bodyMRegular.copyWith(color: base90), + bodySmall: bodyMRegular.copyWith(color: base90), + displaySmall: captionRegular(base90), + bodyLarge: bodyMMedium.copyWith(color: base90), +); + +final darkTheme = ThemeData( + brightness: Brightness.dark, + colorSchemeSeed: green50, +); + +extension IsLight on ThemeData { + bool isLight() => brightness == Brightness.light; +} diff --git a/mobile_app/lib/domain/glucose_event_types.dart b/mobile_app/lib/domain/glucose_event_types.dart index 56212f8..28d5612 100644 --- a/mobile_app/lib/domain/glucose_event_types.dart +++ b/mobile_app/lib/domain/glucose_event_types.dart @@ -10,3 +10,4 @@ enum GlucoseEventType { } + diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart index 797340f..8ed5f6e 100644 --- a/mobile_app/lib/main.dart +++ b/mobile_app/lib/main.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/navigation.dart'; import 'package:mobile_app/config/routes.dart'; import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/theme/theme_cubit.dart'; void main() { runApp(const MyApp()); @@ -12,10 +14,18 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - theme: theme, - initialRoute: Routes.splash, - onGenerateRoute: MainNavigation.onGenerateRoute, + return BlocProvider( + create: (context) => ThemeCubit(), + child: BlocBuilder( + builder: (context, state) { + return MaterialApp( + theme: + state.brightness == Brightness.light ? lightTheme : darkTheme, + initialRoute: Routes.splash, + onGenerateRoute: MainNavigation.onGenerateRoute, + ); + }, + ), ); } } diff --git a/mobile_app/lib/presentation/add_note/add_note_page.dart b/mobile_app/lib/presentation/add_note/add_note_page.dart index 662f7d0..fb7d791 100644 --- a/mobile_app/lib/presentation/add_note/add_note_page.dart +++ b/mobile_app/lib/presentation/add_note/add_note_page.dart @@ -1,6 +1,4 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/colors.dart'; @@ -44,7 +42,7 @@ class AddNotePage extends StatelessWidget { (eventType) => DropdownMenuEntry( value: eventType, label: eventType.title, - leadingIcon: SvgPicture.asset(eventType.iconPath)), + leadingIcon: SvgPicture.asset(eventType.iconPath),), ) .toList(), onSelected: (value) => context diff --git a/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart b/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart new file mode 100644 index 0000000..8c8a514 --- /dev/null +++ b/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart @@ -0,0 +1,15 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'chart_state.dart'; + +class ChartCubit extends Cubit { + ChartCubit() : super(ChartLoading()) { + emit(ChartData(pickedDayIndex: 1)); + } + + void setPickedDay(int index) { + final curState = state as ChartData; + emit(curState.copyWith(pickedDayIndex: index)); + } +} diff --git a/mobile_app/lib/presentation/chart/cubit/chart_state.dart b/mobile_app/lib/presentation/chart/cubit/chart_state.dart new file mode 100644 index 0000000..d75b8b3 --- /dev/null +++ b/mobile_app/lib/presentation/chart/cubit/chart_state.dart @@ -0,0 +1,15 @@ +part of 'chart_cubit.dart'; + +@immutable +sealed class ChartState {} + +final class ChartLoading extends ChartState {} + +final class ChartData extends ChartState { + final int pickedDayIndex; + + ChartData({required this.pickedDayIndex}); + + ChartData copyWith({int? pickedDayIndex}) => + ChartData(pickedDayIndex: pickedDayIndex ?? this.pickedDayIndex); +} diff --git a/mobile_app/lib/presentation/chart/screen.dart b/mobile_app/lib/presentation/chart/screen.dart new file mode 100644 index 0000000..15a79a9 --- /dev/null +++ b/mobile_app/lib/presentation/chart/screen.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/presentation/chart/cubit/chart_cubit.dart'; +import 'package:mobile_app/presentation/chart/widgets/date_card.dart'; +import 'package:mobile_app/presentation/chart/widgets/line_chart.dart'; +import 'package:mobile_app/presentation/components/card.dart'; + +class ChartScreen extends StatelessWidget { + const ChartScreen({super.key}); + + @override + Widget build(BuildContext context) { + final cubit = context.read(); + return BlocBuilder( + builder: (context, state) { + return switch (state) { + ChartLoading() => const Center( + child: CircularProgressIndicator(), + ), + ChartData() => Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + height: 60, + child: ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + width: 13, + ), + itemBuilder: (context, index) => DateCard( + status: index == state.pickedDayIndex + ? DateCardStatus.picked + : DateCardStatus.notPicked, + onCardTap: () => cubit.setPickedDay(index), + ), + scrollDirection: Axis.horizontal, + itemCount: 20, + ), + ), + SizedBox( + height: 250, + child: Row( + children: [ + Column( + children: [ + Text( + 'mmol/L', + style: captionRegular(base40), + ), + Text( + '6', + style: bodySRegular(base40), + ), + const Spacer(), + Text( + '4', + style: bodySRegular(base40), + ), + const Spacer(), + Text( + '2', + style: bodySRegular(base40), + ), + const Spacer(), + ], + ), + const Expanded( + child: LineChartSample2(), + ), + ], + ), + ), + const Row( + children: [ + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ), + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ), + ], + ), + const InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + const InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ], + ), + }; + }, + ); + } +} diff --git a/mobile_app/lib/presentation/chart/widgets/date_card.dart b/mobile_app/lib/presentation/chart/widgets/date_card.dart new file mode 100644 index 0000000..c765ee5 --- /dev/null +++ b/mobile_app/lib/presentation/chart/widgets/date_card.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/presentation/chart/widgets/date_column.dart'; + +enum DateCardStatus { + picked, + notPicked, +} + +class DateCard extends StatelessWidget { + final DateCardStatus status; + final VoidCallback? onCardTap; + + const DateCard({ + super.key, + required this.status, + this.onCardTap, + }); + + @override + Widget build(BuildContext context) { + return status == DateCardStatus.picked + ? DecoratedBox( + decoration: BoxDecoration( + color: green5, + border: Border.all(color: green50), + borderRadius: BorderRadius.circular(4), + ), + child: const DateColumn(textColor: base90), + ) + : InkWell( + onTap: onCardTap, + child: const DateColumn(textColor: base40), + ); + } +} diff --git a/mobile_app/lib/presentation/chart/widgets/date_column.dart b/mobile_app/lib/presentation/chart/widgets/date_column.dart new file mode 100644 index 0000000..9260f3b --- /dev/null +++ b/mobile_app/lib/presentation/chart/widgets/date_column.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; + +class DateColumn extends StatelessWidget { + final Color textColor; + const DateColumn({ + super.key, + required this.textColor, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Wd', + style: Theme.of(context).isLight() + ? bodyMRegular.copyWith(color: textColor) + : bodyMRegular, + ), + const SizedBox( + height: 4, + ), + Text( + '6', + style: Theme.of(context).isLight() + ? bodyMRegular.copyWith(color: textColor) + : bodyMRegular, + ), + ], + ), + ); + } +} diff --git a/mobile_app/lib/presentation/chart/widgets/line_chart.dart b/mobile_app/lib/presentation/chart/widgets/line_chart.dart new file mode 100644 index 0000000..c0c1001 --- /dev/null +++ b/mobile_app/lib/presentation/chart/widgets/line_chart.dart @@ -0,0 +1,125 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; + +class LineChartSample2 extends StatefulWidget { + const LineChartSample2({super.key}); + + @override + State createState() => _LineChartSample2State(); +} + +class _LineChartSample2State extends State { + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: 1200, + child: Padding( + padding: const EdgeInsets.only( + right: 18, + left: 20, + top: 24, + bottom: 12, + ), + child: LineChart( + mainData(), + ), + ), + ), + ); + } + + Widget bottomTitleWidgets(double value, TitleMeta meta) { + final time = value.toInt() % 12; + final partOfDay = value >= 12 ? 'pm' : 'am'; + final text = Text( + '$time$partOfDay', + style: bodySRegular(base40), + ); + + return SideTitleWidget( + axisSide: meta.axisSide, + child: text, + ); + } + + Widget leftTitleWidgets(double value, TitleMeta meta) { + const style = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + ); + String text; + switch (value) { + case 2.0: + text = '2'; + break; + case 4.0: + text = '4'; + break; + case 6.0: + text = '6'; + break; + default: + return Container(); + } + + return Text(text, style: style, textAlign: TextAlign.left); + } + + LineChartData mainData() { + return LineChartData( + borderData: FlBorderData(show: false), + gridData: FlGridData( + drawVerticalLine: false, + horizontalInterval: 2, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey[400], + strokeWidth: 1, + dashArray: [7, 7], + ); + }, + ), + titlesData: FlTitlesData( + rightTitles: const AxisTitles(), + topTitles: const AxisTitles(), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + interval: 1, + getTitlesWidget: bottomTitleWidgets, + ), + ), + leftTitles: const AxisTitles(), + ), + minX: 0, + maxX: 23, + minY: 1, + maxY: 6.1, + lineBarsData: [ + LineChartBarData( + spots: const [ + FlSpot(0, 3), + FlSpot(2.6, 2), + FlSpot(4.9, 5), + FlSpot(6.8, 3.1), + FlSpot(8, 4), + FlSpot(9.5, 3), + FlSpot(11, 4), + ], + color: green30, + isCurved: true, + barWidth: 3, + isStrokeCapRound: true, + dotData: const FlDotData( + show: false, + ), + ), + ], + ); + } +} diff --git a/mobile_app/lib/presentation/components/card.dart b/mobile_app/lib/presentation/components/card.dart index d280cbe..06c6322 100644 --- a/mobile_app/lib/presentation/components/card.dart +++ b/mobile_app/lib/presentation/components/card.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; class InfoCard extends StatelessWidget { final String iconPath; @@ -34,7 +35,9 @@ class InfoCard extends StatelessWidget { SvgPicture.asset(iconPath), Text( title, - style: bodyMMedium(base90), + style: Theme.of(context).isLight() + ? bodyMMedium.copyWith(color: base90) + : bodyMMedium, ), ], ), diff --git a/mobile_app/lib/presentation/components/custom_button.dart b/mobile_app/lib/presentation/components/custom_button.dart index 7ccefa1..bf20ebc 100644 --- a/mobile_app/lib/presentation/components/custom_button.dart +++ b/mobile_app/lib/presentation/components/custom_button.dart @@ -8,9 +8,9 @@ enum ButtonType { primary; TextStyle get textStyle => switch (this) { - ButtonType.teritary => bodyMMedium(base70), - ButtonType.secondary => bodyMMedium(green50), - ButtonType.primary => bodyMMedium(Colors.white), + ButtonType.teritary => bodyMMedium.copyWith(color: base70), + ButtonType.secondary => bodyMMedium.copyWith(color: green50), + ButtonType.primary => bodyMMedium.copyWith(color: Colors.white), }; Color get backgroundColor => switch (this) { diff --git a/mobile_app/lib/presentation/history/widgets/day_column.dart b/mobile_app/lib/presentation/history/widgets/day_column.dart index 498830e..680203e 100644 --- a/mobile_app/lib/presentation/history/widgets/day_column.dart +++ b/mobile_app/lib/presentation/history/widgets/day_column.dart @@ -1,6 +1,5 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart'; -import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/domain/entities/glucose_event.dart'; diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart index ac95815..294efd1 100644 --- a/mobile_app/lib/presentation/home/home_page.dart +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -1,8 +1,8 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/components/card.dart'; import 'package:mobile_app/presentation/home/widgets/gauge_indicator.dart'; @@ -11,37 +11,39 @@ class HomePageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return const Column( + return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( 'Hello, Maxim', style: h4, ), Text( 'Your glucose data today are', - style: bodyMRegular, + style: Theme.of(context).isLight() + ? bodyMRegular.copyWith(color: base90) + : bodyMRegular, ), ], ), - SizedBox( + const SizedBox( height: 20, ), - SizedBox( + const SizedBox( height: 200, width: double.infinity, child: GaugeIndicator( value: 3, ), ), - SizedBox( + const SizedBox( height: 20, ), - Row( + const Row( children: [ Expanded( child: InfoCard( @@ -59,12 +61,12 @@ class HomePageWidget extends StatelessWidget { ), ], ), - InfoCard( + const InfoCard( iconPath: arrowUpSvg, title: 'Highest', value: 5.7, ), - InfoCard( + const InfoCard( iconPath: arrowUpSvg, title: 'Highest', value: 5.7, diff --git a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart index 6823910..81b8735 100644 --- a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart +++ b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/const.dart'; diff --git a/mobile_app/lib/presentation/login/login_page.dart b/mobile_app/lib/presentation/login/login_page.dart index 48170d9..60ec4ed 100644 --- a/mobile_app/lib/presentation/login/login_page.dart +++ b/mobile_app/lib/presentation/login/login_page.dart @@ -16,7 +16,7 @@ class LoginPage extends StatelessWidget { body: BlocConsumer( listener: (context, state) { if (state is LoginSuccessful) { - Navigator.of(context).pushReplacementNamed(Routes.homePage); + Navigator.of(context).pushReplacementNamed(Routes.main); } else if (state is NotRegisteredYet) { Navigator.of(context).pushReplacementNamed(Routes.registration); } diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index 4d0b637..6475e22 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/add_note/add_note_page.dart'; import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; +import 'package:mobile_app/presentation/chart/cubit/chart_cubit.dart'; +import 'package:mobile_app/presentation/chart/screen.dart'; import 'package:mobile_app/presentation/history/history_page.dart'; import 'package:mobile_app/presentation/home/home_page.dart'; +import 'package:mobile_app/presentation/settings/screen.dart'; class MainPageWidget extends StatefulWidget { const MainPageWidget({super.key}); @@ -37,9 +41,7 @@ class _MainPageWidgetState extends State { Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, - backgroundColor: Colors.white, appBar: AppBar( - backgroundColor: Colors.white, title: Text( getTitle(_selectedIndex), ), @@ -55,13 +57,16 @@ class _MainPageWidgetState extends State { create: (context) => AddNoteCubit(), child: const AddNotePage(), ), - const HomePageWidget(), - const HomePageWidget(), + BlocProvider( + create: (context) => ChartCubit(), + child: const ChartScreen(), + ), + const SettingsScreen(), ], ), ), bottomNavigationBar: BottomNavigationBar( - backgroundColor: Colors.white, + //backgroundColor: Colors.white, items: const [ BottomNavigationBarItem( icon: Icon(Icons.home_outlined), @@ -85,7 +90,7 @@ class _MainPageWidgetState extends State { ), ], onTap: _onItemTapped, - selectedItemColor: green50, + selectedItemColor: Theme.of(context).isLight() ? green50 : green30, currentIndex: _selectedIndex, unselectedItemColor: base50, showSelectedLabels: false, diff --git a/mobile_app/lib/presentation/registration/registration_page.dart b/mobile_app/lib/presentation/registration/registration_page.dart index fda9dde..006fccb 100644 --- a/mobile_app/lib/presentation/registration/registration_page.dart +++ b/mobile_app/lib/presentation/registration/registration_page.dart @@ -16,7 +16,7 @@ class RegistrationPage extends StatelessWidget { child: BlocConsumer( listener: (context, state) { if (state is RegistrationSuccessful) { - Navigator.of(context).pushReplacementNamed(Routes.homePage); + Navigator.of(context).pushReplacementNamed(Routes.main); } }, builder: (_, state) { return Column( diff --git a/mobile_app/lib/presentation/settings/cubit/settings_cubit.dart b/mobile_app/lib/presentation/settings/cubit/settings_cubit.dart new file mode 100644 index 0000000..c1f78f3 --- /dev/null +++ b/mobile_app/lib/presentation/settings/cubit/settings_cubit.dart @@ -0,0 +1,8 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'settings_state.dart'; + +class SettingsCubit extends Cubit { + SettingsCubit() : super(SettingsInitial()); +} diff --git a/mobile_app/lib/presentation/settings/cubit/settings_state.dart b/mobile_app/lib/presentation/settings/cubit/settings_state.dart new file mode 100644 index 0000000..bd112be --- /dev/null +++ b/mobile_app/lib/presentation/settings/cubit/settings_state.dart @@ -0,0 +1,6 @@ +part of 'settings_cubit.dart'; + +@immutable +sealed class SettingsState {} + +final class SettingsInitial extends SettingsState {} diff --git a/mobile_app/lib/presentation/settings/screen.dart b/mobile_app/lib/presentation/settings/screen.dart new file mode 100644 index 0000000..c671722 --- /dev/null +++ b/mobile_app/lib/presentation/settings/screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/settings/widgets/radio_button_row.dart'; +import 'package:mobile_app/presentation/settings/widgets/test_text.dart'; +import 'package:mobile_app/theme/theme_cubit.dart'; + +class SettingsScreen extends StatelessWidget { + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Choose theme', + style: Theme.of(context).isLight() + ? bodyMRegular.copyWith(color: base90) + : bodyMRegular, + ), + const SizedBox( + height: 8, + ), + const RadioButtonRow( + title: 'System', + value: ThemeMode.system, + ), + const SizedBox( + height: 8, + ), + const RadioButtonRow( + title: 'Light', + value: ThemeMode.light, + ), + const SizedBox( + height: 8, + ), + const RadioButtonRow( + title: 'Dark', + value: ThemeMode.dark, + ), + TestTextWidget( + text: state.brightness.index.toString(), + ), + ], + ); + }, + ); + } +} diff --git a/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart b/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart new file mode 100644 index 0000000..5f0891e --- /dev/null +++ b/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/theme/theme_cubit.dart'; + +class RadioButtonRow extends StatelessWidget { + final String title; + final ThemeMode value; + const RadioButtonRow({ + super.key, + required this.title, + required this.value, + }); + + @override + Widget build(BuildContext context) { + final cubit = context.read(); + return Row( + children: [ + SizedBox( + height: 24, + width: 24, + child: Radio.adaptive( + value: value, + groupValue: cubit.themeMode, + onChanged: (value) => cubit.setThemeMode(value!), + ), + ), + Text( + title, + style: bodySRegular(base80), + ), + ], + ); + } +} diff --git a/mobile_app/lib/presentation/settings/widgets/test_text.dart b/mobile_app/lib/presentation/settings/widgets/test_text.dart new file mode 100644 index 0000000..1cf9101 --- /dev/null +++ b/mobile_app/lib/presentation/settings/widgets/test_text.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + + +class TestTextWidget extends StatelessWidget { + final String text; + const TestTextWidget({ + super.key, + required this.text, + }); + + @override + Widget build(BuildContext context) { + return Text(text); + } +} diff --git a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart index 3e433bd..4ac6e5f 100644 --- a/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart +++ b/mobile_app/lib/presentation/splash/cubit/splash_cubit.dart @@ -13,6 +13,6 @@ class SplashCubit extends Cubit { Future _init() async{ init(); await Future.delayed(const Duration(seconds: 2)); - emit(SplashInitialized(nextRoute: Routes.homePage)); + emit(SplashInitialized(nextRoute: Routes.main)); } } diff --git a/mobile_app/lib/presentation/splash/splash_screen.dart b/mobile_app/lib/presentation/splash/splash_screen.dart index d240c0a..9d83b00 100644 --- a/mobile_app/lib/presentation/splash/splash_screen.dart +++ b/mobile_app/lib/presentation/splash/splash_screen.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/const.dart'; @@ -10,12 +9,10 @@ class SplashScreen extends StatelessWidget { @override Widget build(BuildContext context) { - // SchedulerBinding.instance.addPostFrameCallback - // WidgetsBinding.instance.addPostFrameCallback return Scaffold( body: BlocListener( listener: (context, state) { - if (state is SplashInitialized){ + if (state is SplashInitialized) { Navigator.of(context).pushReplacementNamed(state.nextRoute); } }, diff --git a/mobile_app/lib/theme/theme_cubit.dart b/mobile_app/lib/theme/theme_cubit.dart new file mode 100644 index 0000000..d8fb0ad --- /dev/null +++ b/mobile_app/lib/theme/theme_cubit.dart @@ -0,0 +1,26 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +part 'theme_state.dart'; + +class ThemeCubit extends Cubit { + ThemeCubit() : super(const ThemeState(brightness: Brightness.dark)); + + void setThemeMode(ThemeMode mode) { + switch (mode) { + case ThemeMode.system: + case ThemeMode.light: + emit(const ThemeState(brightness: Brightness.light)); + case ThemeMode.dark: + emit(const ThemeState(brightness: Brightness.dark)); + } + } + + ThemeMode get themeMode{ + return switch (state.brightness){ + Brightness.dark => ThemeMode.dark, + Brightness.light => ThemeMode.light, + }; + } + +} diff --git a/mobile_app/lib/theme/theme_state.dart b/mobile_app/lib/theme/theme_state.dart new file mode 100644 index 0000000..341a8ea --- /dev/null +++ b/mobile_app/lib/theme/theme_state.dart @@ -0,0 +1,8 @@ +part of 'theme_cubit.dart'; + +@immutable +class ThemeState { + final Brightness brightness; + + const ThemeState({required this.brightness}); +} From ebef5cc66ea31cc57c0da2b4955ead7faefaa85b Mon Sep 17 00:00:00 2001 From: Max Size Date: Fri, 10 May 2024 13:06:18 +0300 Subject: [PATCH 13/18] Theme options, bluetooth screen, added db --- mobile_app/lib/config/navigation.dart | 3 + mobile_app/lib/config/routes.dart | 1 + mobile_app/lib/config/styles.dart | 18 ++++- mobile_app/lib/config/theme.dart | 2 +- .../lib/data/data_source/local/database.dart | 37 +++++++++ .../local/tables/measurements_table.dart | 7 ++ .../lib/data/models/db/measurement.dart | 79 +++++++++++++++++++ .../repository/measurements_repository.dart | 38 +++++++++ mobile_app/lib/di/di_container.dart | 6 ++ .../domain/entities/glucose_measurement.dart | 1 + .../domain/repository/auth_repository.dart | 2 +- .../local/glucose_data_repository.dart | 8 ++ .../presentation/add_note/add_note_page.dart | 12 ++- .../lib/presentation/bluetooth/screen.dart | 57 +++++++++++++ .../bluetooth/widgets/devices_column.dart | 38 +++++++++ .../bluetooth/widgets/devices_row.dart | 49 ++++++++++++ mobile_app/lib/presentation/chart/screen.dart | 17 +++- .../chart/widgets/line_chart.dart | 5 +- .../lib/presentation/components/card.dart | 19 +++-- .../components/custom_text_field.dart | 3 +- .../lib/presentation/main/main_page.dart | 33 ++++---- .../lib/presentation/settings/screen.dart | 34 ++++++++ .../settings/widgets/radio_button_row.dart | 5 +- mobile_app/pubspec.yaml | 7 +- 24 files changed, 439 insertions(+), 42 deletions(-) create mode 100644 mobile_app/lib/data/data_source/local/database.dart create mode 100644 mobile_app/lib/data/data_source/local/tables/measurements_table.dart create mode 100644 mobile_app/lib/data/models/db/measurement.dart create mode 100644 mobile_app/lib/data/repository/measurements_repository.dart create mode 100644 mobile_app/lib/domain/entities/glucose_measurement.dart create mode 100644 mobile_app/lib/domain/repository/local/glucose_data_repository.dart create mode 100644 mobile_app/lib/presentation/bluetooth/screen.dart create mode 100644 mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart create mode 100644 mobile_app/lib/presentation/bluetooth/widgets/devices_row.dart diff --git a/mobile_app/lib/config/navigation.dart b/mobile_app/lib/config/navigation.dart index 946275c..efbdb8a 100644 --- a/mobile_app/lib/config/navigation.dart +++ b/mobile_app/lib/config/navigation.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/routes.dart'; import 'package:mobile_app/data/repository/auth_repository.dart'; +import 'package:mobile_app/presentation/bluetooth/screen.dart'; import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; import 'package:mobile_app/presentation/login/login_page.dart'; import 'package:mobile_app/presentation/main/cubit/home_page_cubit.dart'; @@ -37,6 +38,8 @@ class MainNavigation { ), child: const RegistrationPage(), ); + case Routes.bluetooth: + page = const BluetoothScreen(); default: page = const Scaffold( body: Center( diff --git a/mobile_app/lib/config/routes.dart b/mobile_app/lib/config/routes.dart index 43e3427..b3cc653 100644 --- a/mobile_app/lib/config/routes.dart +++ b/mobile_app/lib/config/routes.dart @@ -3,4 +3,5 @@ abstract class Routes { static const auth = '/auth'; static const main = '/main'; static const registration = '/registration'; + static const bluetooth = '$main/bluetooth'; } diff --git a/mobile_app/lib/config/styles.dart b/mobile_app/lib/config/styles.dart index 66ce35b..1ca012b 100644 --- a/mobile_app/lib/config/styles.dart +++ b/mobile_app/lib/config/styles.dart @@ -5,18 +5,28 @@ const h4 = TextStyle( fontWeight: FontWeight.w500, ); -TextStyle captionRegular(Color color) => - TextStyle(fontSize: 12, fontWeight: FontWeight.w400, color: color); +const captionRegular = TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, +); const bodyMRegular = TextStyle( fontSize: 16, fontWeight: FontWeight.w400, ); +const bodySMedium = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, +); + const bodyMMedium = TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ); -TextStyle bodySRegular(Color color) => - TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: color); +const bodySRegular = TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, +); + diff --git a/mobile_app/lib/config/theme.dart b/mobile_app/lib/config/theme.dart index a3d5c2e..cbb5c3e 100644 --- a/mobile_app/lib/config/theme.dart +++ b/mobile_app/lib/config/theme.dart @@ -17,7 +17,7 @@ final textLightTheme = TextTheme( headlineMedium: h4, bodyMedium: bodyMRegular.copyWith(color: base90), bodySmall: bodyMRegular.copyWith(color: base90), - displaySmall: captionRegular(base90), + displaySmall: captionRegular.copyWith(color: base90), bodyLarge: bodyMMedium.copyWith(color: base90), ); diff --git a/mobile_app/lib/data/data_source/local/database.dart b/mobile_app/lib/data/data_source/local/database.dart new file mode 100644 index 0000000..4bef94a --- /dev/null +++ b/mobile_app/lib/data/data_source/local/database.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:mobile_app/data/data_source/local/tables/measurements_table.dart'; +import 'package:mobile_app/data/models/db/measurement.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; + +part 'database.g.dart'; + +@DriftDatabase(tables: [GlucoseMeasuremnts]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; +} + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(join(dbFolder.path, 'db.sqlite')); + + if (Platform.isAndroid) { + await applyWorkaroundToOpenSqlite3OnOldAndroidVersions(); + } + + final cachebase = (await getTemporaryDirectory()).path; + + sqlite3.tempDirectory = cachebase; + + return NativeDatabase.createInBackground(file); + }); +} diff --git a/mobile_app/lib/data/data_source/local/tables/measurements_table.dart b/mobile_app/lib/data/data_source/local/tables/measurements_table.dart new file mode 100644 index 0000000..d363c57 --- /dev/null +++ b/mobile_app/lib/data/data_source/local/tables/measurements_table.dart @@ -0,0 +1,7 @@ +import 'package:drift/drift.dart'; + +class GlucoseMeasuremnts extends Table { + IntColumn get id => integer().autoIncrement()(); + RealColumn get value => real()(); + DateTimeColumn get createdAt => dateTime()(); +} \ No newline at end of file diff --git a/mobile_app/lib/data/models/db/measurement.dart b/mobile_app/lib/data/models/db/measurement.dart new file mode 100644 index 0000000..f180fc9 --- /dev/null +++ b/mobile_app/lib/data/models/db/measurement.dart @@ -0,0 +1,79 @@ +import 'package:drift/drift.dart'; +import 'package:mobile_app/data/data_source/local/database.dart'; + +class GlucoseMeasuremnt extends DataClass + implements Insertable { + final int id; + final double value; + final DateTime? createdAt; + const GlucoseMeasuremnt( + {required this.id, required this.value, this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['value'] = Variable(value); + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + return map; + } + + GlucoseMeasuremntsCompanion toCompanion(bool nullToAbsent) { + return GlucoseMeasuremntsCompanion( + id: Value(id), + value: Value(value), + createdAt: createdAt == null && nullToAbsent + ? const Value.absent() + : Value(createdAt), + ); + } + + factory GlucoseMeasuremnt.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GlucoseMeasuremnt( + id: serializer.fromJson(json['id']), + value: serializer.fromJson(json['value']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'value': serializer.toJson(value), + 'createdAt': serializer.toJson(createdAt), + }; + } + + GlucoseMeasuremnt copyWith( + {int? id, + double? value, + Value createdAt = const Value.absent()}) => + GlucoseMeasuremnt( + id: id ?? this.id, + value: value ?? this.value, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + ); + @override + String toString() { + return (StringBuffer('GlucoseMeasuremnt(') + ..write('id: $id, ') + ..write('value: $value, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, value, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GlucoseMeasuremnt && + other.id == this.id && + other.value == this.value && + other.createdAt == this.createdAt); +} \ No newline at end of file diff --git a/mobile_app/lib/data/repository/measurements_repository.dart b/mobile_app/lib/data/repository/measurements_repository.dart new file mode 100644 index 0000000..74147ed --- /dev/null +++ b/mobile_app/lib/data/repository/measurements_repository.dart @@ -0,0 +1,38 @@ +import 'package:dartz/dartz.dart'; +import 'package:drift/drift.dart'; +import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/data/models/db/measurement.dart'; +import 'package:mobile_app/domain/repository/local/glucose_data_repository.dart'; + +class GlucoseDataRepository implements IGlucoseDataRepository { + final AppDatabase _database; + + GlucoseDataRepository({required AppDatabase database}) : _database = database; + + @override + Future>> getGlucoseDataForDay( + {DateTime? date}) async { + date ??= DateTime.now(); + try { + final glucoseData = await _database.managers.glucoseMeasuremnts + .filter((f) => f.createdAt + .isBetween(date!, DateTime(date.year, date.month, date.day + 1))) + .get(); + return right(glucoseData); + } catch (e) { + return left(UnitMonoid().zero()); + } + } + + @override + Future addClucoseMeasurement( + {required GlucoseMeasuremnt measurement}) async { + try { + await _database.managers.glucoseMeasuremnts.create((o) => + o(createdAt: Value(measurement.createdAt), value: measurement.value)); + return true; + } catch (e) { + return false; + } + } +} diff --git a/mobile_app/lib/di/di_container.dart b/mobile_app/lib/di/di_container.dart index fd0f74c..a3e9f16 100644 --- a/mobile_app/lib/di/di_container.dart +++ b/mobile_app/lib/di/di_container.dart @@ -1,15 +1,21 @@ import 'package:dio/dio.dart'; import 'package:get_it/get_it.dart'; import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/data/repository/measurements_repository.dart'; final sl = GetIt.instance; final regLs = sl.registerLazySingleton; final regS = sl.registerSingleton; void init() { + regLs(() => AppDatabase()); regLs( () => Dio() ..options.baseUrl = baseUrl ..options.headers['Content-Type'] = 'application/json', ); + regLs( + () => GlucoseDataRepository(database: sl()), + ); } diff --git a/mobile_app/lib/domain/entities/glucose_measurement.dart b/mobile_app/lib/domain/entities/glucose_measurement.dart new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/mobile_app/lib/domain/entities/glucose_measurement.dart @@ -0,0 +1 @@ + diff --git a/mobile_app/lib/domain/repository/auth_repository.dart b/mobile_app/lib/domain/repository/auth_repository.dart index a8f7716..044e9d4 100644 --- a/mobile_app/lib/domain/repository/auth_repository.dart +++ b/mobile_app/lib/domain/repository/auth_repository.dart @@ -4,7 +4,7 @@ import 'package:mobile_app/data/models/request/registration_data.dart'; import 'package:mobile_app/data/models/response/login_data.dart'; import 'package:mobile_app/data/models/response/registration_data.dart'; -abstract class AuthRepository{ +abstract interface class AuthRepository{ Future> register(RegistrationData data); Future> login(LoginData data); diff --git a/mobile_app/lib/domain/repository/local/glucose_data_repository.dart b/mobile_app/lib/domain/repository/local/glucose_data_repository.dart new file mode 100644 index 0000000..c98cef2 --- /dev/null +++ b/mobile_app/lib/domain/repository/local/glucose_data_repository.dart @@ -0,0 +1,8 @@ +import 'package:dartz/dartz.dart'; +import 'package:mobile_app/data/models/db/measurement.dart'; + +abstract interface class IGlucoseDataRepository { + Future>> getGlucoseDataForDay({DateTime? date}); + + Future addClucoseMeasurement({required GlucoseMeasuremnt measurement}); +} \ No newline at end of file diff --git a/mobile_app/lib/presentation/add_note/add_note_page.dart b/mobile_app/lib/presentation/add_note/add_note_page.dart index fb7d791..7fd961d 100644 --- a/mobile_app/lib/presentation/add_note/add_note_page.dart +++ b/mobile_app/lib/presentation/add_note/add_note_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/domain/glucose_event_types.dart'; import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; import 'package:mobile_app/presentation/components/custom_button.dart'; @@ -22,7 +23,9 @@ class AddNotePage extends StatelessWidget { children: [ Text( 'Type of event', - style: bodySRegular(base40), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base40) + : bodySRegular, ), const SizedBox( height: 4, @@ -40,9 +43,10 @@ class AddNotePage extends StatelessWidget { dropdownMenuEntries: GlucoseEventType.values .map( (eventType) => DropdownMenuEntry( - value: eventType, - label: eventType.title, - leadingIcon: SvgPicture.asset(eventType.iconPath),), + value: eventType, + label: eventType.title, + leadingIcon: SvgPicture.asset(eventType.iconPath), + ), ) .toList(), onSelected: (value) => context diff --git a/mobile_app/lib/presentation/bluetooth/screen.dart b/mobile_app/lib/presentation/bluetooth/screen.dart new file mode 100644 index 0000000..8737ea5 --- /dev/null +++ b/mobile_app/lib/presentation/bluetooth/screen.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/bluetooth/widgets/devices_column.dart'; + +class BluetoothScreen extends StatefulWidget { + const BluetoothScreen({super.key}); + + @override + State createState() => _BluetoothScreenState(); +} + +class _BluetoothScreenState extends State { + bool checked = false; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Bluetooth')), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Bluetooth', + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base80) + : bodySRegular, + ), + CupertinoSwitch( + value: checked, + onChanged: (c) {setState(() { + checked = c; + });}, + trackColor: base30, + activeColor: green50, + ) + ], + ), + Expanded( + child: ListView.builder( + physics: ClampingScrollPhysics(), + itemBuilder: (context, index) => DevicesColumn(title: 'Paired'), + itemCount: 4, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart new file mode 100644 index 0000000..1e6c91b --- /dev/null +++ b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/bluetooth/widgets/devices_row.dart'; + +class DevicesColumn extends StatelessWidget { + final String title; + const DevicesColumn({ + super.key, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).isLight() + ? bodyMMedium.copyWith(color: base90) + : bodyMMedium, + ), + for (int i = 0; i < 4; i++) + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: BluetoothDeviceRow( + deviceName: 'Super device', connectionState: 'Connecting'), + ), + ], + ), + ); + } +} diff --git a/mobile_app/lib/presentation/bluetooth/widgets/devices_row.dart b/mobile_app/lib/presentation/bluetooth/widgets/devices_row.dart new file mode 100644 index 0000000..22f3b4a --- /dev/null +++ b/mobile_app/lib/presentation/bluetooth/widgets/devices_row.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; + +class BluetoothDeviceRow extends StatelessWidget { + final String deviceName; + final String connectionState; + + const BluetoothDeviceRow({ + super.key, + required this.deviceName, + required this.connectionState, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Icon( + Icons.bluetooth, + color: green50, + size: 24, + ), + const SizedBox( + width: 8, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + deviceName, + style: Theme.of(context).isLight() + ? bodySMedium.copyWith(color: base90) + : bodySMedium, + ), + Text( + connectionState, + style: Theme.of(context).isLight() + ? captionRegular.copyWith(color: base50) + : captionRegular, + ), + ], + ) + ], + ); + } +} diff --git a/mobile_app/lib/presentation/chart/screen.dart b/mobile_app/lib/presentation/chart/screen.dart index 15a79a9..c11172c 100644 --- a/mobile_app/lib/presentation/chart/screen.dart +++ b/mobile_app/lib/presentation/chart/screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/chart/cubit/chart_cubit.dart'; import 'package:mobile_app/presentation/chart/widgets/date_card.dart'; import 'package:mobile_app/presentation/chart/widgets/line_chart.dart'; @@ -47,21 +48,29 @@ class ChartScreen extends StatelessWidget { children: [ Text( 'mmol/L', - style: captionRegular(base40), + style: Theme.of(context).isLight() + ? captionRegular.copyWith(color: base40) + : captionRegular, ), Text( '6', - style: bodySRegular(base40), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base40) + : bodySRegular, ), const Spacer(), Text( '4', - style: bodySRegular(base40), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base40) + : bodySRegular, ), const Spacer(), Text( '2', - style: bodySRegular(base40), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base40) + : bodySRegular, ), const Spacer(), ], diff --git a/mobile_app/lib/presentation/chart/widgets/line_chart.dart b/mobile_app/lib/presentation/chart/widgets/line_chart.dart index c0c1001..1f6effc 100644 --- a/mobile_app/lib/presentation/chart/widgets/line_chart.dart +++ b/mobile_app/lib/presentation/chart/widgets/line_chart.dart @@ -2,6 +2,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; class LineChartSample2 extends StatefulWidget { const LineChartSample2({super.key}); @@ -37,7 +38,9 @@ class _LineChartSample2State extends State { final partOfDay = value >= 12 ? 'pm' : 'am'; final text = Text( '$time$partOfDay', - style: bodySRegular(base40), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base40) + : bodySRegular, ); return SideTitleWidget( diff --git a/mobile_app/lib/presentation/components/card.dart b/mobile_app/lib/presentation/components/card.dart index 06c6322..9b37115 100644 --- a/mobile_app/lib/presentation/components/card.dart +++ b/mobile_app/lib/presentation/components/card.dart @@ -22,22 +22,25 @@ class InfoCard extends StatelessWidget { @override Widget build(BuildContext context) { return Card( - surfaceTintColor: Colors.white, - color: Colors.white, + surfaceTintColor: Theme.of(context).isLight() ? Colors.white : null, + color: Theme.of(context).isLight() ? Colors.white : null, elevation: 20, - shadowColor: shadowColor, + shadowColor: Theme.of(context).isLight() ? shadowColor : null, child: Padding( padding: const EdgeInsets.all(12), child: Column( children: [ Row( children: [ - SvgPicture.asset(iconPath), + SvgPicture.asset( + iconPath, + color: Theme.of(context).isLight() ? null : green30, + ), Text( title, style: Theme.of(context).isLight() - ? bodyMMedium.copyWith(color: base90) - : bodyMMedium, + ? bodyMMedium.copyWith(color: base90) + : bodyMMedium, ), ], ), @@ -50,7 +53,9 @@ class InfoCard extends StatelessWidget { Expanded( child: Text( ' mmol/L', - style: bodySRegular(base30), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base30) + : bodySRegular, ), ), Visibility( diff --git a/mobile_app/lib/presentation/components/custom_text_field.dart b/mobile_app/lib/presentation/components/custom_text_field.dart index f4902fd..1261470 100644 --- a/mobile_app/lib/presentation/components/custom_text_field.dart +++ b/mobile_app/lib/presentation/components/custom_text_field.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; class CustomTextField extends StatelessWidget { final String title; @@ -24,7 +25,7 @@ class CustomTextField extends StatelessWidget { children: [ Text( title, - style: bodySRegular(base40), + style: Theme.of(context).isLight() ? bodySRegular.copyWith(color: base40) : bodySRegular, ), const SizedBox( height: 4, diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index 6475e22..814101d 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -39,6 +39,21 @@ class _MainPageWidgetState extends State { @override Widget build(BuildContext context) { + + final pages = [ + const HomePageWidget(), + const HistoryPage(), + BlocProvider( + create: (context) => AddNoteCubit(), + child: const AddNotePage(), + ), + BlocProvider( + create: (context) => ChartCubit(), + child: const ChartScreen(), + ), + const SettingsScreen(), + ]; + return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -48,25 +63,9 @@ class _MainPageWidgetState extends State { ), body: Padding( padding: const EdgeInsets.all(16), - child: IndexedStack( - index: _selectedIndex, - children: [ - const HomePageWidget(), - const HistoryPage(), - BlocProvider( - create: (context) => AddNoteCubit(), - child: const AddNotePage(), - ), - BlocProvider( - create: (context) => ChartCubit(), - child: const ChartScreen(), - ), - const SettingsScreen(), - ], - ), + child: pages[_selectedIndex], ), bottomNavigationBar: BottomNavigationBar( - //backgroundColor: Colors.white, items: const [ BottomNavigationBarItem( icon: Icon(Icons.home_outlined), diff --git a/mobile_app/lib/presentation/settings/screen.dart b/mobile_app/lib/presentation/settings/screen.dart index c671722..965ac07 100644 --- a/mobile_app/lib/presentation/settings/screen.dart +++ b/mobile_app/lib/presentation/settings/screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/routes.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/settings/widgets/radio_button_row.dart'; @@ -47,6 +48,39 @@ class SettingsScreen extends StatelessWidget { TestTextWidget( text: state.brightness.index.toString(), ), + Text( + 'Connect bluetooth device', + style: Theme.of(context).isLight() + ? bodyMRegular.copyWith(color: base90) + : bodyMRegular, + ), + OutlinedButton( + onPressed: () => + Navigator.of(context).pushNamed(Routes.bluetooth), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.add, + color: green50, + size: 16, + ), + Text( + 'Add device', + style: Theme.of(context).isLight() + ? bodySMedium.copyWith(color: green50) + : bodySMedium, + ), + ], + ), + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), ], ); }, diff --git a/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart b/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart index 5f0891e..c4c5e51 100644 --- a/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart +++ b/mobile_app/lib/presentation/settings/widgets/radio_button_row.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/theme/theme_cubit.dart'; class RadioButtonRow extends StatelessWidget { @@ -29,7 +30,9 @@ class RadioButtonRow extends StatelessWidget { ), Text( title, - style: bodySRegular(base80), + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base80) + : bodySRegular, ), ], ); diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index f0f3d18..bf3a899 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -22,6 +22,10 @@ dependencies: dio: ^5.4.1 dartz: ^0.10.1 get_it: ^7.6.7 + drift: ^2.18.0 + sqlite3_flutter_libs: ^0.5.0 + path_provider: ^2.0.0 + path: ^1.9.0 @@ -29,7 +33,8 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - retrofit_generator: '>=7.0.0 <8.0.0' + retrofit_generator: '>=7.0.0 <8.0.0' + drift_dev: ^2.18.0 build_runner: '>=2.3.0 <4.0.0' json_serializable: ^6.6.2 From 469a40c7d0e8cdfaaf65e68692810b7dade217c0 Mon Sep 17 00:00:00 2001 From: Max Size Date: Fri, 10 May 2024 15:58:26 +0300 Subject: [PATCH 14/18] Added bluetooth functionality --- mobile_app/android/app/build.gradle | 134 +++++++++--------- .../android/app/src/main/AndroidManifest.xml | 82 ++++++----- mobile_app/lib/config/navigation.dart | 6 +- .../bluetooth/cubit/bluetooth_cubit.dart | 69 +++++++++ .../bluetooth/cubit/bluetooth_state.dart | 29 ++++ .../lib/presentation/bluetooth/screen.dart | 87 ++++++++---- .../bluetooth/widgets/devices_column.dart | 19 ++- mobile_app/lib/theme/theme_cubit.dart | 2 +- mobile_app/pubspec.yaml | 2 + 9 files changed, 293 insertions(+), 137 deletions(-) create mode 100644 mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart create mode 100644 mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart diff --git a/mobile_app/android/app/build.gradle b/mobile_app/android/app/build.gradle index 178fffd..9072968 100644 --- a/mobile_app/android/app/build.gradle +++ b/mobile_app/android/app/build.gradle @@ -1,67 +1,67 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -android { - namespace "com.example.mobile_app" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.mobile_app" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - 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.debug - } - } -} - -flutter { - source '../..' -} - -dependencies {} +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.mobile_app" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.mobile_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + 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.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/mobile_app/android/app/src/main/AndroidManifest.xml b/mobile_app/android/app/src/main/AndroidManifest.xml index aaf04d5..76f9179 100644 --- a/mobile_app/android/app/src/main/AndroidManifest.xml +++ b/mobile_app/android/app/src/main/AndroidManifest.xml @@ -1,33 +1,49 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile_app/lib/config/navigation.dart b/mobile_app/lib/config/navigation.dart index efbdb8a..09e1bd2 100644 --- a/mobile_app/lib/config/navigation.dart +++ b/mobile_app/lib/config/navigation.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/routes.dart'; import 'package:mobile_app/data/repository/auth_repository.dart'; +import 'package:mobile_app/presentation/bluetooth/cubit/bluetooth_cubit.dart'; import 'package:mobile_app/presentation/bluetooth/screen.dart'; import 'package:mobile_app/presentation/login/cubit/login_cubit.dart'; import 'package:mobile_app/presentation/login/login_page.dart'; @@ -39,7 +40,10 @@ class MainNavigation { child: const RegistrationPage(), ); case Routes.bluetooth: - page = const BluetoothScreen(); + page = BlocProvider( + create: (context) => BluetoothCubit(), + child: const BluetoothScreen(), + ); default: page = const Scaffold( body: Center( diff --git a/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart new file mode 100644 index 0000000..986a409 --- /dev/null +++ b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart @@ -0,0 +1,69 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:meta/meta.dart'; + +part 'bluetooth_state.dart'; + +class BluetoothCubit extends Cubit { + late final StreamSubscription + bluetoothStatusSubscription; + late final StreamSubscription> scanResultSubscription; + + BluetoothCubit() : super(BluetoothLoading()) { + checkBluetoothStatus(); + } + Future checkBluetoothStatus() async { + if (await FlutterBluePlus.isSupported == false) { + return; + } + bluetoothStatusSubscription = FlutterBluePlus.adapterState + .listen((BluetoothAdapterState state) async { + switch (state) { + case BluetoothAdapterState.on: + emit(BluetoothLoading()); + final pairedDevices = await FlutterBluePlus.bondedDevices; + emit( + BluetoothOn( + pairedDevices: pairedDevices, + availableDevices: const [], + ), + ); + final subscription = FlutterBluePlus.onScanResults.listen( + (results) { + if (results.isNotEmpty) { + emit( + BluetoothOn( + pairedDevices: pairedDevices, + availableDevices: results, + ), + ); + } + }, + onError: (e) => print(e), + ); + FlutterBluePlus.cancelWhenScanComplete(subscription); + await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); + case BluetoothAdapterState.off: + emit(BluetoothOff()); + default: + break; + } + }); + } + + void toggleBluetooth({required bool turnedOn}) async { + if (turnedOn) { + await FlutterBluePlus.turnOn(); + } else { + await FlutterBluePlus.turnOff(); + } + } + + @override + Future close() { + bluetoothStatusSubscription.cancel(); + return super.close(); + } +} diff --git a/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart new file mode 100644 index 0000000..4b809fc --- /dev/null +++ b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart @@ -0,0 +1,29 @@ +part of 'bluetooth_cubit.dart'; + +@immutable +sealed class BluetoothState {} + +final class BluetoothOff extends BluetoothState {} + +final class BluetoothOn extends BluetoothState { + final List pairedDevices; + final List availableDevices; + + BluetoothOn({ + required this.pairedDevices, + required this.availableDevices, + }); + + BluetoothOn copyWith({ + List? pairedDevices, + List? availableDevices, + }) => + BluetoothOn( + pairedDevices: pairedDevices ?? this.pairedDevices, + availableDevices: availableDevices ?? this.availableDevices, + ); +} + +final class BluetoothError extends BluetoothState {} + +final class BluetoothLoading extends BluetoothState {} diff --git a/mobile_app/lib/presentation/bluetooth/screen.dart b/mobile_app/lib/presentation/bluetooth/screen.dart index 8737ea5..ca8de55 100644 --- a/mobile_app/lib/presentation/bluetooth/screen.dart +++ b/mobile_app/lib/presentation/bluetooth/screen.dart @@ -1,9 +1,11 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/bluetooth/cubit/bluetooth_cubit.dart'; import 'package:mobile_app/presentation/bluetooth/widgets/devices_column.dart'; class BluetoothScreen extends StatefulWidget { @@ -17,40 +19,67 @@ class _BluetoothScreenState extends State { bool checked = false; @override Widget build(BuildContext context) { + final cubit = context.read(); return Scaffold( appBar: AppBar(title: const Text('Bluetooth')), body: Padding( padding: const EdgeInsets.all(16), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Bluetooth', - style: Theme.of(context).isLight() - ? bodySRegular.copyWith(color: base80) - : bodySRegular, - ), - CupertinoSwitch( - value: checked, - onChanged: (c) {setState(() { - checked = c; - });}, - trackColor: base30, - activeColor: green50, - ) - ], - ), - Expanded( - child: ListView.builder( - physics: ClampingScrollPhysics(), - itemBuilder: (context, index) => DevicesColumn(title: 'Paired'), - itemCount: 4, + child: BlocBuilder( + builder: (context, state) { + return switch (state) { + BluetoothOff() => const Center( + child: Text('Bluetooth is off'), ), - ), - ], - ), + BluetoothOn( + pairedDevices: final pairedDevices, + availableDevices: final availableDevices + ) => + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Bluetooth', + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base80) + : bodySRegular, + ), + CupertinoSwitch( + value: state is BluetoothOn, + onChanged: (status) { + cubit.toggleBluetooth(turnedOn: status); + }, + trackColor: base30, + activeColor: green50, + ), + ], + ), + Expanded( + child: ListView( + children: [ + DevicesColumn( + title: 'Paired Devices', + devices: pairedDevices, + ), + DevicesColumn( + title: 'Available Devices', + devices: + availableDevices.map((e) => e.device).toList(), + ), + ], + ), + ), + ], + ), + BluetoothError() => const Center( + child: Text('Что-то пошло не так'), + ), + BluetoothLoading() => const Center( + child: CircularProgressIndicator(), + ), + }; + }), ), ); } diff --git a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart index 1e6c91b..49e0fef 100644 --- a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart +++ b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/bluetooth/widgets/devices_row.dart'; class DevicesColumn extends StatelessWidget { + final List devices; final String title; const DevicesColumn({ super.key, required this.title, + required this.devices, }); @override @@ -16,7 +19,6 @@ class DevicesColumn extends StatelessWidget { return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( - mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( @@ -25,11 +27,16 @@ class DevicesColumn extends StatelessWidget { ? bodyMMedium.copyWith(color: base90) : bodyMMedium, ), - for (int i = 0; i < 4; i++) - const Padding( - padding: EdgeInsets.symmetric(vertical: 8), - child: BluetoothDeviceRow( - deviceName: 'Super device', connectionState: 'Connecting'), + for (final device in devices) + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: BluetoothDeviceRow( + deviceName: device.platformName.isNotEmpty + ? device.platformName + : device.remoteId.str, + connectionState: + device.isConnected ? 'Connected' : 'Disconnected', + ), ), ], ), diff --git a/mobile_app/lib/theme/theme_cubit.dart b/mobile_app/lib/theme/theme_cubit.dart index d8fb0ad..f46808f 100644 --- a/mobile_app/lib/theme/theme_cubit.dart +++ b/mobile_app/lib/theme/theme_cubit.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; part 'theme_state.dart'; class ThemeCubit extends Cubit { - ThemeCubit() : super(const ThemeState(brightness: Brightness.dark)); + ThemeCubit() : super(const ThemeState(brightness: Brightness.light)); void setThemeMode(ThemeMode mode) { switch (mode) { diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index bf3a899..4e97cac 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -26,6 +26,8 @@ dependencies: sqlite3_flutter_libs: ^0.5.0 path_provider: ^2.0.0 path: ^1.9.0 + flutter_blue_plus: ^1.32.5 + From 057ddf70db0dc61b814174eef674993d90bda804 Mon Sep 17 00:00:00 2001 From: Max Size Date: Sun, 12 May 2024 20:31:11 +0300 Subject: [PATCH 15/18] created bluetooth functionality --- mobile_app/lib/config/const.dart | 1 + .../data_source/mock/mock_ble_device.dart | 24 ++++ .../lib/data/repository/ble_device_repo.dart | 20 +++ mobile_app/lib/di/di_container.dart | 5 + .../domain/repository/ble_device_repo.dart | 18 +++ .../bluetooth/cubit/bluetooth_cubit.dart | 92 +++++++++---- .../bluetooth/cubit/bluetooth_state.dart | 4 +- .../lib/presentation/bluetooth/screen.dart | 43 ++---- .../bluetooth/widgets/devices_column.dart | 27 +++- .../bluetooth/widgets/toggle_row.dart | 36 +++++ .../presentation/home/cubit/home_cubit.dart | 25 +++- .../presentation/home/cubit/home_state.dart | 14 +- .../lib/presentation/home/home_page.dart | 130 ++++++++++-------- .../home/widgets/gauge_indicator.dart | 2 +- .../lib/presentation/main/main_page.dart | 13 +- 15 files changed, 326 insertions(+), 128 deletions(-) create mode 100644 mobile_app/lib/data/data_source/mock/mock_ble_device.dart create mode 100644 mobile_app/lib/data/repository/ble_device_repo.dart create mode 100644 mobile_app/lib/domain/repository/ble_device_repo.dart create mode 100644 mobile_app/lib/presentation/bluetooth/widgets/toggle_row.dart diff --git a/mobile_app/lib/config/const.dart b/mobile_app/lib/config/const.dart index d25511a..67065ba 100644 --- a/mobile_app/lib/config/const.dart +++ b/mobile_app/lib/config/const.dart @@ -5,3 +5,4 @@ const activitySvg = 'assets/run.svg'; const injectionSvg = 'assets/needle.svg'; const foodSvg = 'assets/fork_knife.svg'; const baseUrl = 'http://31.129.107.206:5000'; +const mockBleDevice = 'mockBleDevice'; diff --git a/mobile_app/lib/data/data_source/mock/mock_ble_device.dart b/mobile_app/lib/data/data_source/mock/mock_ble_device.dart new file mode 100644 index 0000000..8fffaf9 --- /dev/null +++ b/mobile_app/lib/data/data_source/mock/mock_ble_device.dart @@ -0,0 +1,24 @@ +import 'dart:math'; + +class MockBluetoothDevice { + static bool isConnected = false; + static final _instance = MockBluetoothDevice._(); + + factory MockBluetoothDevice() => _instance; + + MockBluetoothDevice._(); + + double lastValue = 3; + Stream get dataStream { + isConnected = true; + return Stream.periodic(const Duration(seconds: 3), (i) => _getNewValue()); + } + + double _getNewValue() { + final sign = Random().nextBool() ? 1 : -1; + lastValue += sign * 0.05; + if (lastValue > 6) lastValue = 5.9; + if (lastValue < 0) lastValue = 0.1; + return lastValue; + } +} diff --git a/mobile_app/lib/data/repository/ble_device_repo.dart b/mobile_app/lib/data/repository/ble_device_repo.dart new file mode 100644 index 0000000..eadbd59 --- /dev/null +++ b/mobile_app/lib/data/repository/ble_device_repo.dart @@ -0,0 +1,20 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first + +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:mobile_app/domain/repository/ble_device_repo.dart'; + +class IBLEDeviceRepository extends BLEDeviceRepository { + + final _bleStateStream = FlutterBluePlus.adapterState; + + @override + void setValueStream(Stream valueStream) { + super.setValueStream(valueStream); + _bleStateStream.listen((status) { + if (status == BluetoothAdapterState.off) { + connectionStateController.sink.add(false); + } + }); + } + +} diff --git a/mobile_app/lib/di/di_container.dart b/mobile_app/lib/di/di_container.dart index a3e9f16..02c18f3 100644 --- a/mobile_app/lib/di/di_container.dart +++ b/mobile_app/lib/di/di_container.dart @@ -2,7 +2,9 @@ import 'package:dio/dio.dart'; import 'package:get_it/get_it.dart'; import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/data/repository/ble_device_repo.dart'; import 'package:mobile_app/data/repository/measurements_repository.dart'; +import 'package:mobile_app/domain/repository/ble_device_repo.dart'; final sl = GetIt.instance; final regLs = sl.registerLazySingleton; @@ -18,4 +20,7 @@ void init() { regLs( () => GlucoseDataRepository(database: sl()), ); + regLs( + () => IBLEDeviceRepository(), + ); } diff --git a/mobile_app/lib/domain/repository/ble_device_repo.dart b/mobile_app/lib/domain/repository/ble_device_repo.dart new file mode 100644 index 0000000..d82ee35 --- /dev/null +++ b/mobile_app/lib/domain/repository/ble_device_repo.dart @@ -0,0 +1,18 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; + +abstract class BLEDeviceRepository { + Stream? valueStream; + + Stream get isConnected => connectionStateController.stream; + + @protected + final connectionStateController = StreamController(); + + void setValueStream(Stream valueStream) { + this.valueStream = valueStream; + connectionStateController.sink.add(true); + } + +} diff --git a/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart index 986a409..7a58bb7 100644 --- a/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart +++ b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_cubit.dart @@ -3,17 +3,21 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:meta/meta.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/data/data_source/mock/mock_ble_device.dart'; +import 'package:mobile_app/di/di_container.dart'; +import 'package:mobile_app/domain/repository/ble_device_repo.dart'; part 'bluetooth_state.dart'; class BluetoothCubit extends Cubit { late final StreamSubscription bluetoothStatusSubscription; - late final StreamSubscription> scanResultSubscription; - + final BLEDeviceRepository bleDeviceRepo = sl(); BluetoothCubit() : super(BluetoothLoading()) { checkBluetoothStatus(); } + Future checkBluetoothStatus() async { if (await FlutterBluePlus.isSupported == false) { return; @@ -23,42 +27,74 @@ class BluetoothCubit extends Cubit { switch (state) { case BluetoothAdapterState.on: emit(BluetoothLoading()); - final pairedDevices = await FlutterBluePlus.bondedDevices; - emit( - BluetoothOn( - pairedDevices: pairedDevices, - availableDevices: const [], - ), - ); - final subscription = FlutterBluePlus.onScanResults.listen( - (results) { - if (results.isNotEmpty) { - emit( - BluetoothOn( - pairedDevices: pairedDevices, - availableDevices: results, - ), - ); - } - }, - onError: (e) => print(e), - ); - FlutterBluePlus.cancelWhenScanComplete(subscription); - await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); + await getAllDevices(); case BluetoothAdapterState.off: emit(BluetoothOff()); - default: - break; + case BluetoothAdapterState.unknown: + case BluetoothAdapterState.unavailable: + case BluetoothAdapterState.unauthorized: + case BluetoothAdapterState.turningOn: + case BluetoothAdapterState.turningOff: } }); } - void toggleBluetooth({required bool turnedOn}) async { + Future toggleBluetooth({required bool turnedOn}) async { if (turnedOn) { await FlutterBluePlus.turnOn(); } else { - await FlutterBluePlus.turnOff(); + //await FlutterBluePlus.turnOff(); + } + } + + Future connectToBluetoothDevice(BluetoothDevice device) async { + if (device.remoteId.str == mockBleDevice) { + emit(BluetoothLoading()); + final mockDevice = MockBluetoothDevice(); + bleDeviceRepo.setValueStream(mockDevice.dataStream); + await getAllDevices(); + return; } + emit(BluetoothLoading()); + await device.connect(autoConnect: true); + final services = await device.discoverServices(); + final charactericstics = services.first.characteristics; + final valueStream = charactericstics.first.onValueReceived; + bleDeviceRepo.setValueStream(valueStream); + await getAllDevices(); + } + + Future getAllDevices() async { + final mockDevice = + BluetoothDevice(remoteId: const DeviceIdentifier(mockBleDevice)); + + final pairedDevices = await FlutterBluePlus.bondedDevices; + if (MockBluetoothDevice.isConnected) pairedDevices.add(mockDevice); + emit( + BluetoothOn( + pairedDevices: pairedDevices, + availableDevices: const [], + ), + ); + final subscription = FlutterBluePlus.onScanResults.listen( + (results) { + if (results.isNotEmpty) { + final availableDevices = results.map((e) => e.device).toList(); + if (!MockBluetoothDevice.isConnected) { + availableDevices.insert(0, mockDevice); + } + emit( + BluetoothOn( + pairedDevices: pairedDevices, + availableDevices: availableDevices, + ), + ); + } + }, + onError: (e) => print(e), + ); + FlutterBluePlus.cancelWhenScanComplete(subscription); + await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15)); } @override diff --git a/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart index 4b809fc..0ea59b3 100644 --- a/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart +++ b/mobile_app/lib/presentation/bluetooth/cubit/bluetooth_state.dart @@ -7,7 +7,7 @@ final class BluetoothOff extends BluetoothState {} final class BluetoothOn extends BluetoothState { final List pairedDevices; - final List availableDevices; + final List availableDevices; BluetoothOn({ required this.pairedDevices, @@ -16,7 +16,7 @@ final class BluetoothOn extends BluetoothState { BluetoothOn copyWith({ List? pairedDevices, - List? availableDevices, + List? availableDevices, }) => BluetoothOn( pairedDevices: pairedDevices ?? this.pairedDevices, diff --git a/mobile_app/lib/presentation/bluetooth/screen.dart b/mobile_app/lib/presentation/bluetooth/screen.dart index ca8de55..f016880 100644 --- a/mobile_app/lib/presentation/bluetooth/screen.dart +++ b/mobile_app/lib/presentation/bluetooth/screen.dart @@ -1,12 +1,9 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:mobile_app/config/colors.dart'; -import 'package:mobile_app/config/styles.dart'; -import 'package:mobile_app/config/theme.dart'; + import 'package:mobile_app/presentation/bluetooth/cubit/bluetooth_cubit.dart'; import 'package:mobile_app/presentation/bluetooth/widgets/devices_column.dart'; +import 'package:mobile_app/presentation/bluetooth/widgets/toggle_row.dart'; class BluetoothScreen extends StatefulWidget { const BluetoothScreen({super.key}); @@ -17,9 +14,9 @@ class BluetoothScreen extends StatefulWidget { class _BluetoothScreenState extends State { bool checked = false; + @override Widget build(BuildContext context) { - final cubit = context.read(); return Scaffold( appBar: AppBar(title: const Text('Bluetooth')), body: Padding( @@ -27,8 +24,15 @@ class _BluetoothScreenState extends State { child: BlocBuilder( builder: (context, state) { return switch (state) { - BluetoothOff() => const Center( - child: Text('Bluetooth is off'), + BluetoothOff() => const Column( + children: [ + ToggleRow(), + Expanded( + child: Center( + child: Text('Bluetooth is off'), + ), + ) + ], ), BluetoothOn( pairedDevices: final pairedDevices, @@ -36,25 +40,7 @@ class _BluetoothScreenState extends State { ) => Column( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Bluetooth', - style: Theme.of(context).isLight() - ? bodySRegular.copyWith(color: base80) - : bodySRegular, - ), - CupertinoSwitch( - value: state is BluetoothOn, - onChanged: (status) { - cubit.toggleBluetooth(turnedOn: status); - }, - trackColor: base30, - activeColor: green50, - ), - ], - ), + const ToggleRow(), Expanded( child: ListView( children: [ @@ -63,9 +49,10 @@ class _BluetoothScreenState extends State { devices: pairedDevices, ), DevicesColumn( + canBeConnected: true, title: 'Available Devices', devices: - availableDevices.map((e) => e.device).toList(), + availableDevices, ), ], ), diff --git a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart index 49e0fef..5e68e0e 100644 --- a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart +++ b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart @@ -1,21 +1,29 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/bluetooth/cubit/bluetooth_cubit.dart'; import 'package:mobile_app/presentation/bluetooth/widgets/devices_row.dart'; +import 'package:path/path.dart'; class DevicesColumn extends StatelessWidget { final List devices; final String title; + final bool canBeConnected; const DevicesColumn({ super.key, required this.title, required this.devices, + this.canBeConnected = false, }); @override Widget build(BuildContext context) { + final cubit = context.read(); + return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( @@ -30,12 +38,19 @@ class DevicesColumn extends StatelessWidget { for (final device in devices) Padding( padding: const EdgeInsets.symmetric(vertical: 8), - child: BluetoothDeviceRow( - deviceName: device.platformName.isNotEmpty - ? device.platformName - : device.remoteId.str, - connectionState: - device.isConnected ? 'Connected' : 'Disconnected', + child: InkWell( + onTap: canBeConnected + ? () => cubit.connectToBluetoothDevice(device) + : null, + child: BluetoothDeviceRow( + deviceName: device.platformName.isNotEmpty + ? device.platformName + : device.remoteId.str, + connectionState: !device.isConnected && + device.remoteId.str != mockBleDevice + ? 'Disconnected' + : 'Connected', + ), ), ), ], diff --git a/mobile_app/lib/presentation/bluetooth/widgets/toggle_row.dart b/mobile_app/lib/presentation/bluetooth/widgets/toggle_row.dart new file mode 100644 index 0000000..7f45d72 --- /dev/null +++ b/mobile_app/lib/presentation/bluetooth/widgets/toggle_row.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/styles.dart'; +import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/bluetooth/cubit/bluetooth_cubit.dart'; + +class ToggleRow extends StatelessWidget { + const ToggleRow({super.key}); + + @override + Widget build(BuildContext context) { + final cubit = context.read(); + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Bluetooth', + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base80) + : bodySRegular, + ), + CupertinoSwitch( + value: cubit.state is BluetoothOn, + onChanged: (status) { + cubit.toggleBluetooth(turnedOn: status); + }, + trackColor: base30, + activeColor: green50, + ), + ], + ); + } +} diff --git a/mobile_app/lib/presentation/home/cubit/home_cubit.dart b/mobile_app/lib/presentation/home/cubit/home_cubit.dart index 0774ec6..53007be 100644 --- a/mobile_app/lib/presentation/home/cubit/home_cubit.dart +++ b/mobile_app/lib/presentation/home/cubit/home_cubit.dart @@ -1,8 +1,31 @@ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; +import 'package:mobile_app/di/di_container.dart'; +import 'package:mobile_app/domain/repository/ble_device_repo.dart'; part 'home_state.dart'; class HomeCubit extends Cubit { - HomeCubit() : super(HomeInitial()); + StreamSubscription? valueStream; + final bleDeviceRepo = sl(); + HomeCubit() : super(HomeLoading()) { + _initListener(); + } + + void _initListener() { + bleDeviceRepo.isConnected.listen((isConnected) { + switch (isConnected) { + case true: + valueStream = bleDeviceRepo.valueStream?.listen((value) { + if (!isClosed) emit(DeviceConnected(currentGlucoseValue: value)); + }); + + case false: + valueStream?.cancel(); + emit(DeviceDisconnected()); + } + }); + } } diff --git a/mobile_app/lib/presentation/home/cubit/home_state.dart b/mobile_app/lib/presentation/home/cubit/home_state.dart index 588d960..29819bf 100644 --- a/mobile_app/lib/presentation/home/cubit/home_state.dart +++ b/mobile_app/lib/presentation/home/cubit/home_state.dart @@ -3,4 +3,16 @@ part of 'home_cubit.dart'; @immutable sealed class HomeState {} -final class HomeInitial extends HomeState {} +final class HomeLoading extends HomeState {} + +final class DeviceConnected extends HomeState { + final double currentGlucoseValue; + + DeviceConnected({required this.currentGlucoseValue}); + + DeviceConnected copyWith({double? currentGlucoseValue}) => DeviceConnected( + currentGlucoseValue: currentGlucoseValue ?? this.currentGlucoseValue, + ); +} + +final class DeviceDisconnected extends HomeState {} diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart index 294efd1..d467f96 100644 --- a/mobile_app/lib/presentation/home/home_page.dart +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/components/card.dart'; +import 'package:mobile_app/presentation/home/cubit/home_cubit.dart'; import 'package:mobile_app/presentation/home/widgets/gauge_indicator.dart'; class HomePageWidget extends StatelessWidget { @@ -11,67 +13,79 @@ class HomePageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Hello, Maxim', - style: h4, + return BlocBuilder( + builder: (context, state) { + return switch (state) { + HomeLoading() => const Center( + child: CircularProgressIndicator(), ), - Text( - 'Your glucose data today are', - style: Theme.of(context).isLight() - ? bodyMRegular.copyWith(color: base90) - : bodyMRegular, + DeviceConnected() => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Hello, Maxim', + style: h4, + ), + Text( + 'Your glucose data today are', + style: Theme.of(context).isLight() + ? bodyMRegular.copyWith(color: base90) + : bodyMRegular, + ), + ], + ), + const SizedBox( + height: 20, + ), + SizedBox( + height: 200, + width: double.infinity, + child: GaugeIndicator( + value: state.currentGlucoseValue, + ), + ), + const SizedBox( + height: 20, + ), + const Row( + children: [ + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ), + Expanded( + child: InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ), + ], + ), + const InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + const InfoCard( + iconPath: arrowUpSvg, + title: 'Highest', + value: 5.7, + ), + ], ), - ], - ), - const SizedBox( - height: 20, - ), - const SizedBox( - height: 200, - width: double.infinity, - child: GaugeIndicator( - value: 3, - ), - ), - const SizedBox( - height: 20, - ), - const Row( - children: [ - Expanded( - child: InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, - ), + DeviceDisconnected() => const Center( + child: Text('First of all please connect your device'), ), - Expanded( - child: InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, - ), - ), - ], - ), - const InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, - ), - const InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, - ), - ], + }; + }, ); } } diff --git a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart index 81b8735..a371c9b 100644 --- a/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart +++ b/mobile_app/lib/presentation/home/widgets/gauge_indicator.dart @@ -34,7 +34,7 @@ class GaugeIndicator extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - value.toString(), + value.toStringAsFixed(2), style: h4, ), const Text( diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index 814101d..6e18328 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -7,7 +7,9 @@ import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; import 'package:mobile_app/presentation/chart/cubit/chart_cubit.dart'; import 'package:mobile_app/presentation/chart/screen.dart'; import 'package:mobile_app/presentation/history/history_page.dart'; +import 'package:mobile_app/presentation/home/cubit/home_cubit.dart'; import 'package:mobile_app/presentation/home/home_page.dart'; +import 'package:mobile_app/presentation/main/cubit/home_page_cubit.dart'; import 'package:mobile_app/presentation/settings/screen.dart'; class MainPageWidget extends StatefulWidget { @@ -39,9 +41,11 @@ class _MainPageWidgetState extends State { @override Widget build(BuildContext context) { - final pages = [ - const HomePageWidget(), + BlocProvider( + create: (context) => HomeCubit(), + child: const HomePageWidget(), + ), const HistoryPage(), BlocProvider( create: (context) => AddNoteCubit(), @@ -63,7 +67,10 @@ class _MainPageWidgetState extends State { ), body: Padding( padding: const EdgeInsets.all(16), - child: pages[_selectedIndex], + child: IndexedStack( + index: _selectedIndex, + children: pages, + ), ), bottomNavigationBar: BottomNavigationBar( items: const [ From 5ab02ebd967efa3d5fdcafdb1d7ab27c2c8b43d5 Mon Sep 17 00:00:00 2001 From: Max Size Date: Tue, 14 May 2024 12:37:27 +0300 Subject: [PATCH 16/18] Added foreground service and getting data for the chart --- .../android/app/src/main/AndroidManifest.xml | 13 ++ mobile_app/lib/config/const.dart | 3 + .../local/shared_prefs/shared_prefs.dart | 13 ++ .../data_source/mock/mock_ble_device.dart | 2 +- .../repository/measurements_repository.dart | 14 +- mobile_app/lib/di/di_container.dart | 8 +- .../local/glucose_data_repository.dart | 8 +- .../foreground_service/task_handler.dart | 64 ++++++++ .../presentation/chart/cubit/chart_cubit.dart | 44 +++++- .../presentation/chart/cubit/chart_state.dart | 47 +++++- mobile_app/lib/presentation/chart/screen.dart | 83 ++++++++--- .../presentation/chart/widgets/date_card.dart | 27 +++- .../chart/widgets/date_column.dart | 8 +- .../chart/widgets/line_chart.dart | 55 +++---- .../lib/presentation/components/card.dart | 2 +- .../presentation/home/cubit/home_cubit.dart | 137 +++++++++++++++++- .../lib/presentation/home/home_page.dart | 20 ++- .../lib/presentation/main/main_page.dart | 89 ++++++------ mobile_app/pubspec.yaml | 3 + 19 files changed, 516 insertions(+), 124 deletions(-) create mode 100644 mobile_app/lib/data/data_source/local/shared_prefs/shared_prefs.dart create mode 100644 mobile_app/lib/managers/foreground_service/task_handler.dart diff --git a/mobile_app/android/app/src/main/AndroidManifest.xml b/mobile_app/android/app/src/main/AndroidManifest.xml index 76f9179..15f6f74 100644 --- a/mobile_app/android/app/src/main/AndroidManifest.xml +++ b/mobile_app/android/app/src/main/AndroidManifest.xml @@ -15,10 +15,23 @@ https://developer.android.com/about/versions/12/features/bluetooth-permissions - + + + + + + + + + + _prefs.getBool(_isDeviceConnected); + + Future setIsConnected(bool value) async => _prefs.setBool(_isDeviceConnected, value); +} \ No newline at end of file diff --git a/mobile_app/lib/data/data_source/mock/mock_ble_device.dart b/mobile_app/lib/data/data_source/mock/mock_ble_device.dart index 8fffaf9..320cd5f 100644 --- a/mobile_app/lib/data/data_source/mock/mock_ble_device.dart +++ b/mobile_app/lib/data/data_source/mock/mock_ble_device.dart @@ -11,7 +11,7 @@ class MockBluetoothDevice { double lastValue = 3; Stream get dataStream { isConnected = true; - return Stream.periodic(const Duration(seconds: 3), (i) => _getNewValue()); + return Stream.periodic(const Duration(seconds: 30), (i) => _getNewValue()); } double _getNewValue() { diff --git a/mobile_app/lib/data/repository/measurements_repository.dart b/mobile_app/lib/data/repository/measurements_repository.dart index 74147ed..346f60c 100644 --- a/mobile_app/lib/data/repository/measurements_repository.dart +++ b/mobile_app/lib/data/repository/measurements_repository.dart @@ -9,14 +9,17 @@ class GlucoseDataRepository implements IGlucoseDataRepository { GlucoseDataRepository({required AppDatabase database}) : _database = database; + @override Future>> getGlucoseDataForDay( {DateTime? date}) async { date ??= DateTime.now(); try { final glucoseData = await _database.managers.glucoseMeasuremnts - .filter((f) => f.createdAt - .isBetween(date!, DateTime(date.year, date.month, date.day + 1))) + .filter((f) => f.createdAt.isBetween( + DateTime(date!.year, date.month, date.day), + DateTime(date.year, date.month, date.day + 1), + )) .get(); return right(glucoseData); } catch (e) { @@ -26,10 +29,11 @@ class GlucoseDataRepository implements IGlucoseDataRepository { @override Future addClucoseMeasurement( - {required GlucoseMeasuremnt measurement}) async { + {required double measurementValue, + required DateTime measurementDate}) async { try { - await _database.managers.glucoseMeasuremnts.create((o) => - o(createdAt: Value(measurement.createdAt), value: measurement.value)); + await _database.managers.glucoseMeasuremnts.create( + (o) => o(createdAt: Value(measurementDate), value: measurementValue)); return true; } catch (e) { return false; diff --git a/mobile_app/lib/di/di_container.dart b/mobile_app/lib/di/di_container.dart index 02c18f3..e68079c 100644 --- a/mobile_app/lib/di/di_container.dart +++ b/mobile_app/lib/di/di_container.dart @@ -2,15 +2,21 @@ import 'package:dio/dio.dart'; import 'package:get_it/get_it.dart'; import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/data/data_source/local/shared_prefs/shared_prefs.dart'; import 'package:mobile_app/data/repository/ble_device_repo.dart'; import 'package:mobile_app/data/repository/measurements_repository.dart'; import 'package:mobile_app/domain/repository/ble_device_repo.dart'; +import 'package:shared_preferences/shared_preferences.dart'; final sl = GetIt.instance; final regLs = sl.registerLazySingleton; final regS = sl.registerSingleton; -void init() { +Future init() async { + final sharedPrefs = await SharedPreferences.getInstance(); + regLs( + () => SharedPreferencesManager(sharedPrefs), + ); regLs(() => AppDatabase()); regLs( () => Dio() diff --git a/mobile_app/lib/domain/repository/local/glucose_data_repository.dart b/mobile_app/lib/domain/repository/local/glucose_data_repository.dart index c98cef2..89dfa25 100644 --- a/mobile_app/lib/domain/repository/local/glucose_data_repository.dart +++ b/mobile_app/lib/domain/repository/local/glucose_data_repository.dart @@ -2,7 +2,9 @@ import 'package:dartz/dartz.dart'; import 'package:mobile_app/data/models/db/measurement.dart'; abstract interface class IGlucoseDataRepository { - Future>> getGlucoseDataForDay({DateTime? date}); + Future>> getGlucoseDataForDay( + {DateTime? date}); - Future addClucoseMeasurement({required GlucoseMeasuremnt measurement}); -} \ No newline at end of file + Future addClucoseMeasurement( + {required double measurementValue, required DateTime measurementDate}); +} diff --git a/mobile_app/lib/managers/foreground_service/task_handler.dart b/mobile_app/lib/managers/foreground_service/task_handler.dart new file mode 100644 index 0000000..bf6d307 --- /dev/null +++ b/mobile_app/lib/managers/foreground_service/task_handler.dart @@ -0,0 +1,64 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:async'; +import 'dart:isolate'; + +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; + +import 'package:mobile_app/config/routes.dart'; +import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/data/data_source/mock/mock_ble_device.dart'; +import 'package:mobile_app/data/repository/measurements_repository.dart'; + +class FirstTaskHandler extends TaskHandler { + SendPort? _sendPort; + StreamSubscription? streamSubscription; + final measurementsTableRepo = GlucoseDataRepository(database: AppDatabase()); + // Called when the task is started. + @override + void onStart(DateTime timestamp, SendPort? sendPort) async { + final mockBluetoothDevice = MockBluetoothDevice(); + _sendPort = sendPort; + + streamSubscription = mockBluetoothDevice.dataStream.listen((value) { + sendPort?.send(value); + measurementsTableRepo.addClucoseMeasurement( + measurementDate: DateTime.now(), + measurementValue: value, + ); + FlutterForegroundTask.updateService( + notificationText: 'Current glucose value: ${value.toStringAsFixed(2)} mmol/L'); + }); + } + + // Called every [interval] milliseconds in [ForegroundTaskOptions]. + @override + void onRepeatEvent(DateTime timestamp, SendPort? sendPort) async { + // Send data to the main isolate. + sendPort?.send('sent data'); + } + + // Called when the notification button on the Android platform is pressed. + @override + void onDestroy(DateTime timestamp, SendPort? sendPort) async { + await streamSubscription?.cancel(); + } + + // Called when the notification button on the Android platform is pressed. + @override + void onNotificationButtonPressed(String id) { + print('onNotificationButtonPressed >> $id'); + } + + // Called when the notification itself on the Android platform is pressed. + // + // "android.permission.SYSTEM_ALERT_WINDOW" permission must be granted for + // this function to be called. + @override + void onNotificationPressed() { + // Note that the app will only route to "/resume-route" when it is exited so + // it will usually be necessary to send a message through the send port to + // signal it to restore state when the app is already started. + FlutterForegroundTask.launchApp(Routes.splash); + _sendPort?.send('onNotificationPressed'); + } +} diff --git a/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart b/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart index 8c8a514..3995eab 100644 --- a/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart +++ b/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart @@ -1,15 +1,51 @@ import 'package:bloc/bloc.dart'; +import 'package:fl_chart/fl_chart.dart'; import 'package:meta/meta.dart'; +import 'package:mobile_app/data/repository/measurements_repository.dart'; +import 'package:mobile_app/di/di_container.dart'; part 'chart_state.dart'; class ChartCubit extends Cubit { + final glucoseDataRepo = sl(); ChartCubit() : super(ChartLoading()) { - emit(ChartData(pickedDayIndex: 1)); + getStatsForDay(DateTime.now(), 6); } - void setPickedDay(int index) { - final curState = state as ChartData; - emit(curState.copyWith(pickedDayIndex: index)); + + Future getStatsForDay(DateTime date, int dayIndex) async { + emit(ChartLoading()); + final data = await glucoseDataRepo.getGlucoseDataForDay(date: date); + data.fold((l) => null, (r) { + if(r.isEmpty){ + emit(EmptyDayData(pickedDayIndex: dayIndex)); + return; + } + var minValue = 6.0; + var maxValue = 0.0; + var valuesSum = 0.0; + final spots = r.map((measurement) { + final time = + measurement.createdAt!.hour + measurement.createdAt!.minute / 60; + if (measurement.value < minValue) minValue = measurement.value; + if (measurement.value > maxValue) maxValue = measurement.value; + valuesSum += measurement.value; + return FlSpot(time, measurement.value); + }).toList(); + + final averageValue = valuesSum / r.length; + final variabilityValue = maxValue - minValue; + + emit( + ChartData( + spots: spots, + minValue: minValue, + maxValue: maxValue, + averageValue: averageValue, + variabilityValue: variabilityValue, + pickedDayIndex: dayIndex, + ), + ); + }); } } diff --git a/mobile_app/lib/presentation/chart/cubit/chart_state.dart b/mobile_app/lib/presentation/chart/cubit/chart_state.dart index d75b8b3..26f001b 100644 --- a/mobile_app/lib/presentation/chart/cubit/chart_state.dart +++ b/mobile_app/lib/presentation/chart/cubit/chart_state.dart @@ -5,11 +5,52 @@ sealed class ChartState {} final class ChartLoading extends ChartState {} + final class ChartData extends ChartState { final int pickedDayIndex; + final List spots; + final double minValue; + final double maxValue; + final double averageValue; + final double variabilityValue; + + ChartData({ + required this.spots, + required this.minValue, + required this.maxValue, + required this.averageValue, + required this.variabilityValue, + required this.pickedDayIndex, + }); + + ChartData copyWith( + {int? pickedDayIndex, + + List? spots, + double? minValue, + double? maxValue, + double? averageValue, + double? variabilityValue}) => + ChartData( + pickedDayIndex: pickedDayIndex ?? this.pickedDayIndex, + + spots: spots ?? this.spots, + minValue: minValue ?? this.minValue, + maxValue: maxValue ?? this.maxValue, + averageValue: averageValue ?? this.averageValue, + variabilityValue: variabilityValue ?? this.variabilityValue, + ); +} + +final class EmptyDayData extends ChartState{ + final int pickedDayIndex; - ChartData({required this.pickedDayIndex}); + EmptyDayData({ + required this.pickedDayIndex, + }); - ChartData copyWith({int? pickedDayIndex}) => - ChartData(pickedDayIndex: pickedDayIndex ?? this.pickedDayIndex); + EmptyDayData copyWith({int? pickedDayIndex, DateTime? pickedDate}) => + EmptyDayData( + pickedDayIndex: pickedDayIndex ?? this.pickedDayIndex, + ); } diff --git a/mobile_app/lib/presentation/chart/screen.dart b/mobile_app/lib/presentation/chart/screen.dart index c11172c..0d0c72a 100644 --- a/mobile_app/lib/presentation/chart/screen.dart +++ b/mobile_app/lib/presentation/chart/screen.dart @@ -30,14 +30,23 @@ class ChartScreen extends StatelessWidget { separatorBuilder: (context, index) => const SizedBox( width: 13, ), - itemBuilder: (context, index) => DateCard( - status: index == state.pickedDayIndex - ? DateCardStatus.picked - : DateCardStatus.notPicked, - onCardTap: () => cubit.setPickedDay(index), - ), + itemBuilder: (context, index) { + final day = DateTime.now().day - 6 + index; + final date = DateTime( + DateTime.now().year, + DateTime.now().month, + day, + ); + return DateCard( + date: date, + status: index == state.pickedDayIndex + ? DateCardStatus.picked + : DateCardStatus.notPicked, + onCardTap: () => cubit.getStatsForDay(date, index), + ); + }, scrollDirection: Axis.horizontal, - itemCount: 20, + itemCount: 7, ), ), SizedBox( @@ -81,33 +90,67 @@ class ChartScreen extends StatelessWidget { ], ), ), - const Row( + Row( children: [ Expanded( child: InfoCard( iconPath: arrowUpSvg, title: 'Highest', - value: 5.7, + value: state.maxValue, ), ), Expanded( child: InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, + iconPath: arrowDownSvg, + title: 'Lowest', + value: state.minValue, ), ), ], ), - const InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, + InfoCard( + iconPath: middle, + title: 'Average', + value: state.averageValue, ), - const InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, + InfoCard( + iconPath: variability, + title: 'Variability', + value: state.variabilityValue, + ), + ], + ), + EmptyDayData() => Column( + children: [ + SizedBox( + height: 60, + child: ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + width: 13, + ), + itemBuilder: (context, index) { + final day = DateTime.now().day - 6 + index; + final date = DateTime( + DateTime.now().year, + DateTime.now().month, + day, + ); + return DateCard( + date: date, + status: index == state.pickedDayIndex + ? DateCardStatus.picked + : DateCardStatus.notPicked, + onCardTap: () => cubit.getStatsForDay(date, index), + ); + }, + scrollDirection: Axis.horizontal, + itemCount: 7, + ), + ), + const Expanded( + child: Center( + child: Text('No data'), + ), ), ], ), diff --git a/mobile_app/lib/presentation/chart/widgets/date_card.dart b/mobile_app/lib/presentation/chart/widgets/date_card.dart index c765ee5..f27a9cf 100644 --- a/mobile_app/lib/presentation/chart/widgets/date_card.dart +++ b/mobile_app/lib/presentation/chart/widgets/date_card.dart @@ -10,13 +10,28 @@ enum DateCardStatus { class DateCard extends StatelessWidget { final DateCardStatus status; final VoidCallback? onCardTap; + final DateTime date; const DateCard({ super.key, required this.status, this.onCardTap, + required this.date, }); + String get _dayOfWeek { + return switch (date.weekday) { + 1 => 'Mon', + 2 => 'Tue', + 3 => 'Wed', + 4 => 'Thu', + 5 => 'Fri', + 6 => 'Sat', + 7 => 'Sun', + _ => '...', + }; + } + @override Widget build(BuildContext context) { return status == DateCardStatus.picked @@ -26,11 +41,19 @@ class DateCard extends StatelessWidget { border: Border.all(color: green50), borderRadius: BorderRadius.circular(4), ), - child: const DateColumn(textColor: base90), + child: DateColumn( + textColor: base90, + day: date.day, + dayOfWeek: _dayOfWeek, + ), ) : InkWell( onTap: onCardTap, - child: const DateColumn(textColor: base40), + child: DateColumn( + textColor: base40, + day: date.day, + dayOfWeek: _dayOfWeek, + ), ); } } diff --git a/mobile_app/lib/presentation/chart/widgets/date_column.dart b/mobile_app/lib/presentation/chart/widgets/date_column.dart index 9260f3b..3394b43 100644 --- a/mobile_app/lib/presentation/chart/widgets/date_column.dart +++ b/mobile_app/lib/presentation/chart/widgets/date_column.dart @@ -4,9 +4,13 @@ import 'package:mobile_app/config/theme.dart'; class DateColumn extends StatelessWidget { final Color textColor; + final int day; + final String dayOfWeek; const DateColumn({ super.key, required this.textColor, + required this.day, + required this.dayOfWeek, }); @override @@ -20,7 +24,7 @@ class DateColumn extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - 'Wd', + dayOfWeek, style: Theme.of(context).isLight() ? bodyMRegular.copyWith(color: textColor) : bodyMRegular, @@ -29,7 +33,7 @@ class DateColumn extends StatelessWidget { height: 4, ), Text( - '6', + '$day', style: Theme.of(context).isLight() ? bodyMRegular.copyWith(color: textColor) : bodyMRegular, diff --git a/mobile_app/lib/presentation/chart/widgets/line_chart.dart b/mobile_app/lib/presentation/chart/widgets/line_chart.dart index 1f6effc..90f43ea 100644 --- a/mobile_app/lib/presentation/chart/widgets/line_chart.dart +++ b/mobile_app/lib/presentation/chart/widgets/line_chart.dart @@ -1,8 +1,10 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; +import 'package:mobile_app/presentation/chart/cubit/chart_cubit.dart'; class LineChartSample2 extends StatefulWidget { const LineChartSample2({super.key}); @@ -14,22 +16,26 @@ class LineChartSample2 extends StatefulWidget { class _LineChartSample2State extends State { @override Widget build(BuildContext context) { - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: 1200, - child: Padding( - padding: const EdgeInsets.only( - right: 18, - left: 20, - top: 24, - bottom: 12, + return BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: 1200, + child: Padding( + padding: const EdgeInsets.only( + right: 18, + left: 20, + top: 24, + bottom: 12, + ), + child: LineChart( + mainData((state as ChartData).spots), + ), + ), ), - child: LineChart( - mainData(), - ), - ), - ), + ); + }, ); } @@ -72,8 +78,15 @@ class _LineChartSample2State extends State { return Text(text, style: style, textAlign: TextAlign.left); } - LineChartData mainData() { + LineChartData mainData(List spots) { return LineChartData( + lineTouchData: LineTouchData( + touchTooltipData: LineTouchTooltipData( + getTooltipItems: (touchedSpots) => touchedSpots + .map((e) => LineTooltipItem( + e.y.toStringAsFixed(2), const TextStyle(color: green5))) + .toList(), + )), borderData: FlBorderData(show: false), gridData: FlGridData( drawVerticalLine: false, @@ -105,15 +118,7 @@ class _LineChartSample2State extends State { maxY: 6.1, lineBarsData: [ LineChartBarData( - spots: const [ - FlSpot(0, 3), - FlSpot(2.6, 2), - FlSpot(4.9, 5), - FlSpot(6.8, 3.1), - FlSpot(8, 4), - FlSpot(9.5, 3), - FlSpot(11, 4), - ], + spots: spots, color: green30, isCurved: true, barWidth: 3, diff --git a/mobile_app/lib/presentation/components/card.dart b/mobile_app/lib/presentation/components/card.dart index 9b37115..1484959 100644 --- a/mobile_app/lib/presentation/components/card.dart +++ b/mobile_app/lib/presentation/components/card.dart @@ -47,7 +47,7 @@ class InfoCard extends StatelessWidget { Row( children: [ Text( - value.toString(), + value.toStringAsFixed(1), style: h4, ), Expanded( diff --git a/mobile_app/lib/presentation/home/cubit/home_cubit.dart b/mobile_app/lib/presentation/home/cubit/home_cubit.dart index 53007be..b84aeda 100644 --- a/mobile_app/lib/presentation/home/cubit/home_cubit.dart +++ b/mobile_app/lib/presentation/home/cubit/home_cubit.dart @@ -1,31 +1,156 @@ import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; import 'package:bloc/bloc.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:meta/meta.dart'; +import 'package:mobile_app/data/data_source/local/shared_prefs/shared_prefs.dart'; +import 'package:mobile_app/data/repository/measurements_repository.dart'; import 'package:mobile_app/di/di_container.dart'; import 'package:mobile_app/domain/repository/ble_device_repo.dart'; +import 'package:mobile_app/managers/foreground_service/task_handler.dart'; part 'home_state.dart'; +@pragma('vm:entry-point') +void startCallback() { + // The setTaskHandler function must be called to handle the task in the background. + FlutterForegroundTask.setTaskHandler(FirstTaskHandler()); +} + class HomeCubit extends Cubit { - StreamSubscription? valueStream; + ReceivePort? _receivePort; + StreamSubscription? _streamSubscription; final bleDeviceRepo = sl(); + final sharedPrefs = sl(); + final glucoseDatarepo = sl(); HomeCubit() : super(HomeLoading()) { _initListener(); + _initForegroundService(); } + Future _initForegroundService() async { + await _requestPermissionForAndroid(); + _initForegroundTask(); + + // You can get the previous ReceivePort without restarting the service. + if (await FlutterForegroundTask.isRunningService) { + final newReceivePort = FlutterForegroundTask.receivePort; + _registerReceivePort(newReceivePort); + } + } + + void _initListener() { bleDeviceRepo.isConnected.listen((isConnected) { switch (isConnected) { case true: - valueStream = bleDeviceRepo.valueStream?.listen((value) { - if (!isClosed) emit(DeviceConnected(currentGlucoseValue: value)); - }); - + _startForegroundTask(); + sharedPrefs.setIsConnected(true); case false: - valueStream?.cancel(); + sharedPrefs.setIsConnected(false); emit(DeviceDisconnected()); } }); } + + + void _initForegroundTask() { + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + foregroundServiceType: AndroidForegroundServiceType.HEALTH, + channelId: 'foreground_service', + channelName: 'Foreground Service Notification', + channelDescription: + 'This notification appears when the foreground service is running.', + channelImportance: NotificationChannelImportance.HIGH, + priority: NotificationPriority.HIGH, + iconData: const NotificationIconData( + resType: ResourceType.mipmap, + resPrefix: ResourcePrefix.ic, + name: 'launcher', + ), + buttons: [ + const NotificationButton(id: 'sendButton', text: 'Send'), + const NotificationButton(id: 'testButton', text: 'Test'), + ], + ), + iosNotificationOptions: const IOSNotificationOptions( + showNotification: true, + playSound: false, + ), + foregroundTaskOptions: const ForegroundTaskOptions( + isOnceEvent: true, + autoRunOnBoot: true, + allowWakeLock: true, + allowWifiLock: true, + ), + ); + } + + Future _requestPermissionForAndroid() async { + if (!Platform.isAndroid) { + return; + } + if (!await FlutterForegroundTask.canDrawOverlays) { + await FlutterForegroundTask.openSystemAlertWindowSettings(); + } + + if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) { + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + } + final notificationPermissionStatus = + await FlutterForegroundTask.checkNotificationPermission(); + if (notificationPermissionStatus != NotificationPermission.granted) { + await FlutterForegroundTask.requestNotificationPermission(); + } + } + + Future _startForegroundTask() async { + // Register the receivePort before starting the service. + final receivePort = FlutterForegroundTask.receivePort; + final isRegistered = _registerReceivePort(receivePort); + if (!isRegistered) { + print('Failed to register receivePort!'); + return false; + } + + if (await FlutterForegroundTask.isRunningService) { + return FlutterForegroundTask.restartService(); + } else { + return FlutterForegroundTask.startService( + notificationTitle: 'Foreground Service is running', + notificationText: 'Tap to return to the app', + callback: startCallback, + ); + } + } + + bool _registerReceivePort(ReceivePort? newReceivePort) { + if (newReceivePort == null) { + return false; + } + + closeReceivePort(); + + _receivePort = newReceivePort; + _receivePort?.listen((data) { + if (data is double && !isClosed) + emit(DeviceConnected(currentGlucoseValue: data as double)); + }); + + return _receivePort != null; + } + + void closeReceivePort() { + _receivePort?.close(); + _receivePort = null; + } + + @override + Future close() { + closeReceivePort(); + return super.close(); + } } diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart index d467f96..acc1f85 100644 --- a/mobile_app/lib/presentation/home/home_page.dart +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/const.dart'; @@ -62,22 +63,25 @@ class HomePageWidget extends StatelessWidget { ), Expanded( child: InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', + iconPath: arrowDownSvg, + title: 'Lowest', value: 5.7, ), ), ], ), const InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', + iconPath: middle, + title: 'Average', value: 5.7, ), - const InfoCard( - iconPath: arrowUpSvg, - title: 'Highest', - value: 5.7, + GestureDetector( + onTap:() => context.read().closeReceivePort(), + child: const InfoCard( + iconPath: variability, + title: 'Variability', + value: 5.2, + ), ), ], ), diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index 6e18328..a382412 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:mobile_app/config/colors.dart'; import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/add_note/add_note_page.dart'; @@ -58,51 +59,53 @@ class _MainPageWidgetState extends State { const SettingsScreen(), ]; - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: Text( - getTitle(_selectedIndex), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: IndexedStack( - index: _selectedIndex, - children: pages, - ), - ), - bottomNavigationBar: BottomNavigationBar( - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home_outlined), - label: 'fdsf', - ), - BottomNavigationBarItem( - icon: Icon(Icons.history), - label: 'fdsf', + return WithForegroundTask( + child: Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: Text( + getTitle(_selectedIndex), ), - BottomNavigationBarItem( - icon: Icon(Icons.add), - label: 'fdsf', - ), - BottomNavigationBarItem( - icon: Icon(Icons.auto_graph), - label: 'fdsf', - ), - BottomNavigationBarItem( - icon: Icon(Icons.settings), - label: 'fdsf', + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: IndexedStack( + index: _selectedIndex, + children: pages, ), - ], - onTap: _onItemTapped, - selectedItemColor: Theme.of(context).isLight() ? green50 : green30, - currentIndex: _selectedIndex, - unselectedItemColor: base50, - showSelectedLabels: false, - showUnselectedLabels: false, - iconSize: 30, - type: BottomNavigationBarType.fixed, + ), + bottomNavigationBar: BottomNavigationBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home_outlined), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.history), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.add), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.auto_graph), + label: 'fdsf', + ), + BottomNavigationBarItem( + icon: Icon(Icons.settings), + label: 'fdsf', + ), + ], + onTap: _onItemTapped, + selectedItemColor: Theme.of(context).isLight() ? green50 : green30, + currentIndex: _selectedIndex, + unselectedItemColor: base50, + showSelectedLabels: false, + showUnselectedLabels: false, + iconSize: 30, + type: BottomNavigationBarType.fixed, + ), ), ); } diff --git a/mobile_app/pubspec.yaml b/mobile_app/pubspec.yaml index 4e97cac..a012112 100644 --- a/mobile_app/pubspec.yaml +++ b/mobile_app/pubspec.yaml @@ -27,6 +27,9 @@ dependencies: path_provider: ^2.0.0 path: ^1.9.0 flutter_blue_plus: ^1.32.5 + flutter_foreground_task: ^6.2.0 + shared_preferences: ^2.2.3 + From c1e65b1c374e573bdebac3c379b7126400cffa6a Mon Sep 17 00:00:00 2001 From: Max Size Date: Tue, 14 May 2024 18:13:23 +0300 Subject: [PATCH 17/18] Created table for glucose events --- .../lib/config/utils/month_converter.dart | 30 +++++ .../lib/data/data_source/local/database.dart | 23 +++- .../local/tables/glucose_events.dart | 9 ++ .../lib/data/models/db/glucose_event.dart | 114 ++++++++++++++++++ .../lib/data/models/db/measurement.dart | 25 ++-- .../data/repository/glucose_event_repo.dart | 41 +++++++ .../repository/measurements_repository.dart | 7 +- mobile_app/lib/di/di_container.dart | 13 ++ .../entities/glucose_evens_for_day.dart | 11 ++ .../lib/domain/entities/glucose_event.dart | 25 ---- .../domain/entities/glucose_measurement.dart | 1 - .../local/glucose_events_repository.dart | 13 ++ .../presentation/add_note/add_note_page.dart | 54 ++++++++- .../add_note/cubit/add_note_cubit.dart | 17 ++- .../bluetooth/widgets/devices_column.dart | 1 - .../presentation/chart/cubit/chart_cubit.dart | 2 +- .../components/custom_text_field.dart | 10 +- .../history/cubit/history_cubit.dart | 33 ++++- .../history/cubit/history_state.dart | 19 ++- .../presentation/history/history_page.dart | 40 ++++-- .../history/widgets/day_column.dart | 41 +------ .../presentation/home/cubit/home_cubit.dart | 7 +- .../lib/presentation/main/main_page.dart | 20 +-- .../lib/presentation/tabs/home_tab.dart | 25 ++++ 24 files changed, 464 insertions(+), 117 deletions(-) create mode 100644 mobile_app/lib/config/utils/month_converter.dart create mode 100644 mobile_app/lib/data/data_source/local/tables/glucose_events.dart create mode 100644 mobile_app/lib/data/models/db/glucose_event.dart create mode 100644 mobile_app/lib/data/repository/glucose_event_repo.dart create mode 100644 mobile_app/lib/domain/entities/glucose_evens_for_day.dart delete mode 100644 mobile_app/lib/domain/entities/glucose_event.dart delete mode 100644 mobile_app/lib/domain/entities/glucose_measurement.dart create mode 100644 mobile_app/lib/domain/repository/local/glucose_events_repository.dart create mode 100644 mobile_app/lib/presentation/tabs/home_tab.dart diff --git a/mobile_app/lib/config/utils/month_converter.dart b/mobile_app/lib/config/utils/month_converter.dart new file mode 100644 index 0000000..e24a03f --- /dev/null +++ b/mobile_app/lib/config/utils/month_converter.dart @@ -0,0 +1,30 @@ +String getMonthByNumber(int monthNumber) { + switch (monthNumber) { + case 1: + return 'January'; + case 2: + return 'February'; + case 3: + return 'March'; + case 4: + return 'April'; + case 5: + return 'May'; + case 6: + return 'June'; + case 7: + return 'July'; + case 8: + return 'August'; + case 9: + return 'September'; + case 10: + return 'October'; + case 11: + return 'November'; + case 12: + return 'December'; + default: + return ''; + } +} \ No newline at end of file diff --git a/mobile_app/lib/data/data_source/local/database.dart b/mobile_app/lib/data/data_source/local/database.dart index 4bef94a..c8965ae 100644 --- a/mobile_app/lib/data/data_source/local/database.dart +++ b/mobile_app/lib/data/data_source/local/database.dart @@ -2,8 +2,12 @@ import 'dart:io'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; +import 'package:mobile_app/data/data_source/local/tables/glucose_events.dart'; import 'package:mobile_app/data/data_source/local/tables/measurements_table.dart'; +import 'package:mobile_app/data/models/db/glucose_event.dart'; import 'package:mobile_app/data/models/db/measurement.dart'; +// import 'package:mobile_app/data/models/db/glucose_event.dart'; +// import 'package:mobile_app/data/models/db/measurement.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqlite3/sqlite3.dart'; @@ -11,12 +15,27 @@ import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; part 'database.g.dart'; -@DriftDatabase(tables: [GlucoseMeasuremnts]) +@DriftDatabase(tables: [GlucoseMeasuremnts, GlucoseEvents]) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 1; + int get schemaVersion => 2; + + @override + // TODO: implement migration + MigrationStrategy get migration { + return MigrationStrategy( + onCreate: (m) async { + await m.createAll(); + }, + onUpgrade: (m, from, to) async { + if(from < 2){ + await m.createTable(glucoseEvents); + } + }, + ); + } } LazyDatabase _openConnection() { diff --git a/mobile_app/lib/data/data_source/local/tables/glucose_events.dart b/mobile_app/lib/data/data_source/local/tables/glucose_events.dart new file mode 100644 index 0000000..6de80df --- /dev/null +++ b/mobile_app/lib/data/data_source/local/tables/glucose_events.dart @@ -0,0 +1,9 @@ +import 'package:drift/drift.dart'; + +class GlucoseEvents extends Table { + IntColumn get id => integer().autoIncrement()(); + RealColumn get value => real()(); + DateTimeColumn get createdAt => dateTime()(); + TextColumn get description => text()(); + IntColumn get eventEnumIndex => integer()(); +} \ No newline at end of file diff --git a/mobile_app/lib/data/models/db/glucose_event.dart b/mobile_app/lib/data/models/db/glucose_event.dart new file mode 100644 index 0000000..c51aa48 --- /dev/null +++ b/mobile_app/lib/data/models/db/glucose_event.dart @@ -0,0 +1,114 @@ +import 'package:drift/drift.dart'; +import 'package:mobile_app/config/const.dart'; +import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/domain/glucose_event_types.dart'; + +class GlucoseEvent extends DataClass implements Insertable { + final int id; + final double value; + final DateTime createdAt; + final String description; + final int eventEnumIndex; + const GlucoseEvent( + {required this.id, + required this.value, + required this.createdAt, + required this.description, + required this.eventEnumIndex}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['value'] = Variable(value); + map['created_at'] = Variable(createdAt); + map['description'] = Variable(description); + map['event_enum_index'] = Variable(eventEnumIndex); + return map; + } + + GlucoseEventsCompanion toCompanion(bool nullToAbsent) { + return GlucoseEventsCompanion( + id: Value(id), + value: Value(value), + createdAt: Value(createdAt), + description: Value(description), + eventEnumIndex: Value(eventEnumIndex), + ); + } + + factory GlucoseEvent.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GlucoseEvent( + id: serializer.fromJson(json['id']), + value: serializer.fromJson(json['value']), + createdAt: serializer.fromJson(json['createdAt']), + description: serializer.fromJson(json['description']), + eventEnumIndex: serializer.fromJson(json['eventEnumIndex']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'value': serializer.toJson(value), + 'createdAt': serializer.toJson(createdAt), + 'description': serializer.toJson(description), + 'eventEnumIndex': serializer.toJson(eventEnumIndex), + }; + } + + GlucoseEvent copyWith( + {int? id, + double? value, + DateTime? createdAt, + String? description, + int? eventEnumIndex}) => + GlucoseEvent( + id: id ?? this.id, + value: value ?? this.value, + createdAt: createdAt ?? this.createdAt, + description: description ?? this.description, + eventEnumIndex: eventEnumIndex ?? this.eventEnumIndex, + ); + @override + String toString() { + return (StringBuffer('GlucoseEvent(') + ..write('id: $id, ') + ..write('value: $value, ') + ..write('createdAt: $createdAt, ') + ..write('description: $description, ') + ..write('eventEnumIndex: $eventEnumIndex') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, value, createdAt, description, eventEnumIndex); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GlucoseEvent && + other.id == this.id && + other.value == this.value && + other.createdAt == this.createdAt && + other.description == this.description && + other.eventEnumIndex == this.eventEnumIndex); + + String get iconPath { + if (eventEnumIndex == GlucoseEventType.food.index) { + return foodSvg; + } else if (eventEnumIndex == GlucoseEventType.insulin.index) { + return injectionSvg; + } else if (eventEnumIndex == GlucoseEventType.food.index) { + return foodSvg; + } else { + return activitySvg; + } + } + + String get time => + '${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}'; +} diff --git a/mobile_app/lib/data/models/db/measurement.dart b/mobile_app/lib/data/models/db/measurement.dart index f180fc9..15c59d8 100644 --- a/mobile_app/lib/data/models/db/measurement.dart +++ b/mobile_app/lib/data/models/db/measurement.dart @@ -5,17 +5,15 @@ class GlucoseMeasuremnt extends DataClass implements Insertable { final int id; final double value; - final DateTime? createdAt; + final DateTime createdAt; const GlucoseMeasuremnt( - {required this.id, required this.value, this.createdAt}); + {required this.id, required this.value, required this.createdAt}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['value'] = Variable(value); - if (!nullToAbsent || createdAt != null) { - map['created_at'] = Variable(createdAt); - } + map['created_at'] = Variable(createdAt); return map; } @@ -23,9 +21,7 @@ class GlucoseMeasuremnt extends DataClass return GlucoseMeasuremntsCompanion( id: Value(id), value: Value(value), - createdAt: createdAt == null && nullToAbsent - ? const Value.absent() - : Value(createdAt), + createdAt: Value(createdAt), ); } @@ -35,7 +31,7 @@ class GlucoseMeasuremnt extends DataClass return GlucoseMeasuremnt( id: serializer.fromJson(json['id']), value: serializer.fromJson(json['value']), - createdAt: serializer.fromJson(json['createdAt']), + createdAt: serializer.fromJson(json['createdAt']), ); } @override @@ -44,18 +40,15 @@ class GlucoseMeasuremnt extends DataClass return { 'id': serializer.toJson(id), 'value': serializer.toJson(value), - 'createdAt': serializer.toJson(createdAt), + 'createdAt': serializer.toJson(createdAt), }; } - GlucoseMeasuremnt copyWith( - {int? id, - double? value, - Value createdAt = const Value.absent()}) => + GlucoseMeasuremnt copyWith({int? id, double? value, DateTime? createdAt}) => GlucoseMeasuremnt( id: id ?? this.id, value: value ?? this.value, - createdAt: createdAt.present ? createdAt.value : this.createdAt, + createdAt: createdAt ?? this.createdAt, ); @override String toString() { @@ -76,4 +69,4 @@ class GlucoseMeasuremnt extends DataClass other.id == this.id && other.value == this.value && other.createdAt == this.createdAt); -} \ No newline at end of file +} diff --git a/mobile_app/lib/data/repository/glucose_event_repo.dart b/mobile_app/lib/data/repository/glucose_event_repo.dart new file mode 100644 index 0000000..b20ff9b --- /dev/null +++ b/mobile_app/lib/data/repository/glucose_event_repo.dart @@ -0,0 +1,41 @@ +import 'package:dartz/dartz.dart'; +import 'package:mobile_app/data/data_source/local/database.dart'; +import 'package:mobile_app/data/models/db/glucose_event.dart'; +import 'package:mobile_app/domain/repository/local/glucose_events_repository.dart'; + +class IGlucoseEventsRepository implements GlucoseEventsRepository { + final AppDatabase _database; + + IGlucoseEventsRepository({required AppDatabase database}) + : _database = database; + + @override + Future addClucoseEvent( + {required double measurementValue, + required DateTime measurementDate, + required String descripton, + required int eventTypeIndex}) async { + try { + await _database.managers.glucoseEvents.create((o) => o( + createdAt: measurementDate, + value: measurementValue, + description: descripton, + eventEnumIndex: eventTypeIndex)); + return true; + } catch (e) { + return false; + } + } + + @override + Future>> getGlucoseEvents() async { + try { + final glucoseEvents = await _database.managers.glucoseEvents.orderBy( + (o) => o.createdAt.desc(), + ).get(); + return right(glucoseEvents); + } catch (e) { + return left(UnitMonoid().zero()); + } + } +} diff --git a/mobile_app/lib/data/repository/measurements_repository.dart b/mobile_app/lib/data/repository/measurements_repository.dart index 346f60c..744bb8e 100644 --- a/mobile_app/lib/data/repository/measurements_repository.dart +++ b/mobile_app/lib/data/repository/measurements_repository.dart @@ -9,7 +9,6 @@ class GlucoseDataRepository implements IGlucoseDataRepository { GlucoseDataRepository({required AppDatabase database}) : _database = database; - @override Future>> getGlucoseDataForDay( {DateTime? date}) async { @@ -32,8 +31,10 @@ class GlucoseDataRepository implements IGlucoseDataRepository { {required double measurementValue, required DateTime measurementDate}) async { try { - await _database.managers.glucoseMeasuremnts.create( - (o) => o(createdAt: Value(measurementDate), value: measurementValue)); + await _database.managers.glucoseMeasuremnts.create((o) => o( + createdAt: measurementDate, + value: measurementValue, + )); return true; } catch (e) { return false; diff --git a/mobile_app/lib/di/di_container.dart b/mobile_app/lib/di/di_container.dart index e68079c..8df5eec 100644 --- a/mobile_app/lib/di/di_container.dart +++ b/mobile_app/lib/di/di_container.dart @@ -4,8 +4,10 @@ import 'package:mobile_app/config/const.dart'; import 'package:mobile_app/data/data_source/local/database.dart'; import 'package:mobile_app/data/data_source/local/shared_prefs/shared_prefs.dart'; import 'package:mobile_app/data/repository/ble_device_repo.dart'; +import 'package:mobile_app/data/repository/glucose_event_repo.dart'; import 'package:mobile_app/data/repository/measurements_repository.dart'; import 'package:mobile_app/domain/repository/ble_device_repo.dart'; +import 'package:mobile_app/domain/repository/local/glucose_events_repository.dart'; import 'package:shared_preferences/shared_preferences.dart'; final sl = GetIt.instance; @@ -14,19 +16,30 @@ final regS = sl.registerSingleton; Future init() async { final sharedPrefs = await SharedPreferences.getInstance(); + regLs( () => SharedPreferencesManager(sharedPrefs), ); + regLs(() => AppDatabase()); + regLs( () => Dio() ..options.baseUrl = baseUrl ..options.headers['Content-Type'] = 'application/json', ); + regLs( () => GlucoseDataRepository(database: sl()), ); + regLs( () => IBLEDeviceRepository(), ); + + regLs( + () => IGlucoseEventsRepository( + database: sl(), + ), + ); } diff --git a/mobile_app/lib/domain/entities/glucose_evens_for_day.dart b/mobile_app/lib/domain/entities/glucose_evens_for_day.dart new file mode 100644 index 0000000..bc3459f --- /dev/null +++ b/mobile_app/lib/domain/entities/glucose_evens_for_day.dart @@ -0,0 +1,11 @@ +import 'package:mobile_app/data/models/db/glucose_event.dart'; + +class GlucoseEventsPerDay { + final DateTime date; + final List events; + + GlucoseEventsPerDay({ + required this.date, + required this.events, + }); +} diff --git a/mobile_app/lib/domain/entities/glucose_event.dart b/mobile_app/lib/domain/entities/glucose_event.dart deleted file mode 100644 index cbf9c2c..0000000 --- a/mobile_app/lib/domain/entities/glucose_event.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:mobile_app/config/const.dart'; -import 'package:mobile_app/domain/glucose_event_types.dart'; - -class GlucoseEvent { - final GlucoseEventType eventType; - final DateTime dateTime; - final double value; - final String title; - - GlucoseEvent({ - required this.eventType, - required this.dateTime, - required this.value, - required this.title, - }); - - String get iconPath => switch (eventType) { - GlucoseEventType.sport => activitySvg, - GlucoseEventType.insulin => injectionSvg, - GlucoseEventType.food => foodSvg, - }; - - String get time => - '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; -} diff --git a/mobile_app/lib/domain/entities/glucose_measurement.dart b/mobile_app/lib/domain/entities/glucose_measurement.dart deleted file mode 100644 index d3f5a12..0000000 --- a/mobile_app/lib/domain/entities/glucose_measurement.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/mobile_app/lib/domain/repository/local/glucose_events_repository.dart b/mobile_app/lib/domain/repository/local/glucose_events_repository.dart new file mode 100644 index 0000000..2553d67 --- /dev/null +++ b/mobile_app/lib/domain/repository/local/glucose_events_repository.dart @@ -0,0 +1,13 @@ +import 'package:dartz/dartz.dart'; +import 'package:mobile_app/data/models/db/glucose_event.dart'; + +abstract interface class GlucoseEventsRepository { + Future>> getGlucoseEvents(); + + Future addClucoseEvent({ + required double measurementValue, + required DateTime measurementDate, + required String descripton, + required int eventTypeIndex, + }); +} diff --git a/mobile_app/lib/presentation/add_note/add_note_page.dart b/mobile_app/lib/presentation/add_note/add_note_page.dart index 7fd961d..b4fa08b 100644 --- a/mobile_app/lib/presentation/add_note/add_note_page.dart +++ b/mobile_app/lib/presentation/add_note/add_note_page.dart @@ -10,7 +10,16 @@ import 'package:mobile_app/presentation/components/custom_button.dart'; import 'package:mobile_app/presentation/components/custom_text_field.dart'; class AddNotePage extends StatelessWidget { - const AddNotePage({super.key}); + + AddNotePage({super.key}); + + final descriptionController = TextEditingController(); + + final valueController = TextEditingController(); + + final dateController = TextEditingController(); + + final timeController = TextEditingController(); @override Widget build(BuildContext context) { @@ -60,14 +69,15 @@ class AddNotePage extends StatelessWidget { ), CustomTextField( title: 'Description', - controller: TextEditingController(), + controller: descriptionController, ), const SizedBox( height: 16, ), CustomTextField( + onlyNumbers: true, title: 'Level of glucose', - controller: TextEditingController(), + controller: valueController, ), const SizedBox( height: 16, @@ -76,8 +86,20 @@ class AddNotePage extends StatelessWidget { children: [ Expanded( child: CustomTextField( + onTap: () async { + final date = await showDatePicker( + context: context, + firstDate: DateTime(1900), + lastDate: DateTime(DateTime.now().year + 50), + initialDate: DateTime.now(), + ); + if (date != null) { + dateController.text = + date.toIso8601String().substring(0, 10); + } + }, title: 'Date', - controller: TextEditingController(), + controller: dateController, enabled: false, ), ), @@ -86,8 +108,15 @@ class AddNotePage extends StatelessWidget { ), Expanded( child: CustomTextField( + onTap: () async { + final time = await showTimePicker( + context: context, initialTime: TimeOfDay.now()); + if (time != null) { + timeController.text = '${time.hour}:${time.minute}'; + } + }, title: 'Time', - controller: TextEditingController(), + controller: timeController, enabled: false, ), ), @@ -105,7 +134,20 @@ class AddNotePage extends StatelessWidget { CustomButton( text: 'Add note', buttonType: ButtonType.primary, - onPressed: () => null, + onPressed: () { + final date = DateTime.tryParse( + '${dateController.text} ${timeController.text}:00'); + final value = double.tryParse(valueController.text); + if (date == null || value == null) { + return; + } + context.read().addNote( + value: value, + date: date, + description: descriptionController.text, + eventEnumIndex: + state.selectedGlucoseEventType?.index ?? 0); + }, ), ], ), diff --git a/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart b/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart index df555fc..050063d 100644 --- a/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart +++ b/mobile_app/lib/presentation/add_note/cubit/add_note_cubit.dart @@ -1,14 +1,29 @@ import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; +import 'package:mobile_app/di/di_container.dart'; import 'package:mobile_app/domain/glucose_event_types.dart'; +import 'package:mobile_app/domain/repository/local/glucose_events_repository.dart'; part 'add_note_state.dart'; class AddNoteCubit extends Cubit { + final eventsRepo = sl(); + AddNoteCubit() : super(const AddNoteState()); - void onSelectedEventTypeChange(GlucoseEventType? pickedEventType){ + void onSelectedEventTypeChange(GlucoseEventType? pickedEventType) { emit(state.copyWith(selectedGlucoseEventType: pickedEventType)); } + void addNote( + {required double value, + required DateTime date, + required String description, + required int eventEnumIndex}) { + eventsRepo.addClucoseEvent( + measurementValue: value, + measurementDate: date, + descripton: description, + eventTypeIndex: eventEnumIndex); + } } diff --git a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart index 5e68e0e..6ecab01 100644 --- a/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart +++ b/mobile_app/lib/presentation/bluetooth/widgets/devices_column.dart @@ -7,7 +7,6 @@ import 'package:mobile_app/config/styles.dart'; import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/bluetooth/cubit/bluetooth_cubit.dart'; import 'package:mobile_app/presentation/bluetooth/widgets/devices_row.dart'; -import 'package:path/path.dart'; class DevicesColumn extends StatelessWidget { final List devices; diff --git a/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart b/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart index 3995eab..d8759b2 100644 --- a/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart +++ b/mobile_app/lib/presentation/chart/cubit/chart_cubit.dart @@ -26,7 +26,7 @@ class ChartCubit extends Cubit { var valuesSum = 0.0; final spots = r.map((measurement) { final time = - measurement.createdAt!.hour + measurement.createdAt!.minute / 60; + measurement.createdAt.hour + measurement.createdAt.minute / 60; if (measurement.value < minValue) minValue = measurement.value; if (measurement.value > maxValue) maxValue = measurement.value; valuesSum += measurement.value; diff --git a/mobile_app/lib/presentation/components/custom_text_field.dart b/mobile_app/lib/presentation/components/custom_text_field.dart index 1261470..307c2ea 100644 --- a/mobile_app/lib/presentation/components/custom_text_field.dart +++ b/mobile_app/lib/presentation/components/custom_text_field.dart @@ -9,6 +9,7 @@ class CustomTextField extends StatelessWidget { final TextEditingController controller; final bool enabled; final VoidCallback? onTap; + final bool onlyNumbers; const CustomTextField({ super.key, @@ -16,6 +17,7 @@ class CustomTextField extends StatelessWidget { required this.controller, this.enabled = true, this.onTap, + this.onlyNumbers = false, }); @override @@ -25,12 +27,18 @@ class CustomTextField extends StatelessWidget { children: [ Text( title, - style: Theme.of(context).isLight() ? bodySRegular.copyWith(color: base40) : bodySRegular, + style: Theme.of(context).isLight() + ? bodySRegular.copyWith(color: base40) + : bodySRegular, ), const SizedBox( height: 4, ), TextFormField( + controller: controller, + keyboardType: onlyNumbers + ? const TextInputType.numberWithOptions(decimal: true) + : null, onTap: onTap, readOnly: !enabled, decoration: InputDecoration( diff --git a/mobile_app/lib/presentation/history/cubit/history_cubit.dart b/mobile_app/lib/presentation/history/cubit/history_cubit.dart index af8d39a..9bf6564 100644 --- a/mobile_app/lib/presentation/history/cubit/history_cubit.dart +++ b/mobile_app/lib/presentation/history/cubit/history_cubit.dart @@ -1,8 +1,39 @@ import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; +import 'package:mobile_app/data/models/db/glucose_event.dart'; +import 'package:mobile_app/di/di_container.dart'; +import 'package:mobile_app/domain/entities/glucose_evens_for_day.dart'; +import 'package:mobile_app/domain/repository/local/glucose_events_repository.dart'; part 'history_state.dart'; class HistoryCubit extends Cubit { - HistoryCubit() : super(HistoryInitial()); + final eventsRepo = sl(); + + HistoryCubit() : super(HistoryLoading()){ + getAndGroupGlucoseEvents(); + } + + Future getAndGroupGlucoseEvents() async { + final result = await eventsRepo.getGlucoseEvents(); + result.fold((error) { + emit(HistoryError()); + }, (events) { + final daysMap = >{}; + for (final event in events) { + final date = DateTime( + event.createdAt.year, event.createdAt.month, event.createdAt.day); + if (daysMap.containsKey(date)) { + daysMap[date]?.add(event); + } else { + daysMap[date] = [event]; + } + } + final eventsPerDay = []; + daysMap.forEach((date, list) { + eventsPerDay.add(GlucoseEventsPerDay(date: date, events: list)); + }); + emit(HistoryData(eventDays: eventsPerDay)); + }); + } } diff --git a/mobile_app/lib/presentation/history/cubit/history_state.dart b/mobile_app/lib/presentation/history/cubit/history_state.dart index ce65197..878ed47 100644 --- a/mobile_app/lib/presentation/history/cubit/history_state.dart +++ b/mobile_app/lib/presentation/history/cubit/history_state.dart @@ -3,4 +3,21 @@ part of 'history_cubit.dart'; @immutable sealed class HistoryState {} -final class HistoryInitial extends HistoryState {} +final class HistoryError extends HistoryState {} + +final class HistoryLoading extends HistoryState {} + +final class HistoryIsEmpty extends HistoryState {} + +final class HistoryData extends HistoryState { + final List eventDays; + + HistoryData({required this.eventDays}); + + HistoryData copyWith({ + List? eventDays, + }) => + HistoryData( + eventDays: eventDays ?? this.eventDays, + ); +} diff --git a/mobile_app/lib/presentation/history/history_page.dart b/mobile_app/lib/presentation/history/history_page.dart index c812b8e..648a430 100644 --- a/mobile_app/lib/presentation/history/history_page.dart +++ b/mobile_app/lib/presentation/history/history_page.dart @@ -1,20 +1,44 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/config/utils/month_converter.dart'; +import 'package:mobile_app/presentation/history/cubit/history_cubit.dart'; import 'package:mobile_app/presentation/history/widgets/day_column.dart'; class HistoryPage extends StatelessWidget { const HistoryPage({super.key}); + @override Widget build(BuildContext context) { - return ListView.builder( - clipBehavior: Clip.none, - itemBuilder: (context, index) { - return DayColumn( - day: 'February 14th, today', - glucoseEvents: mockList, - ); + return BlocBuilder( + builder: (context, state) { + return switch (state) { + HistoryLoading() => const Center( + child: CircularProgressIndicator(), + ), + HistoryData() => ListView.builder( + clipBehavior: Clip.none, + itemBuilder: (context, index) { + final today = + DateTime.now().day == state.eventDays[index].date.day + ? ', today' + : ''; + return DayColumn( + day: + '${getMonthByNumber(state.eventDays[index].date.month)} ${state.eventDays[index].date.day}th$today', + glucoseEvents: state.eventDays[index].events, + ); + }, + itemCount: state.eventDays.length, + ), + HistoryError() => const Center( + child: Text('Something went wrong'), + ), + HistoryIsEmpty() => const Center( + child: Text('No data'), + ), + }; }, - itemCount: 4, ); } } diff --git a/mobile_app/lib/presentation/history/widgets/day_column.dart b/mobile_app/lib/presentation/history/widgets/day_column.dart index 680203e..566d66f 100644 --- a/mobile_app/lib/presentation/history/widgets/day_column.dart +++ b/mobile_app/lib/presentation/history/widgets/day_column.dart @@ -1,9 +1,7 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart'; import 'package:mobile_app/config/styles.dart'; - -import 'package:mobile_app/domain/entities/glucose_event.dart'; -import 'package:mobile_app/domain/glucose_event_types.dart'; +import 'package:mobile_app/data/models/db/glucose_event.dart'; import 'package:mobile_app/presentation/components/card.dart'; class DayColumn extends StatelessWidget { @@ -30,44 +28,11 @@ class DayColumn extends StatelessWidget { for (final event in glucoseEvents) InfoCard( iconPath: event.iconPath, - title: event.title, + title: event.description, value: event.value, time: event.time, - ) + ), ], ); } } - -final mockList = [ - GlucoseEvent( - eventType: GlucoseEventType.food, - dateTime: DateTime.now(), - value: 5.4, - title: 'Light breakfast', - ), - GlucoseEvent( - eventType: GlucoseEventType.insulin, - dateTime: DateTime.now(), - value: 3.1, - title: 'Light breakfast', - ), - GlucoseEvent( - eventType: GlucoseEventType.sport, - dateTime: DateTime.now(), - value: 2.6, - title: 'Light breakfast', - ), - GlucoseEvent( - eventType: GlucoseEventType.food, - dateTime: DateTime.now(), - value: 5.4, - title: 'Light breakfast', - ), - GlucoseEvent( - eventType: GlucoseEventType.sport, - dateTime: DateTime.now(), - value: 5.4, - title: 'Light breakfast', - ), -]; diff --git a/mobile_app/lib/presentation/home/cubit/home_cubit.dart b/mobile_app/lib/presentation/home/cubit/home_cubit.dart index b84aeda..7892439 100644 --- a/mobile_app/lib/presentation/home/cubit/home_cubit.dart +++ b/mobile_app/lib/presentation/home/cubit/home_cubit.dart @@ -21,7 +21,7 @@ void startCallback() { class HomeCubit extends Cubit { ReceivePort? _receivePort; - StreamSubscription? _streamSubscription; + //StreamSubscription? _streamSubscription; final bleDeviceRepo = sl(); final sharedPrefs = sl(); final glucoseDatarepo = sl(); @@ -76,10 +76,7 @@ class HomeCubit extends Cubit { const NotificationButton(id: 'testButton', text: 'Test'), ], ), - iosNotificationOptions: const IOSNotificationOptions( - showNotification: true, - playSound: false, - ), + iosNotificationOptions: const IOSNotificationOptions(), foregroundTaskOptions: const ForegroundTaskOptions( isOnceEvent: true, autoRunOnBoot: true, diff --git a/mobile_app/lib/presentation/main/main_page.dart b/mobile_app/lib/presentation/main/main_page.dart index a382412..3875da5 100644 --- a/mobile_app/lib/presentation/main/main_page.dart +++ b/mobile_app/lib/presentation/main/main_page.dart @@ -7,11 +7,12 @@ import 'package:mobile_app/presentation/add_note/add_note_page.dart'; import 'package:mobile_app/presentation/add_note/cubit/add_note_cubit.dart'; import 'package:mobile_app/presentation/chart/cubit/chart_cubit.dart'; import 'package:mobile_app/presentation/chart/screen.dart'; +import 'package:mobile_app/presentation/history/cubit/history_cubit.dart'; import 'package:mobile_app/presentation/history/history_page.dart'; import 'package:mobile_app/presentation/home/cubit/home_cubit.dart'; import 'package:mobile_app/presentation/home/home_page.dart'; -import 'package:mobile_app/presentation/main/cubit/home_page_cubit.dart'; import 'package:mobile_app/presentation/settings/screen.dart'; +import 'package:mobile_app/presentation/tabs/home_tab.dart'; class MainPageWidget extends StatefulWidget { const MainPageWidget({super.key}); @@ -21,9 +22,11 @@ class MainPageWidget extends StatefulWidget { } class _MainPageWidgetState extends State { + final pageController = PageController(); int _selectedIndex = 0; void _onItemTapped(int index) { + pageController.jumpToPage(index); setState(() { _selectedIndex = index; }); @@ -43,14 +46,14 @@ class _MainPageWidgetState extends State { @override Widget build(BuildContext context) { final pages = [ + const HomeTab(), BlocProvider( - create: (context) => HomeCubit(), - child: const HomePageWidget(), + create: (context) => HistoryCubit(), + child: const HistoryPage(), ), - const HistoryPage(), BlocProvider( create: (context) => AddNoteCubit(), - child: const AddNotePage(), + child: AddNotePage(), ), BlocProvider( create: (context) => ChartCubit(), @@ -69,9 +72,12 @@ class _MainPageWidgetState extends State { ), body: Padding( padding: const EdgeInsets.all(16), - child: IndexedStack( - index: _selectedIndex, + child: PageView( + physics: const NeverScrollableScrollPhysics(), + clipBehavior: Clip.none, + controller: pageController, children: pages, + onPageChanged: (page) => setState(() => _selectedIndex = page), ), ), bottomNavigationBar: BottomNavigationBar( diff --git a/mobile_app/lib/presentation/tabs/home_tab.dart b/mobile_app/lib/presentation/tabs/home_tab.dart new file mode 100644 index 0000000..248b28b --- /dev/null +++ b/mobile_app/lib/presentation/tabs/home_tab.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_app/presentation/home/cubit/home_cubit.dart'; +import 'package:mobile_app/presentation/home/home_page.dart'; + +class HomeTab extends StatefulWidget { + const HomeTab({super.key}); + + @override + State createState() => _HomeTabState(); +} + +class _HomeTabState extends State with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return BlocProvider( + create: (context) => HomeCubit(), + child: const HomePageWidget(), + ); + } + + @override + bool get wantKeepAlive => true; +} From 10a69c96145362611a8da930a4529629e9afffa7 Mon Sep 17 00:00:00 2001 From: Max Size Date: Wed, 12 Jun 2024 01:02:48 +0300 Subject: [PATCH 18/18] final fixes --- mobile_app/.gitignore | 1 + .../lib/data/data_source/local/database.dart | 3 - mobile_app/lib/main.dart | 1 + .../presentation/chart/widgets/date_card.dart | 3 +- .../presentation/home/cubit/home_cubit.dart | 31 ++- .../presentation/home/cubit/home_state.dart | 25 +- .../lib/presentation/home/home_page.dart | 14 +- .../lib/presentation/settings/screen.dart | 6 +- mobile_app/pubspec.lock | 251 +++++++++++++++++- 9 files changed, 306 insertions(+), 29 deletions(-) diff --git a/mobile_app/.gitignore b/mobile_app/.gitignore index a214f8e..af77a49 100644 --- a/mobile_app/.gitignore +++ b/mobile_app/.gitignore @@ -1,4 +1,5 @@ # Miscellaneous +*.lock *.class *.log *.pyc diff --git a/mobile_app/lib/data/data_source/local/database.dart b/mobile_app/lib/data/data_source/local/database.dart index c8965ae..a7b6138 100644 --- a/mobile_app/lib/data/data_source/local/database.dart +++ b/mobile_app/lib/data/data_source/local/database.dart @@ -6,8 +6,6 @@ import 'package:mobile_app/data/data_source/local/tables/glucose_events.dart'; import 'package:mobile_app/data/data_source/local/tables/measurements_table.dart'; import 'package:mobile_app/data/models/db/glucose_event.dart'; import 'package:mobile_app/data/models/db/measurement.dart'; -// import 'package:mobile_app/data/models/db/glucose_event.dart'; -// import 'package:mobile_app/data/models/db/measurement.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqlite3/sqlite3.dart'; @@ -23,7 +21,6 @@ class AppDatabase extends _$AppDatabase { int get schemaVersion => 2; @override - // TODO: implement migration MigrationStrategy get migration { return MigrationStrategy( onCreate: (m) async { diff --git a/mobile_app/lib/main.dart b/mobile_app/lib/main.dart index 8ed5f6e..0d9ac97 100644 --- a/mobile_app/lib/main.dart +++ b/mobile_app/lib/main.dart @@ -19,6 +19,7 @@ class MyApp extends StatelessWidget { child: BlocBuilder( builder: (context, state) { return MaterialApp( + debugShowCheckedModeBanner: false, theme: state.brightness == Brightness.light ? lightTheme : darkTheme, initialRoute: Routes.splash, diff --git a/mobile_app/lib/presentation/chart/widgets/date_card.dart b/mobile_app/lib/presentation/chart/widgets/date_card.dart index f27a9cf..253b047 100644 --- a/mobile_app/lib/presentation/chart/widgets/date_card.dart +++ b/mobile_app/lib/presentation/chart/widgets/date_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mobile_app/config/colors.dart'; +import 'package:mobile_app/config/theme.dart'; import 'package:mobile_app/presentation/chart/widgets/date_column.dart'; enum DateCardStatus { @@ -37,7 +38,7 @@ class DateCard extends StatelessWidget { return status == DateCardStatus.picked ? DecoratedBox( decoration: BoxDecoration( - color: green5, + color: Theme.of(context).isLight() ? green5 : green30, border: Border.all(color: green50), borderRadius: BorderRadius.circular(4), ), diff --git a/mobile_app/lib/presentation/home/cubit/home_cubit.dart b/mobile_app/lib/presentation/home/cubit/home_cubit.dart index 7892439..562b987 100644 --- a/mobile_app/lib/presentation/home/cubit/home_cubit.dart +++ b/mobile_app/lib/presentation/home/cubit/home_cubit.dart @@ -24,7 +24,7 @@ class HomeCubit extends Cubit { //StreamSubscription? _streamSubscription; final bleDeviceRepo = sl(); final sharedPrefs = sl(); - final glucoseDatarepo = sl(); + final glucoseDataRepo = sl(); HomeCubit() : super(HomeLoading()) { _initListener(); _initForegroundService(); @@ -41,7 +41,6 @@ class HomeCubit extends Cubit { } } - void _initListener() { bleDeviceRepo.isConnected.listen((isConnected) { switch (isConnected) { @@ -55,7 +54,6 @@ class HomeCubit extends Cubit { }); } - void _initForegroundTask() { FlutterForegroundTask.init( androidNotificationOptions: AndroidNotificationOptions( @@ -132,9 +130,30 @@ class HomeCubit extends Cubit { closeReceivePort(); _receivePort = newReceivePort; - _receivePort?.listen((data) { - if (data is double && !isClosed) - emit(DeviceConnected(currentGlucoseValue: data as double)); + _receivePort?.listen((data) async { + if (data is double && !isClosed) { + final res = await glucoseDataRepo.getGlucoseDataForDay(); + res.fold((l) => null, (r) { + var minValue = 6.0; + var maxValue = 0.0; + var valuesSum = 0.0; + for (final measurement in r) { + if (measurement.value < minValue) minValue = measurement.value; + if (measurement.value > maxValue) maxValue = measurement.value; + valuesSum += measurement.value; + } + + final averageValue = valuesSum / r.length; + final variabilityValue = maxValue - minValue; + emit(DeviceConnected( + currentGlucoseValue: data, + averageValue: averageValue, + variabilityValue: variabilityValue, + minValue: minValue, + maxValue: maxValue, + )); + }); + } }); return _receivePort != null; diff --git a/mobile_app/lib/presentation/home/cubit/home_state.dart b/mobile_app/lib/presentation/home/cubit/home_state.dart index 29819bf..b8901d2 100644 --- a/mobile_app/lib/presentation/home/cubit/home_state.dart +++ b/mobile_app/lib/presentation/home/cubit/home_state.dart @@ -6,12 +6,33 @@ sealed class HomeState {} final class HomeLoading extends HomeState {} final class DeviceConnected extends HomeState { + final double minValue; + final double maxValue; + final double averageValue; + final double variabilityValue; final double currentGlucoseValue; - DeviceConnected({required this.currentGlucoseValue}); + DeviceConnected({ + required this.minValue, + required this.maxValue, + required this.averageValue, + required this.variabilityValue, + required this.currentGlucoseValue, + }); - DeviceConnected copyWith({double? currentGlucoseValue}) => DeviceConnected( + DeviceConnected copyWith({ + double? currentGlucoseValue, + double? averageValue, + double? variabilityValue, + double? minValue, + double? maxValue, + }) => + DeviceConnected( currentGlucoseValue: currentGlucoseValue ?? this.currentGlucoseValue, + minValue: minValue ?? this.minValue, + maxValue: maxValue ?? this.maxValue, + averageValue: averageValue ?? this.averageValue, + variabilityValue: variabilityValue ?? this.variabilityValue, ); } diff --git a/mobile_app/lib/presentation/home/home_page.dart b/mobile_app/lib/presentation/home/home_page.dart index acc1f85..eb41b3f 100644 --- a/mobile_app/lib/presentation/home/home_page.dart +++ b/mobile_app/lib/presentation/home/home_page.dart @@ -52,35 +52,35 @@ class HomePageWidget extends StatelessWidget { const SizedBox( height: 20, ), - const Row( + Row( children: [ Expanded( child: InfoCard( iconPath: arrowUpSvg, title: 'Highest', - value: 5.7, + value: state.maxValue, ), ), Expanded( child: InfoCard( iconPath: arrowDownSvg, title: 'Lowest', - value: 5.7, + value: state.minValue, ), ), ], ), - const InfoCard( + InfoCard( iconPath: middle, title: 'Average', - value: 5.7, + value: state.averageValue, ), GestureDetector( onTap:() => context.read().closeReceivePort(), - child: const InfoCard( + child: InfoCard( iconPath: variability, title: 'Variability', - value: 5.2, + value: state.variabilityValue, ), ), ], diff --git a/mobile_app/lib/presentation/settings/screen.dart b/mobile_app/lib/presentation/settings/screen.dart index 965ac07..5e6a964 100644 --- a/mobile_app/lib/presentation/settings/screen.dart +++ b/mobile_app/lib/presentation/settings/screen.dart @@ -45,9 +45,9 @@ class SettingsScreen extends StatelessWidget { title: 'Dark', value: ThemeMode.dark, ), - TestTextWidget( - text: state.brightness.index.toString(), - ), + // TestTextWidget( + // text: state.brightness.index.toString(), + // ), Text( 'Connect bluetooth device', style: Theme.of(context).isLight() diff --git a/mobile_app/pubspec.lock b/mobile_app/pubspec.lock index c92ce6d..800abe3 100644 --- a/mobile_app/pubspec.lock +++ b/mobile_app/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.4.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev" + source: hosted + version: "0.11.3" args: dependency: transitive description: @@ -121,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" checked_yaml: dependency: transitive description: @@ -129,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -201,6 +225,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.1" + drift: + dependency: "direct main" + description: + name: drift + sha256: "6acedc562ffeed308049f78fb1906abad3d65714580b6745441ee6d50ec564cd" + url: "https://pub.dev" + source: hosted + version: "2.18.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: d9b020736ea85fff1568699ce18b89fabb3f0f042e8a7a05e84a3ec20d39acde + url: "https://pub.dev" + source: hosted + version: "2.18.0" equatable: dependency: transitive description: @@ -217,6 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" file: dependency: transitive description: @@ -254,6 +302,22 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" + flutter_blue_plus: + dependency: "direct main" + description: + name: flutter_blue_plus + sha256: abb08b5a7e44202290ff3dfc8fbb33fde7d4a82c39b20f3803a7f14ce2420aa7 + url: "https://pub.dev" + source: hosted + version: "1.32.5" + flutter_foreground_task: + dependency: "direct main" + description: + name: flutter_foreground_task + sha256: d40a1ddd5f275450d2e32055e7f884796c028a02ac26c751c20916576f79e132 + url: "https://pub.dev" + source: hosted + version: "6.2.0" flutter_lints: dependency: "direct dev" description: @@ -275,6 +339,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -311,10 +380,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -460,7 +529,7 @@ packages: source: hosted version: "2.1.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -475,6 +544,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + url: "https://pub.dev" + source: hosted + version: "2.2.4" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.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: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" petitparser: dependency: transitive description: @@ -483,6 +600,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + 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: @@ -515,6 +648,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" retrofit: dependency: "direct main" description: @@ -531,6 +672,62 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.8" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" shelf: dependency: transitive description: @@ -576,6 +773,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: fb2a106a2ea6042fe57de2c47074cc31539a941819c91e105b864744605da3f5 + url: "https://pub.dev" + source: hosted + version: "0.5.21" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: ade9a67fd70d0369329ed3373208de7ebd8662470e8c396fc8d0d60f9acdfc9f + url: "https://pub.dev" + source: hosted + version: "0.36.0" stack_trace: dependency: transitive description: @@ -700,10 +921,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" web_socket_channel: dependency: transitive description: @@ -712,6 +933,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + url: "https://pub.dev" + source: hosted + version: "5.5.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: @@ -729,5 +966,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.3 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0"