From 5ef745b4032c9ba5568505dc7592ed3f07862cc3 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sat, 4 Nov 2023 18:19:21 +0200 Subject: [PATCH 01/39] project setup --- .gitignore | 44 ++ .metadata | 33 + analysis_options.yaml | 28 + android/.gitignore | 13 + android/app/build.gradle | 67 ++ android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 33 + .../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 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle | 31 + android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle | 20 + ios/.gitignore | 34 + ios/Flutter/AppFrameworkInfo.plist | 26 + ios/Flutter/Debug.xcconfig | 1 + 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 + 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 + ios/Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ ios/Runner/Base.lproj/Main.storyboard | 26 + ios/Runner/Info.plist | 49 ++ ios/Runner/Runner-Bridging-Header.h | 1 + ios/RunnerTests/RunnerTests.swift | 12 + lib/main.dart | 125 ++++ pubspec.lock | 188 ++++++ pubspec.yaml | 90 +++ test/widget_test.dart | 30 + 65 files changed, 1898 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/com/example/shopping_assistant_mobile_client/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 ios/.gitignore create mode 100644 ios/Flutter/AppFrameworkInfo.plist create mode 100644 ios/Flutter/Debug.xcconfig create mode 100644 ios/Flutter/Release.xcconfig create mode 100644 ios/Runner.xcodeproj/project.pbxproj create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 ios/Runner/Base.lproj/Main.storyboard create mode 100644 ios/Runner/Info.plist create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 ios/RunnerTests/RunnerTests.swift create mode 100644 lib/main.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/widget_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# 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 +.packages +.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/.metadata b/.metadata new file mode 100644 index 0000000..93f66d9 --- /dev/null +++ b/.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: "6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e + base_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e + - platform: android + create_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e + base_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e + - platform: ios + create_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e + base_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e + + # 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/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/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/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/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/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..3cbfab0 --- /dev/null +++ b/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.shopping_assistant_mobile_client" + 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.shopping_assistant_mobile_client" + // 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/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7fc7dc0 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/shopping_assistant_mobile_client/MainActivity.kt b/android/app/src/main/kotlin/com/example/shopping_assistant_mobile_client/MainActivity.kt new file mode 100644 index 0000000..dcbfd29 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/shopping_assistant_mobile_client/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.shopping_assistant_mobile_client + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/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/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/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/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/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/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/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/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + 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/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/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/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..55c4ca8 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,20 @@ +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") + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +include ":app" + +apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/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/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/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/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..121a088 --- /dev/null +++ b/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.shoppingAssistantMobileClient; + 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.shoppingAssistantMobileClient.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.shoppingAssistantMobileClient.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.shoppingAssistantMobileClient.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.shoppingAssistantMobileClient; + 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.shoppingAssistantMobileClient; + 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/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/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/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/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/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/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/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/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/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/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/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/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/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/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/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..3e23e9c --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Shopping Assistant Mobile Client + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + shopping_assistant_mobile_client + 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/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/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/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..dda5554 --- /dev/null +++ b/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 blue 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/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..0b808da --- /dev/null +++ b/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: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + 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: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + 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: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + 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: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + 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: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" +sdks: + dart: ">=3.1.4 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..40fa083 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,90 @@ +name: shopping_assistant_mobile_client +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.1.4 <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/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..7aca564 --- /dev/null +++ b/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:shopping_assistant_mobile_client/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 dd742882fd312b1431d5710b6bb7927e1fc547e6 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 12 Nov 2023 10:01:21 +0200 Subject: [PATCH 02/39] add authentication service --- lib/constants/jwt_claims.dart | 10 + lib/models/global_instances/global_user.dart | 10 + lib/network/authentication_service.dart | 170 ++++++++ pubspec.lock | 390 +++++++++++++++++++ pubspec.yaml | 6 + 5 files changed, 586 insertions(+) create mode 100644 lib/constants/jwt_claims.dart create mode 100644 lib/models/global_instances/global_user.dart create mode 100644 lib/network/authentication_service.dart diff --git a/lib/constants/jwt_claims.dart b/lib/constants/jwt_claims.dart new file mode 100644 index 0000000..935d647 --- /dev/null +++ b/lib/constants/jwt_claims.dart @@ -0,0 +1,10 @@ +class JwtClaims { + + static const String id = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'; + + static const String email = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'; + + static const String phone = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone'; + + static const String roles ='http://schemas.microsoft.com/ws/2008/06/identity/claims/role'; +} diff --git a/lib/models/global_instances/global_user.dart b/lib/models/global_instances/global_user.dart new file mode 100644 index 0000000..06168a2 --- /dev/null +++ b/lib/models/global_instances/global_user.dart @@ -0,0 +1,10 @@ +class GlobalUser { + + static String? id; + + static String? email; + + static String? phone; + + static List? roles; +} diff --git a/lib/network/authentication_service.dart b/lib/network/authentication_service.dart new file mode 100644 index 0000000..be65417 --- /dev/null +++ b/lib/network/authentication_service.dart @@ -0,0 +1,170 @@ +import 'package:graphql/client.dart'; +import 'package:jwt_decoder/jwt_decoder.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:shopping_assistant_mobile_client/constants/jwt_claims.dart'; +import 'package:uuid/uuid.dart'; + +class AuthenticationService { + final GraphQLClient client = GraphQLClient( + cache: GraphQLCache(), + link: HttpLink('https://shopping-assistant-api-dev.azurewebsites.net/graphql'), + ); + + late SharedPreferences prefs; + + AuthenticationService() { + SharedPreferences.getInstance().then((result) => {prefs = result}); + } + + Future getAccessToken() async { + var accessToken = prefs.getString('accessToken'); + var refreshToken = prefs.getString('refreshToken'); + + if (accessToken == null && refreshToken != null) { + print('WTF??'); + } else if (accessToken == null && refreshToken == null) { + accessToken = await accessGuest(); + print('Got new access token $accessToken'); + } else if (JwtDecoder.isExpired(accessToken!)) { + accessToken = await refreshAccessToken(); + print('Refreshed access token $accessToken'); + } + + print('Returned access token $accessToken'); + return accessToken!; + } + + Future login(String? email, String? phone, String password) async { + const String loginQuery = r''' + mutation Login($login: AccessUserModelInput!) { + login(login: $login) { + accessToken + refreshToken + } + } + '''; + + final MutationOptions options = MutationOptions( + document: gql(loginQuery), + variables: { + 'login': { + 'email': email, + 'phone': phone, + 'password': password, + }, + }, + ); + + final QueryResult result = await client.mutate(options); + + if (result.hasException) { + print(result.exception.toString()); + } + + final accessToken = result.data?['login']['accessToken'] as String; + final refreshToken = result.data?['login']['refreshToken'] as String; + + prefs.setString('accessToken', accessToken); + prefs.setString('refreshToken', refreshToken); + } + + Future accessGuest() async { + String? guestId = prefs.getString('guestId'); + guestId ??= const Uuid().v4(); + prefs.setString('guestId', guestId); + + const String accessGuestMutation = r''' + mutation AccessGuest($guest: AccessGuestModelInput!) { + accessGuest(guest: $guest) { + accessToken + refreshToken + } + } + '''; + + final MutationOptions options = MutationOptions( + document: gql(accessGuestMutation), + variables: { + 'guest': {'guestId': guestId}, + }, + ); + + final QueryResult result = await client.mutate(options); + + if (result.hasException) { + print(result.exception.toString()); + } + + final String accessToken = + result.data?['accessGuest']['accessToken'] as String; + final String refreshToken = + result.data?['accessGuest']['refreshToken'] as String; + + prefs.setString('accessToken', accessToken); + prefs.setString('refreshToken', refreshToken); + + return accessToken; + } + + Future refreshAccessToken() async { + var accessToken = prefs.getString('accessToken'); + var refreshToken = prefs.getString('refreshToken'); + + const String refreshAccessTokenMutation = r''' + mutation RefreshAccessToken($model: TokensModelInput!) { + refreshAccessToken(model: $model) { + accessToken + refreshToken + } + } + '''; + + final QueryOptions options = QueryOptions( + document: gql(refreshAccessTokenMutation), + variables: { + 'model': { + 'accessToken': accessToken, + 'refreshToken': refreshToken, + }, + }, + ); + + final QueryResult result = await client.query(options); + + if (result.hasException) { + print(result.exception.toString()); + } + + accessToken = result.data?['refreshAccessToken']['accessToken'] as String; + refreshToken = result.data?['refreshAccessToken']['refreshToken'] as String; + + prefs.setString('accessToken', accessToken); + prefs.setString('refreshToken', refreshToken); + + return accessToken; + } + + String getIdFromAccessToken(String accessToken) { + var decodedToken = JwtDecoder.decode(accessToken); + return decodedToken[JwtClaims.id]; + } + + String getEmailFromAccessToken(String accessToken) { + var decodedToken = JwtDecoder.decode(accessToken); + return decodedToken[JwtClaims.email]; + } + + String getPhoneFromAccessToken(String accessToken) { + var decodedToken = JwtDecoder.decode(accessToken); + return decodedToken[JwtClaims.phone]; + } + + // List getRolesFromAccessToken(String accessToken) { + // var decodedToken = JwtDecoder.decode(accessToken); + // List roles = []; + // for (var role in decodedToken[JwtClaims.roles] as List) { + // roles.add(role); + // } + // return roles; + // } +} diff --git a/pubspec.lock b/pubspec.lock index 0b808da..41b9539 100644 --- a/pubspec.lock +++ b/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: @@ -41,6 +49,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.2" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f + url: "https://pub.dev" + source: hosted + version: "5.0.1" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + url: "https://pub.dev" + source: hosted + version: "1.2.4" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -49,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dbus: + dependency: transitive + description: + name: dbus + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" + source: hosted + version: "0.7.8" fake_async: dependency: transitive description: @@ -57,11 +97,35 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_hooks: + dependency: transitive + description: + name: flutter_hooks + sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" + url: "https://pub.dev" + source: hosted + version: "0.20.3" flutter_lints: dependency: "direct dev" description: @@ -75,6 +139,123 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + gql: + dependency: transitive + description: + name: gql + sha256: e5225e3be4d7eb4027406ab07cb68ad3a089deb3f7f6dc46edbdec78f2e5549f + url: "https://pub.dev" + source: hosted + version: "1.0.1-alpha+1696717343881" + gql_dedupe_link: + dependency: transitive + description: + name: gql_dedupe_link + sha256: "79625bc8029755ce6b26483adf0255c6b6114acc56e7ef81469a99f1ce2296db" + url: "https://pub.dev" + source: hosted + version: "2.0.4-alpha+1696717344020" + gql_error_link: + dependency: transitive + description: + name: gql_error_link + sha256: bfdb543137da89448cc5d003fd029c2e8718931d39d4a7dedb16f9169862fbb9 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + gql_exec: + dependency: transitive + description: + name: gql_exec + sha256: da419a3ebaae7672ed662c42d754ffba996347af7fe0ca031f1dd699334994d8 + url: "https://pub.dev" + source: hosted + version: "1.0.1-alpha+1696717343896" + gql_http_link: + dependency: transitive + description: + name: gql_http_link + sha256: "0789d397d46ce274942fcc73e18a080cd2584296dadc33d8ae53d0666d7fe981" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + gql_link: + dependency: transitive + description: + name: gql_link + sha256: bcbb09ae8b200f413aa2d21fbf6ce4c4ac1ac443e81c612f29ef1587f4c84122 + url: "https://pub.dev" + source: hosted + version: "1.0.1-alpha+1696717343909" + gql_transform_link: + dependency: transitive + description: + name: gql_transform_link + sha256: "0645fdd874ca1be695fd327271fdfb24c0cd6fa40774a64b946062f321a59709" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + graphql: + dependency: "direct main" + description: + name: graphql + sha256: "4ac531068107dffef188c74e7ff662777b729e9d5e0686f71623d4af1e3751c8" + url: "https://pub.dev" + source: hosted + version: "5.2.0-beta.6" + graphql_flutter: + dependency: "direct main" + description: + name: graphql_flutter + sha256: "39b5e830bc654ab02c5b776c31675841d1a8c95840fdd284efba713b1d47e65d" + url: "https://pub.dev" + source: hosted + version: "5.2.0-beta.6" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + jwt_decoder: + dependency: "direct main" + description: + name: jwt_decoder + sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f" + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -107,6 +288,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + normalize: + dependency: transitive + description: + name: normalize + sha256: "8a60e37de5b608eeaf9b839273370c71ebba445e9f73b08eee7725e0d92dbc43" + url: "https://pub.dev" + source: hosted + version: "0.8.2+1" path: dependency: transitive description: @@ -115,6 +312,142 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + 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: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + 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: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + platform: + dependency: transitive + description: + name: platform + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + url: "https://pub.dev" + source: hosted + version: "2.3.4" + 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: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + url: "https://pub.dev" + source: hosted + version: "2.3.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" sky_engine: dependency: transitive description: flutter @@ -168,6 +501,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -184,5 +533,46 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4-beta" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + xml: + dependency: transitive + description: + name: xml + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.1.4 <4.0.0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 40fa083..0840073 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + http: ^1.1.0 + jwt_decoder: ^2.0.1 + graphql: ^5.2.0-beta.6 + shared_preferences: ^2.2.2 + uuid: ^3.0.7 + graphql_flutter: ^5.2.0-beta.6 dev_dependencies: flutter_test: From bec49c8fa2b8ed6c13b84297804d9edfde251b15 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 12 Nov 2023 10:03:13 +0200 Subject: [PATCH 03/39] add api_client including GraphQL query and mutation functionality including SSE streaming --- lib/main.dart | 35 ++++++++- lib/models/enums/search_event_type.dart | 6 ++ lib/models/server_sent_event.dart | 9 +++ lib/network/api_client.dart | 98 +++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 lib/models/enums/search_event_type.dart create mode 100644 lib/models/server_sent_event.dart create mode 100644 lib/network/api_client.dart diff --git a/lib/main.dart b/lib/main.dart index dda5554..1deb065 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,7 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:graphql/client.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; void main() { runApp(const MyApp()); @@ -57,7 +60,8 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { int _counter = 0; - void _incrementCounter() { + var client = ApiClient(); + Future _incrementCounter() async { 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 @@ -66,6 +70,35 @@ class _MyHomePageState extends State { // called again, and so nothing would appear to happen. _counter++; }); + + const String startPersonalWishlistMutations = r''' + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist(dto: $dto) { + createdById, id, name, type + } + } + '''; + + MutationOptions mutationOptions = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: const { + 'dto': { + 'firstMessageText': 'Gaming mechanical keyboard', + 'type': 'Product' + }, + } + ); + + var result = await client.mutate(mutationOptions); + print(jsonEncode(result)); + + var wishlistId = result?['startPersonalWishlist']['id']; + var sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': 'silent wireless mouse'}); + await for (var chunk in sseStream) { + print('${chunk.event}: ${chunk.data}'); + } } @override diff --git a/lib/models/enums/search_event_type.dart b/lib/models/enums/search_event_type.dart new file mode 100644 index 0000000..3634744 --- /dev/null +++ b/lib/models/enums/search_event_type.dart @@ -0,0 +1,6 @@ +enum SearchEventType { + wishlist, + message, + suggestion, + product +} diff --git a/lib/models/server_sent_event.dart b/lib/models/server_sent_event.dart new file mode 100644 index 0000000..48c35ea --- /dev/null +++ b/lib/models/server_sent_event.dart @@ -0,0 +1,9 @@ +import 'package:shopping_assistant_mobile_client/models/enums/search_event_type.dart'; + +class ServerSentEvent { + + SearchEventType event; + String data; + + ServerSentEvent(this.event, this.data); +} diff --git a/lib/network/api_client.dart b/lib/network/api_client.dart new file mode 100644 index 0000000..87c0633 --- /dev/null +++ b/lib/network/api_client.dart @@ -0,0 +1,98 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:graphql/client.dart'; +import 'package:http/http.dart' as http; +import 'package:shopping_assistant_mobile_client/models/enums/search_event_type.dart'; +import 'package:shopping_assistant_mobile_client/models/global_instances/global_user.dart'; +import 'package:shopping_assistant_mobile_client/models/server_sent_event.dart'; +import 'package:shopping_assistant_mobile_client/network/authentication_service.dart'; + +class ApiClient { + final String _apiBaseUrl = 'https://shopping-assistant-api-dev.azurewebsites.net/'; + late String _accessToken; + + final AuthenticationService _authenticationService = AuthenticationService(); + + late GraphQLClient _graphqlClient; + final http.Client _httpClient = http.Client(); + + Future?> query(QueryOptions options) async { + await _setAuthentication(); + + final QueryResult result = await _graphqlClient.query(options); + + if (result.hasException) { + print(result.exception.toString()); + } + + return result.data; + } + + Future?> mutate(MutationOptions options) async { + await _setAuthentication(); + + final QueryResult result = await _graphqlClient.mutate(options); + + if (result.hasException) { + print(result.exception.toString()); + } + + return result.data; + } + + Stream getServerSentEventStream(String urlPath, Map requestBody) async* { + await _setAuthentication(); + + final url = Uri.parse('$_apiBaseUrl$urlPath'); + final request = http.Request('POST', url); + request.body = jsonEncode(requestBody); + request.headers.addAll({ + HttpHeaders.authorizationHeader: 'Bearer $_accessToken', + HttpHeaders.contentTypeHeader: 'application/json' + }); + final response = await _httpClient.send(request); + + var eventType = SearchEventType.message; + await for (var line in response.stream.transform(utf8.decoder).transform(const LineSplitter())) { + if (line.startsWith('event: ')) { + var type = line.substring('event: '.length); + switch (type) { + case 'Message': + eventType = SearchEventType.message; + break; + case 'Suggestion': + eventType = SearchEventType.suggestion; + break; + case 'Product': + eventType = SearchEventType.product; + break; + case 'Wishlist': + eventType = SearchEventType.wishlist; + break; + } + } + if (line.startsWith('data: ')) { + yield ServerSentEvent(eventType, line.substring('data: '.length)); + } + } + } + + Future _setAuthentication() async { + _accessToken = await _authenticationService.getAccessToken(); + + GlobalUser.id = _authenticationService.getIdFromAccessToken(_accessToken); + GlobalUser.email = _authenticationService.getEmailFromAccessToken(_accessToken); + GlobalUser.phone = _authenticationService.getPhoneFromAccessToken(_accessToken); + // GlobalUser.roles = _authenticationService.getRolesFromAccessToken(_accessToken); + + final httpLink = HttpLink('${_apiBaseUrl}graphql/', defaultHeaders: { + HttpHeaders.authorizationHeader: 'Bearer $_accessToken' + }); + + _graphqlClient = GraphQLClient( + cache: GraphQLCache(), + link: httpLink + ); + } +} From 225a1b8f6efddcc51a8193ab5b505961c021a198 Mon Sep 17 00:00:00 2001 From: shchoholiev-opflo Date: Sun, 12 Nov 2023 16:59:37 -0500 Subject: [PATCH 04/39] SA-147 Added iOS config --- .gitignore | 46 ++++++ ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Podfile | 44 ++++++ ios/Podfile.lock | 43 ++++++ ios/Runner.xcodeproj/project.pbxproj | 141 ++++++++++++++++-- .../contents.xcworkspacedata | 3 + 7 files changed, 264 insertions(+), 15 deletions(-) create mode 100644 ios/Podfile create mode 100644 ios/Podfile.lock diff --git a/.gitignore b/.gitignore index 24476c5..1601b79 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,49 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/settings.json \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..4871635 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '15.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..4823387 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,43 @@ +PODS: + - connectivity_plus (0.0.1): + - Flutter + - ReachabilitySwift + - Flutter (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - ReachabilitySwift (5.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + +SPEC REPOS: + trunk: + - ReachabilitySwift + +EXTERNAL SOURCES: + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + Flutter: + :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + +SPEC CHECKSUMS: + connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + +PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76 + +COCOAPODS: 1.12.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 121a088..3df735e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,12 +8,14 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 56E0353746ED15182053ECD3 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A806F7CD09DA976CCAD3863 /* Pods_RunnerTests.framework */; }; 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 */; }; + B682E45354CC4BAB12178794 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE92EE02B491BF6AD5FC98CD /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,9 +42,17 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 029954CD834F78829E47247A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 0A4CEAE23127674AF3AB53BE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 0F8DC980862C7B0DDAB88DA6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 127883CDBB2FC7B8C43C2347 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4A806F7CD09DA976CCAD3863 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6962B5FE101B87DBED54C956 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; 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 = ""; }; @@ -53,21 +63,47 @@ 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; }; + 9D611B8839E482A01080BA68 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + FE92EE02B491BF6AD5FC98CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 45D1A63F50B76A664F26E800 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 56E0353746ED15182053ECD3 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B682E45354CC4BAB12178794 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 86B87DA936C01407FF532B09 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FE92EE02B491BF6AD5FC98CD /* Pods_Runner.framework */, + 4A806F7CD09DA976CCAD3863 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -79,14 +115,6 @@ name = Flutter; sourceTree = ""; }; - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( @@ -94,6 +122,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + FADF67E5D56A5B9DFE574E7A /* Pods */, + 86B87DA936C01407FF532B09 /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +151,20 @@ path = Runner; sourceTree = ""; }; + FADF67E5D56A5B9DFE574E7A /* Pods */ = { + isa = PBXGroup; + children = ( + 127883CDBB2FC7B8C43C2347 /* Pods-Runner.debug.xcconfig */, + 0A4CEAE23127674AF3AB53BE /* Pods-Runner.release.xcconfig */, + 0F8DC980862C7B0DDAB88DA6 /* Pods-Runner.profile.xcconfig */, + 029954CD834F78829E47247A /* Pods-RunnerTests.debug.xcconfig */, + 9D611B8839E482A01080BA68 /* Pods-RunnerTests.release.xcconfig */, + 6962B5FE101B87DBED54C956 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,9 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 7D306219987D786916FB999D /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, - 331C807E294A63A400263BE5 /* Frameworks */, 331C807F294A63A400263BE5 /* Resources */, + 45D1A63F50B76A664F26E800 /* Frameworks */, ); buildRules = ( ); @@ -146,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + CA1CCCB9B316BC3A80D34A0D /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + E18ECFF3C9B5CAA9E2C1059F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -239,6 +286,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 7D306219987D786916FB999D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -254,6 +323,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + CA1CCCB9B316BC3A80D34A0D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E18ECFF3C9B5CAA9E2C1059F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -361,6 +469,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 64FG98G3D5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -377,7 +486,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 029954CD834F78829E47247A /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,7 +504,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 9D611B8839E482A01080BA68 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,7 +520,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 6962B5FE101B87DBED54C956 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -539,6 +648,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 64FG98G3D5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -561,6 +671,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 64FG98G3D5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + From 08a66e829f33bab396b35a750faeef05e4d5437b Mon Sep 17 00:00:00 2001 From: stasex Date: Tue, 21 Nov 2023 17:40:14 +0200 Subject: [PATCH 05/39] create screen for chat --- lib/main.dart | 20 ++-- lib/screens/chat.dart | 253 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 lib/screens/chat.dart diff --git a/lib/main.dart b/lib/main.dart index 1deb065..8add828 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,10 +2,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:graphql/client.dart'; import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'package:shopping_assistant_mobile_client/screens/chat.dart'; void main() { runApp(const MyApp()); } +final ApiClient client = ApiClient(); class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -34,7 +36,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: ChatScreen(), ); } } @@ -80,13 +82,13 @@ class _MyHomePageState extends State { '''; MutationOptions mutationOptions = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: const { - 'dto': { - 'firstMessageText': 'Gaming mechanical keyboard', - 'type': 'Product' - }, - } + document: gql(startPersonalWishlistMutations), + variables: const { + 'dto': { + 'firstMessageText': 'Gaming mechanical keyboard', + 'type': 'Product' + }, + } ); var result = await client.mutate(mutationOptions); @@ -155,4 +157,4 @@ class _MyHomePageState extends State { ), // This trailing comma makes auto-formatting nicer for build methods. ); } -} +} \ No newline at end of file diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart new file mode 100644 index 0000000..595d208 --- /dev/null +++ b/lib/screens/chat.dart @@ -0,0 +1,253 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +const String startPersonalWishlistMutations = r''' + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist(dto: $dto) { + createdById, id, name, type + } + } +'''; + +const String sendMessageMutation = r''' + mutation sendMessage($wishlistId: ID!, $message: String!) { + sendMessage(wishlistId: $wishlistId, message: $message) { + // Опис того, що ви очікуєте від відповіді + } + } +'''; + +final ApiClient client = ApiClient(); + +class ChatScreen extends StatefulWidget { + @override + State createState() => ChatScreenState(); +} + +class MessageBubble extends StatelessWidget { + final String message; + + MessageBubble({required this.message}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: Container( + margin: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(10.0), + ), + child: Text( + message, + style: TextStyle(color: Colors.white), + ), + ), + ); + } +} + +class ChatScreenState extends State { + + final TextEditingController _messageController = TextEditingController(); + List messages = []; + bool buttonsVisible = true; + final ScrollController _scrollController = ScrollController(); + + + String wishlistId = ''; + + // Функція для старту першої вішлісту при відправці першого повідомлення + Future _startPersonalWishlist() async { + final options = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: { + 'dto': {'firstMessageText': messages.first, 'type': 'Product'}, + }, + ); + + final result = await client.mutate(options); + + if (result != null && result.containsKey('startPersonalWishlist')) { + setState(() { + wishlistId = result['startPersonalWishlist']['id']; + }); + } + } + + // Функція для відправки повідомлення до API + Future _sendMessageToAPI(String message) async { + final options = MutationOptions( + document: gql(sendMessageMutation), + variables: {'wishlistId': wishlistId, 'message': message}, + ); + + final result = await client.mutate(options); + + // Обробка результатів відправки повідомлення + if (result != null && result.containsKey('sendMessage')) { + // Отримання та обробка відповідей з GPT-4 + var sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': message}, + ); + + await for (var chunk in sseStream) { + print('${chunk.event}: ${chunk.data}'); + // Оновлення UI або збереження результатів, якщо необхідно + } + } + } + + // Функція для відправки повідомлення + void _sendMessage() { + String message = _messageController.text; + setState(() { + messages.insert(0, message); + }); + + if (wishlistId.isEmpty) { + // Якщо вішліст не створено, стартуємо його + _startPersonalWishlist().then((_) { + // Після створення вішлісту, відправляємо перше повідомлення до API + _sendMessageToAPI(message); + }); + } else { + // Якщо вішліст вже існує, відправляємо повідомлення до API + _sendMessageToAPI(message); + } + + _messageController.clear(); + _scrollController.animateTo( + 0.0, + duration: Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + + + void _showGiftNotAvailable() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Gift Functionality'), + content: Text('This function is currently unavailable.'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('New Chat'), + centerTitle: true, // Відцентрувати заголовок + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + // Обробник для кнопки "Назад" + print('Back button pressed'); + }, + ), + ), + body: Column( + children: [ + Visibility( + visible: buttonsVisible, + child: Align( + alignment: Alignment.topCenter, + child: Column( + children: [ + Text( + 'Choose an Option', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + // Обробник для кнопки "Product" + print('Product button pressed'); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), // Закруглення країв + ), + primary: Colors.blue, // Колір кнопки + onPrimary: Colors.white, // Колір тексту на активній кнопці + ), + child: Text('Product'), + ), + SizedBox(width: 16.0), // Простір між кнопками + ElevatedButton( + onPressed: _showGiftNotAvailable, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), // Закруглення країв + ), + primary: Colors.white, // Колір кнопки "Gift" + onPrimary: Colors.black, // Колір тексту на активній кнопці + ), + child: Text('Gift'), + ), + ], + ), + ], + ), + ), + ), + Expanded( + child: ListView( + controller: _scrollController, + reverse: true, // Щоб список був у зворотньому порядку + children: [ + // Повідомлення користувача + for (var message in messages) + MessageBubble( + message: message, + ), + ], + ), + ), + Container( + margin: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + decoration: InputDecoration( + hintText: 'Enter your message...', + ), + ), + ), + IconButton( + icon: Icon(Icons.send), + onPressed: _sendMessage, + ), + ], + ), + ), + ], + ), + ); + } + } \ No newline at end of file From 0393a70f88bf78415acc0273794f4738cdd2b878 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 23 Nov 2023 13:51:10 +0200 Subject: [PATCH 06/39] create new logic for chat --- lib/network/search_service.dart | 49 ++++++++++ lib/screens/chat.dart | 168 ++++++++++++-------------------- 2 files changed, 111 insertions(+), 106 deletions(-) create mode 100644 lib/network/search_service.dart diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart new file mode 100644 index 0000000..b692f17 --- /dev/null +++ b/lib/network/search_service.dart @@ -0,0 +1,49 @@ +// search_service.dart + +import 'package:graphql_flutter/graphql_flutter.dart'; +import '../network/api_client.dart'; +import '../network/authentication_service.dart'; +import '../screens/chat.dart'; + +const String startPersonalWishlistMutations = r''' + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist(dto: $dto) { + createdById, id, name, type + } + } +'''; + +class SearchService { + final AuthenticationService _authenticationService = AuthenticationService(); + final ApiClient client = ApiClient(); + + Future initializeAuthenticationService() async { + await _authenticationService.initialize(); + } + + Future startPersonalWishlist(String message, Function(Message) handleSSEMessage) async { + await _authenticationService.initialize(); + + final options = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: { + 'dto': {'firstMessageText': message, 'type': 'Product'}, + }, + ); + + final result = await client.mutate(options); + + if (result != null && result.containsKey('startPersonalWishlist')) { + final wishlistId = result['startPersonalWishlist']['id']; + final sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': message}, + ); + + await for (final chunk in sseStream) { + print('${chunk.event}: ${chunk.data}'); + handleSSEMessage(Message(text: '${chunk.event}: ${chunk.data}')); + } + } + } +} diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 595d208..865463c 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,135 +1,92 @@ -import 'dart:convert'; +// search_screen.dart import 'package:flutter/material.dart'; -import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'package:shopping_assistant_mobile_client/network/search_service.dart'; -const String startPersonalWishlistMutations = r''' - mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { - startPersonalWishlist(dto: $dto) { - createdById, id, name, type - } - } -'''; +class Message { + final String text; + final bool isUser; -const String sendMessageMutation = r''' - mutation sendMessage($wishlistId: ID!, $message: String!) { - sendMessage(wishlistId: $wishlistId, message: $message) { - // Опис того, що ви очікуєте від відповіді - } - } -'''; - -final ApiClient client = ApiClient(); - -class ChatScreen extends StatefulWidget { - @override - State createState() => ChatScreenState(); + Message({required this.text, this.isUser = false}); } class MessageBubble extends StatelessWidget { final String message; + final bool isOutgoing; - MessageBubble({required this.message}); + MessageBubble({required this.message, this.isOutgoing = true}); @override Widget build(BuildContext context) { return Align( - alignment: Alignment.centerRight, + alignment: isOutgoing ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: Colors.blue, + color: isOutgoing ? Colors.blue : Colors.white, borderRadius: BorderRadius.circular(10.0), ), child: Text( message, - style: TextStyle(color: Colors.white), + style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), ), ), ); } } -class ChatScreenState extends State { +class ChatScreen extends StatefulWidget { + @override + State createState() => ChatScreenState(); +} +class ChatScreenState extends State { + final SearchService _searchService = SearchService(); + List messages = []; final TextEditingController _messageController = TextEditingController(); - List messages = []; bool buttonsVisible = true; final ScrollController _scrollController = ScrollController(); - String wishlistId = ''; - // Функція для старту першої вішлісту при відправці першого повідомлення - Future _startPersonalWishlist() async { - final options = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: { - 'dto': {'firstMessageText': messages.first, 'type': 'Product'}, - }, - ); - - final result = await client.mutate(options); - - if (result != null && result.containsKey('startPersonalWishlist')) { - setState(() { - wishlistId = result['startPersonalWishlist']['id']; - }); - } - } - - // Функція для відправки повідомлення до API - Future _sendMessageToAPI(String message) async { - final options = MutationOptions( - document: gql(sendMessageMutation), - variables: {'wishlistId': wishlistId, 'message': message}, - ); - - final result = await client.mutate(options); - - // Обробка результатів відправки повідомлення - if (result != null && result.containsKey('sendMessage')) { - // Отримання та обробка відповідей з GPT-4 - var sseStream = client.getServerSentEventStream( - 'api/productssearch/search/$wishlistId', - {'text': message}, - ); - - await for (var chunk in sseStream) { - print('${chunk.event}: ${chunk.data}'); - // Оновлення UI або збереження результатів, якщо необхідно - } - } - } - - // Функція для відправки повідомлення - void _sendMessage() { - String message = _messageController.text; + void _handleSSEMessage(Message message) { setState(() { - messages.insert(0, message); + messages.add(message); + }); + } + + Future _startPersonalWishlist(String message) async { + await _searchService.initializeAuthenticationService(); + await _searchService.startPersonalWishlist(message, _handleSSEMessage); + } + + Future _sendMessageToAPI(String message) async { + await _searchService.startPersonalWishlist(message, _handleSSEMessage); + + setState(() { + messages.add(Message(text: message, isUser: true)); + }); + } + + void _sendMessage() { + final message = _messageController.text; + setState(() { + messages.add(Message(text: message, isUser: true)); }); if (wishlistId.isEmpty) { - // Якщо вішліст не створено, стартуємо його - _startPersonalWishlist().then((_) { - // Після створення вішлісту, відправляємо перше повідомлення до API - _sendMessageToAPI(message); - }); + _startPersonalWishlist(message); } else { - // Якщо вішліст вже існує, відправляємо повідомлення до API _sendMessageToAPI(message); } _messageController.clear(); _scrollController.animateTo( - 0.0, + _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, - ); - } - + );} void _showGiftNotAvailable() { showDialog( @@ -156,11 +113,10 @@ class ChatScreenState extends State { return Scaffold( appBar: AppBar( title: Text('New Chat'), - centerTitle: true, // Відцентрувати заголовок + centerTitle: true, leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - // Обробник для кнопки "Назад" print('Back button pressed'); }, ), @@ -182,29 +138,28 @@ class ChatScreenState extends State { children: [ ElevatedButton( onPressed: () { - // Обробник для кнопки "Product" print('Product button pressed'); }, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), // Закруглення країв + borderRadius: BorderRadius.circular(10), ), - primary: Colors.blue, // Колір кнопки - onPrimary: Colors.white, // Колір тексту на активній кнопці + primary: Colors.blue, + onPrimary: Colors.white, ), child: Text('Product'), ), - SizedBox(width: 16.0), // Простір між кнопками + SizedBox(width: 16.0), ElevatedButton( onPressed: _showGiftNotAvailable, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), // Закруглення країв + borderRadius: BorderRadius.circular(10), ), - primary: Colors.white, // Колір кнопки "Gift" - onPrimary: Colors.black, // Колір тексту на активній кнопці + primary: Colors.white, + onPrimary: Colors.black, ), child: Text('Gift'), ), @@ -215,16 +170,17 @@ class ChatScreenState extends State { ), ), Expanded( - child: ListView( + child: ListView.builder( controller: _scrollController, - reverse: true, // Щоб список був у зворотньому порядку - children: [ - // Повідомлення користувача - for (var message in messages) - MessageBubble( - message: message, - ), - ], + reverse: false, + itemCount: messages.length, + itemBuilder: (context, index) { + final message = messages[index]; + return MessageBubble( + message: message.text, + isOutgoing: message.isUser, + ); + }, ), ), Container( @@ -250,4 +206,4 @@ class ChatScreenState extends State { ), ); } - } \ No newline at end of file +} \ No newline at end of file From 7b3963fad7a8c9899bbddc745fd1b9677a3e7cb9 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 23 Nov 2023 17:17:39 +0200 Subject: [PATCH 07/39] add normal messages --- lib/network/search_service.dart | 20 +++++++++++++++++--- lib/screens/chat.dart | 14 +++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index b692f17..e3b0320 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -1,6 +1,10 @@ // search_service.dart +import 'dart:async'; + import 'package:graphql_flutter/graphql_flutter.dart'; +import '../models/enums/search_event_type.dart'; +import '../models/server_sent_event.dart'; import '../network/api_client.dart'; import '../network/authentication_service.dart'; import '../screens/chat.dart'; @@ -17,11 +21,15 @@ class SearchService { final AuthenticationService _authenticationService = AuthenticationService(); final ApiClient client = ApiClient(); + final _sseController = StreamController(); + + Stream get sseStream => _sseController.stream; + Future initializeAuthenticationService() async { await _authenticationService.initialize(); } - Future startPersonalWishlist(String message, Function(Message) handleSSEMessage) async { + Future startPersonalWishlist(String message) async { await _authenticationService.initialize(); final options = MutationOptions( @@ -40,10 +48,16 @@ class SearchService { {'text': message}, ); + StringBuffer fullMessage = StringBuffer(); // Використовуємо StringBuffer для зберігання повідомлення + await for (final chunk in sseStream) { - print('${chunk.event}: ${chunk.data}'); - handleSSEMessage(Message(text: '${chunk.event}: ${chunk.data}')); + fullMessage.write(chunk.data); // Додаємо чанк до повідомлення } + + final cleanedMessage = fullMessage.toString().replaceAll('"', ''); + + final event = ServerSentEvent(SearchEventType.message, cleanedMessage.toString().trim()); + _sseController.add(event); } } } diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 865463c..f75790a 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -24,7 +24,7 @@ class MessageBubble extends StatelessWidget { margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: isOutgoing ? Colors.blue : Colors.white, + color: isOutgoing ? Colors.blue : Colors.white38, borderRadius: BorderRadius.circular(10.0), ), child: Text( @@ -50,6 +50,14 @@ class ChatScreenState extends State { String wishlistId = ''; + @override + void initState() { + super.initState(); + _searchService.sseStream.listen((event) { + _handleSSEMessage(Message(text: '${event.event}: ${event.data}')); + }); + } + void _handleSSEMessage(Message message) { setState(() { messages.add(message); @@ -58,11 +66,11 @@ class ChatScreenState extends State { Future _startPersonalWishlist(String message) async { await _searchService.initializeAuthenticationService(); - await _searchService.startPersonalWishlist(message, _handleSSEMessage); + await _searchService.startPersonalWishlist(message); } Future _sendMessageToAPI(String message) async { - await _searchService.startPersonalWishlist(message, _handleSSEMessage); + await _searchService.startPersonalWishlist(message); setState(() { messages.add(Message(text: message, isUser: true)); From 06ae71960ba7d4a175b6247e511d465d4f714f68 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 23 Nov 2023 22:25:35 +0200 Subject: [PATCH 08/39] add logic for stream --- lib/network/search_service.dart | 55 ++++++++----- lib/screens/chat.dart | 137 +++++++++++++++++++++++++++++--- 2 files changed, 161 insertions(+), 31 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index e3b0320..3856656 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -17,6 +17,8 @@ const String startPersonalWishlistMutations = r''' } '''; +SearchEventType type = SearchEventType.message; + class SearchService { final AuthenticationService _authenticationService = AuthenticationService(); final ApiClient client = ApiClient(); @@ -29,35 +31,52 @@ class SearchService { await _authenticationService.initialize(); } + bool checkerForProduct() { + return type == SearchEventType.product; + } + + String? wishlistId; + Future startPersonalWishlist(String message) async { await _authenticationService.initialize(); - final options = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: { - 'dto': {'firstMessageText': message, 'type': 'Product'}, - }, - ); + // Перевіряємо, чи вже створений wishlist + if (wishlistId == null) { + final options = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: { + 'dto': {'firstMessageText': message, 'type': 'Product'}, + }, + ); - final result = await client.mutate(options); + final result = await client.mutate(options); - if (result != null && result.containsKey('startPersonalWishlist')) { - final wishlistId = result['startPersonalWishlist']['id']; + if (result != null && result.containsKey('startPersonalWishlist')) { + wishlistId = result['startPersonalWishlist']['id']; + } + } + + if (wishlistId != null) { final sseStream = client.getServerSentEventStream( 'api/productssearch/search/$wishlistId', {'text': message}, ); - StringBuffer fullMessage = StringBuffer(); // Використовуємо StringBuffer для зберігання повідомлення - await for (final chunk in sseStream) { - fullMessage.write(chunk.data); // Додаємо чанк до повідомлення + print("Original chunk.data: ${chunk.event}"); + final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), ''); + if(chunk.event == SearchEventType.message) + { + type = SearchEventType.message; + } + if(chunk.event == SearchEventType.product) + { + type = SearchEventType.product; + } + + final event = ServerSentEvent(type, cleanedMessage); + _sseController.add(event); } - - final cleanedMessage = fullMessage.toString().replaceAll('"', ''); - - final event = ServerSentEvent(SearchEventType.message, cleanedMessage.toString().trim()); - _sseController.add(event); } } -} +} \ No newline at end of file diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index f75790a..d021f6c 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -6,15 +6,17 @@ import 'package:shopping_assistant_mobile_client/network/search_service.dart'; class Message { final String text; final bool isUser; + bool isProduct; - Message({required this.text, this.isUser = false}); + Message({required this.text, this.isUser = false, this.isProduct = false}); } class MessageBubble extends StatelessWidget { final String message; final bool isOutgoing; + final bool isProduct; - MessageBubble({required this.message, this.isOutgoing = true}); + MessageBubble({required this.message, this.isOutgoing = true, this.isProduct = false}); @override Widget build(BuildContext context) { @@ -23,13 +25,34 @@ class MessageBubble extends StatelessWidget { child: Container( margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), + constraints: BoxConstraints( + maxWidth: 300.0, // Максимальна ширина контейнера + ), decoration: BoxDecoration( - color: isOutgoing ? Colors.blue : Colors.white38, + color: isOutgoing ? Colors.blue : Colors.grey[200], borderRadius: BorderRadius.circular(10.0), ), - child: Text( - message, - style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + message, + style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), + ), + if (isProduct) // Виводимо кнопку тільки для повідомлень типу Product + ElevatedButton( + onPressed: () { + // Обробка натискання на кнопку "View Product" + print('View Product button pressed'); + }, + style: ElevatedButton.styleFrom( + primary: Colors.indigo, + onPrimary: Colors.white, + minimumSize: Size(300, 50) + ), + child: Text('View Product'), + ), + ], ), ), ); @@ -46,31 +69,55 @@ class ChatScreenState extends State { List messages = []; final TextEditingController _messageController = TextEditingController(); bool buttonsVisible = true; + bool isSendButtonEnabled = false; + bool showButtonsContainer = true; final ScrollController _scrollController = ScrollController(); String wishlistId = ''; - @override void initState() { super.initState(); _searchService.sseStream.listen((event) { - _handleSSEMessage(Message(text: '${event.event}: ${event.data}')); + _handleSSEMessage(Message(text: '${event.data}')); }); } void _handleSSEMessage(Message message) { setState(() { - messages.add(message); + final lastMessage = messages.isNotEmpty ? messages.last : null; + message.isProduct = _searchService.checkerForProduct(); + print("Product status: ${message.isProduct}"); + if (lastMessage != null && !lastMessage.isUser && !message.isUser) { + final updatedMessage = Message( + text: "${lastMessage.text}${message.text}", + isProduct: message.isProduct); + messages.removeLast(); + messages.add(updatedMessage); + } else { + messages.add(message); + } }); + _scrollToBottom(); } + Future _startPersonalWishlist(String message) async { + setState(() { + buttonsVisible = false; + showButtonsContainer = false; + }); await _searchService.initializeAuthenticationService(); await _searchService.startPersonalWishlist(message); + _scrollToBottom(); } Future _sendMessageToAPI(String message) async { + setState(() { + buttonsVisible = false; + showButtonsContainer = false; + }); await _searchService.startPersonalWishlist(message); + _scrollToBottom(); setState(() { messages.add(Message(text: message, isUser: true)); @@ -90,11 +137,16 @@ class ChatScreenState extends State { } _messageController.clear(); + _scrollToBottom(); + } + + void _scrollToBottom() { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, - );} + ); + } void _showGiftNotAvailable() { showDialog( @@ -149,7 +201,8 @@ class ChatScreenState extends State { print('Product button pressed'); }, style: ElevatedButton.styleFrom( - padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + padding: EdgeInsets.symmetric( + horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -162,7 +215,8 @@ class ChatScreenState extends State { ElevatedButton( onPressed: _showGiftNotAvailable, style: ElevatedButton.styleFrom( - padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + padding: EdgeInsets.symmetric( + horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -177,6 +231,56 @@ class ChatScreenState extends State { ), ), ), + SizedBox(height: 16.0), // Відступ вниз + Visibility( + visible: showButtonsContainer, + child: Container( + margin: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () { + _messageController.text = 'Christmas gift🎁'; + _sendMessage(); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + primary: Colors.white, + onPrimary: Colors.blue, + side: BorderSide(color: Colors.blue, width: 2.0), + ), + child: Text('Christmas gift🎁', style: TextStyle(color: Colors.grey)), + ), + ), + Container( + margin: EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () { + _messageController.text = 'Birthday gift🎉'; + _sendMessage(); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + primary: Colors.white, + onPrimary: Colors.blue, + side: BorderSide(color: Colors.blue, width: 2.0), + ), + child: Text('Birthday gift🎉', style: TextStyle(color: Colors.grey)), + ), + ), + ], + ), + ), + ), Expanded( child: ListView.builder( controller: _scrollController, @@ -187,6 +291,7 @@ class ChatScreenState extends State { return MessageBubble( message: message.text, isOutgoing: message.isUser, + isProduct: message.isProduct, ); }, ), @@ -198,6 +303,12 @@ class ChatScreenState extends State { Expanded( child: TextField( controller: _messageController, + onChanged: (text) { + // Коли текст змінюється, оновлюємо стан кнопки + setState(() { + isSendButtonEnabled = text.isNotEmpty; + }); + }, decoration: InputDecoration( hintText: 'Enter your message...', ), @@ -205,7 +316,7 @@ class ChatScreenState extends State { ), IconButton( icon: Icon(Icons.send), - onPressed: _sendMessage, + onPressed: isSendButtonEnabled ? _sendMessage : null, ), ], ), From 4754a2b07c93bbe79a6fa846d74cd72f2fff6a56 Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 24 Nov 2023 00:16:34 +0200 Subject: [PATCH 09/39] fig for name changer --- lib/network/search_service.dart | 23 +++++++++++++++++++++++ lib/screens/chat.dart | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 3856656..71a7e25 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -37,6 +37,29 @@ class SearchService { String? wishlistId; + Future generateNameForPersonalWishlist(String wishlistId) async { + final options = MutationOptions( + document: gql(''' + mutation GenerateNameForPersonalWishlist(\$wishlistId: String!) { + generateNameForPersonalWishlist(wishlistId: \$wishlistId) { + id + name + } + } + '''), + variables: {'wishlistId': wishlistId}, + ); + + final result = await client.mutate(options); + + if (result != null && result.containsKey('generateNameForPersonalWishlist')) { + final name = result['generateNameForPersonalWishlist']['name']; + return name; + } + + return null; + } + Future startPersonalWishlist(String message) async { await _authenticationService.initialize(); diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index d021f6c..53f9afd 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -46,8 +46,8 @@ class MessageBubble extends StatelessWidget { print('View Product button pressed'); }, style: ElevatedButton.styleFrom( - primary: Colors.indigo, - onPrimary: Colors.white, + primary: Colors.indigo, + onPrimary: Colors.white, minimumSize: Size(300, 50) ), child: Text('View Product'), @@ -72,11 +72,13 @@ class ChatScreenState extends State { bool isSendButtonEnabled = false; bool showButtonsContainer = true; final ScrollController _scrollController = ScrollController(); + late Widget appBarTitle; String wishlistId = ''; void initState() { super.initState(); + appBarTitle = Text('New Chat'); _searchService.sseStream.listen((event) { _handleSSEMessage(Message(text: '${event.data}')); }); @@ -100,6 +102,15 @@ class ChatScreenState extends State { _scrollToBottom(); } + Future updateChatTitle(String wishlistId) async { + final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId); + if (wishlistName != null) { + setState(() { + // Оновіть назву чату з результатом методу generateNameForPersonalWishlist + appBarTitle = Text(wishlistName); + }); + } + } Future _startPersonalWishlist(String message) async { setState(() { @@ -108,6 +119,7 @@ class ChatScreenState extends State { }); await _searchService.initializeAuthenticationService(); await _searchService.startPersonalWishlist(message); + updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); } @@ -172,7 +184,7 @@ class ChatScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('New Chat'), + title: appBarTitle, centerTitle: true, leading: IconButton( icon: Icon(Icons.arrow_back), From 747909fe4237a744188b330d851bb4e49ef2f347 Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 24 Nov 2023 22:19:01 +0200 Subject: [PATCH 10/39] fixed message bugs and added the ability to view history --- lib/network/authentication_service.dart | 5 +- lib/network/search_service.dart | 89 +++++++++++++++++++----- lib/screens/chat.dart | 90 +++++++++++++++++++++---- pubspec.yaml | 2 +- 4 files changed, 151 insertions(+), 35 deletions(-) diff --git a/lib/network/authentication_service.dart b/lib/network/authentication_service.dart index be65417..e67aed7 100644 --- a/lib/network/authentication_service.dart +++ b/lib/network/authentication_service.dart @@ -12,11 +12,8 @@ class AuthenticationService { late SharedPreferences prefs; - AuthenticationService() { - SharedPreferences.getInstance().then((result) => {prefs = result}); - } - Future getAccessToken() async { + prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString('accessToken'); var refreshToken = prefs.getString('refreshToken'); diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 71a7e25..ab54b9a 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -1,13 +1,12 @@ // search_service.dart - import 'dart:async'; import 'package:graphql_flutter/graphql_flutter.dart'; import '../models/enums/search_event_type.dart'; import '../models/server_sent_event.dart'; import '../network/api_client.dart'; -import '../network/authentication_service.dart'; import '../screens/chat.dart'; +import 'authentication_service.dart'; const String startPersonalWishlistMutations = r''' mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { @@ -20,18 +19,17 @@ const String startPersonalWishlistMutations = r''' SearchEventType type = SearchEventType.message; class SearchService { - final AuthenticationService _authenticationService = AuthenticationService(); final ApiClient client = ApiClient(); - final _sseController = StreamController(); + late final _sseController = StreamController(); Stream get sseStream => _sseController.stream; - Future initializeAuthenticationService() async { - await _authenticationService.initialize(); + bool checkerForProduct() { + return type == SearchEventType.product; } - bool checkerForProduct() { + bool checkerForSuggestion() { return type == SearchEventType.product; } @@ -60,8 +58,7 @@ class SearchService { return null; } - Future startPersonalWishlist(String message) async { - await _authenticationService.initialize(); + Future startPersonalWishlist(String message) async { // Перевіряємо, чи вже створений wishlist if (wishlistId == null) { @@ -88,18 +85,76 @@ class SearchService { await for (final chunk in sseStream) { print("Original chunk.data: ${chunk.event}"); final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), ''); - if(chunk.event == SearchEventType.message) - { - type = SearchEventType.message; - } - if(chunk.event == SearchEventType.product) - { - type = SearchEventType.product; - } final event = ServerSentEvent(type, cleanedMessage); _sseController.add(event); } } + return wishlistId.toString(); + } + + Future sendMessages(String message) async { + + if (wishlistId != null) { + final sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': message}, + ); + + await for (final chunk in sseStream) { + print("Original chunk.data: ${chunk.event}"); + final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), ''); + + final event = ServerSentEvent(chunk.event, cleanedMessage); + type = chunk.event; + _sseController.add(event); + } + } + } + + Future> getMessagesFromPersonalWishlist(String wishlistIdPar, int pageNumber, int pageSize) async { + final options = QueryOptions( + document: gql(''' + query MessagesPageFromPersonalWishlist(\$wishlistId: String!, \$pageNumber: Int!, \$pageSize: Int!) { + messagesPageFromPersonalWishlist(wishlistId: \$wishlistId, pageNumber: \$pageNumber, pageSize: \$pageSize) { + items { + id + text + role + createdById + } + } + } + '''), + variables: { + 'wishlistId': wishlistIdPar, + 'pageNumber': pageNumber, + 'pageSize': pageSize, + }, + ); + + print("DOCUMENT: ${options.document}"); + + final result = await client.query(options); + + print("RESULT: ${result}"); + print(result); + if (result != null && + result.containsKey('messagesPageFromPersonalWishlist') && + result['messagesPageFromPersonalWishlist'] != null && + result['messagesPageFromPersonalWishlist']['items'] != null) { + final List items = result['messagesPageFromPersonalWishlist']['items']; + + final List messages = items.map((item) { + return Message( + text: item['text'], + role: item['role'], + isProduct: false, + ); + }).toList(); + + return messages; + } + return []; } } \ No newline at end of file diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 53f9afd..5955da0 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,14 +1,15 @@ // search_screen.dart - import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:shopping_assistant_mobile_client/network/search_service.dart'; class Message { final String text; - final bool isUser; + final String role; bool isProduct; + bool isSuggestion; - Message({required this.text, this.isUser = false, this.isProduct = false}); + Message({required this.text, this.role = "", this.isProduct = false, this.isSuggestion = false}); } class MessageBubble extends StatelessWidget { @@ -71,6 +72,7 @@ class ChatScreenState extends State { bool buttonsVisible = true; bool isSendButtonEnabled = false; bool showButtonsContainer = true; + bool isWaitingForResponse = false; final ScrollController _scrollController = ScrollController(); late Widget appBarTitle; @@ -82,16 +84,48 @@ class ChatScreenState extends State { _searchService.sseStream.listen((event) { _handleSSEMessage(Message(text: '${event.data}')); }); + Future.delayed(Duration(milliseconds: 2000)); + if(!wishlistId.isEmpty) + { + _loadPreviousMessages(); + showButtonsContainer = false; + buttonsVisible = false; + } + } + + Future _loadPreviousMessages() async { + final pageNumber = 1; + final pageSize = 200; + print('Previous Messages:'); + try { + final previousMessages = await _searchService.getMessagesFromPersonalWishlist("6560b4c210686c50ed4b9fec", pageNumber, pageSize); + final reversedMessages = previousMessages.reversed.toList(); + setState(() { + messages.addAll(reversedMessages); + }); + print('Previous Messages: $previousMessages'); + + for(final message in messages) + { + print("MESSAGES TEXT: ${message.text}"); + print("MESSAGES ROLE: ${message.role}"); + } + } catch (error) { + print('Error loading previous messages: $error'); + } } void _handleSSEMessage(Message message) { setState(() { + isWaitingForResponse = true; final lastMessage = messages.isNotEmpty ? messages.last : null; message.isProduct = _searchService.checkerForProduct(); + message.isSuggestion = _searchService.checkerForSuggestion(); print("Product status: ${message.isProduct}"); - if (lastMessage != null && !lastMessage.isUser && !message.isUser) { + if (lastMessage != null && lastMessage.role != "User" && message.role != "User") { final updatedMessage = Message( text: "${lastMessage.text}${message.text}", + role: "Application", isProduct: message.isProduct); messages.removeLast(); messages.add(updatedMessage); @@ -99,6 +133,9 @@ class ChatScreenState extends State { messages.add(message); } }); + setState(() { + isWaitingForResponse = false; + }); _scrollToBottom(); } @@ -106,7 +143,6 @@ class ChatScreenState extends State { final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId); if (wishlistName != null) { setState(() { - // Оновіть назву чату з результатом методу generateNameForPersonalWishlist appBarTitle = Text(wishlistName); }); } @@ -116,30 +152,35 @@ class ChatScreenState extends State { setState(() { buttonsVisible = false; showButtonsContainer = false; + isWaitingForResponse = true; }); - await _searchService.initializeAuthenticationService(); - await _searchService.startPersonalWishlist(message); + wishlistId = await _searchService.startPersonalWishlist(message); updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); + + setState(() { + isWaitingForResponse = false; + }); } - Future _sendMessageToAPI(String message) async { + Future _sendMessageToAPI(String message)async { setState(() { buttonsVisible = false; showButtonsContainer = false; + isWaitingForResponse = true; }); - await _searchService.startPersonalWishlist(message); + await _searchService.sendMessages(message); _scrollToBottom(); setState(() { - messages.add(Message(text: message, isUser: true)); + isWaitingForResponse = false; }); } void _sendMessage() { final message = _messageController.text; setState(() { - messages.add(Message(text: message, isUser: true)); + messages.add(Message(text: message, role: "User")); }); if (wishlistId.isEmpty) { @@ -302,12 +343,36 @@ class ChatScreenState extends State { final message = messages[index]; return MessageBubble( message: message.text, - isOutgoing: message.isUser, + isOutgoing: message.role == "User", isProduct: message.isProduct, ); }, ), ), + if (isWaitingForResponse) + SpinKitFadingCircle( + color: Colors.blue, + size: 25.0, + ), + if (messages.any((message) => message.isSuggestion)) + Container( + padding: EdgeInsets.all(8.0), + color: Colors.grey[300], + child: Row( + children: [ + Icon(Icons.lightbulb), + SizedBox(width: 8.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: messages + .where((message) => message.isSuggestion) + .map((message) => Text(message.text)) + .toList(), + ), + ], + ), + ), + // Поле введення повідомлень Container( margin: const EdgeInsets.all(8.0), child: Row( @@ -316,7 +381,6 @@ class ChatScreenState extends State { child: TextField( controller: _messageController, onChanged: (text) { - // Коли текст змінюється, оновлюємо стан кнопки setState(() { isSendButtonEnabled = text.isNotEmpty; }); diff --git a/pubspec.yaml b/pubspec.yaml index 0840073..e862f53 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,7 @@ environment: dependencies: flutter: sdk: flutter - + flutter_spinkit: ^5.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From 8540a01486b2a691b2aaaf7a6960d00e46c6c5d3 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sat, 25 Nov 2023 20:03:36 +0200 Subject: [PATCH 11/39] add wishlists screen --- assets/icons/cart.svg | 4 + assets/icons/settings.svg | 3 + assets/icons/start-new-search.svg | 3 + assets/icons/trash.svg | 3 + assets/icons/wishlists.svg | 4 + lib/main.dart | 256 ++++++++++----------- lib/models/wishlist.dart | 21 ++ lib/network/authentication_service.dart | 15 +- lib/screens/wishlists.dart | 284 ++++++++++++++++++++++++ pubspec.lock | 40 ++++ pubspec.yaml | 10 +- test/widget_test.dart | 58 ++--- 12 files changed, 521 insertions(+), 180 deletions(-) create mode 100644 assets/icons/cart.svg create mode 100644 assets/icons/settings.svg create mode 100644 assets/icons/start-new-search.svg create mode 100644 assets/icons/trash.svg create mode 100644 assets/icons/wishlists.svg create mode 100644 lib/models/wishlist.dart create mode 100644 lib/screens/wishlists.dart diff --git a/assets/icons/cart.svg b/assets/icons/cart.svg new file mode 100644 index 0000000..7b846df --- /dev/null +++ b/assets/icons/cart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/settings.svg b/assets/icons/settings.svg new file mode 100644 index 0000000..f6634dd --- /dev/null +++ b/assets/icons/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/start-new-search.svg b/assets/icons/start-new-search.svg new file mode 100644 index 0000000..4aad3c7 --- /dev/null +++ b/assets/icons/start-new-search.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/trash.svg b/assets/icons/trash.svg new file mode 100644 index 0000000..5726808 --- /dev/null +++ b/assets/icons/trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wishlists.svg b/assets/icons/wishlists.svg new file mode 100644 index 0000000..fb2c609 --- /dev/null +++ b/assets/icons/wishlists.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/main.dart b/lib/main.dart index 1deb065..8e6d29e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,158 +1,134 @@ -import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:graphql/client.dart'; -import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shopping_assistant_mobile_client/screens/wishlists.dart'; void main() { runApp(const MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); - // This widget is the root of your application. + static const List _pageNameOptions = [ + 'Wishlists', + 'New Chat', + 'Settings', + ]; + + static const List _widgetOptions = [ + WishlistsScreen(), + Text(''), + Text(''), + ]; + + static const Color _selectedColor = Color.fromRGBO(36, 36, 36, 1); + static const Color _unselectedColor = Color.fromRGBO(144, 144, 144, 1); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + int _selectedIndex = 0; + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + }); + } + @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 blue 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; - - var client = ApiClient(); - Future _incrementCounter() async { - 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++; - }); - - const String startPersonalWishlistMutations = r''' - mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { - startPersonalWishlist(dto: $dto) { - createdById, id, name, type - } - } - '''; - - MutationOptions mutationOptions = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: const { - 'dto': { - 'firstMessageText': 'Gaming mechanical keyboard', - 'type': 'Product' - }, - } - ); - - var result = await client.mutate(mutationOptions); - print(jsonEncode(result)); - - var wishlistId = result?['startPersonalWishlist']['id']; - var sseStream = client.getServerSentEventStream( - 'api/productssearch/search/$wishlistId', - {'text': 'silent wireless mouse'}); - await for (var chunk in sseStream) { - print('${chunk.event}: ${chunk.data}'); - } - } - - @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, - ), - ], + appBarTheme: AppBarTheme(), + textTheme: TextTheme( + bodyMedium: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + home: Scaffold( + appBar: AppBar( + title: Text(MyApp._pageNameOptions[_selectedIndex]), + centerTitle: true, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Container( + color: Color.fromRGBO(234, 234, 234, 1), + height: 1, + ), + ), + ), + body: MyApp._widgetOptions[_selectedIndex], + bottomNavigationBar: BottomNavigationBar( + items: [ + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/wishlists.svg', + color: _selectedIndex == 0 + ? MyApp._selectedColor + : MyApp._unselectedColor, + ), + label: 'Wishlists', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/start-new-search.svg', + color: _selectedIndex == 1 + ? MyApp._selectedColor + : MyApp._unselectedColor, + ), + label: 'New Chat', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/settings.svg', + color: _selectedIndex == 2 + ? MyApp._selectedColor + : MyApp._unselectedColor, + ), + label: 'Settings', + ), + ], + selectedItemColor: MyApp._selectedColor, + unselectedItemColor: MyApp._unselectedColor, + selectedFontSize: 14, + unselectedFontSize: 14, + currentIndex: _selectedIndex, + onTap: _onItemTapped, ), ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } + +// Use to seed wishlists for new user + +// const String startPersonalWishlistMutations = r''' +// mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { +// startPersonalWishlist(dto: $dto) { +// createdById, id, name, type +// } +// } +// '''; +// +// MutationOptions mutationOptions = MutationOptions( +// document: gql(startPersonalWishlistMutations), +// variables: const { +// 'dto': { +// 'firstMessageText': 'Gaming mechanical keyboard', +// 'type': 'Product' +// }, +// }); +// +// var client = ApiClient(); +// // for (var i = 0; i < 5; i++) { +// // client +// // .mutate(mutationOptions) +// // .then((result) => print(jsonEncode(result))); +// // sleep(Duration(milliseconds: 100)); +// // } +// diff --git a/lib/models/wishlist.dart b/lib/models/wishlist.dart new file mode 100644 index 0000000..c967bd5 --- /dev/null +++ b/lib/models/wishlist.dart @@ -0,0 +1,21 @@ +class Wishlist { + Wishlist( + {required this.id, + required this.name, + required this.type, + required this.createdById}); + + String id; + + String name; + + String type; + + String createdById; + + Wishlist.fromJson(Map json) + : id = json['id'] as String, + name = json['name'] as String, + type = json['type'] as String, + createdById = json['createdById'] as String; +} diff --git a/lib/network/authentication_service.dart b/lib/network/authentication_service.dart index be65417..e1c195c 100644 --- a/lib/network/authentication_service.dart +++ b/lib/network/authentication_service.dart @@ -12,21 +12,18 @@ class AuthenticationService { late SharedPreferences prefs; - AuthenticationService() { - SharedPreferences.getInstance().then((result) => {prefs = result}); - } - Future getAccessToken() async { + prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString('accessToken'); var refreshToken = prefs.getString('refreshToken'); if (accessToken == null && refreshToken != null) { print('WTF??'); } else if (accessToken == null && refreshToken == null) { - accessToken = await accessGuest(); + accessToken = await _accessGuest(); print('Got new access token $accessToken'); } else if (JwtDecoder.isExpired(accessToken!)) { - accessToken = await refreshAccessToken(); + accessToken = await _refreshAccessToken(); print('Refreshed access token $accessToken'); } @@ -35,6 +32,7 @@ class AuthenticationService { } Future login(String? email, String? phone, String password) async { + prefs = await SharedPreferences.getInstance(); const String loginQuery = r''' mutation Login($login: AccessUserModelInput!) { login(login: $login) { @@ -68,7 +66,8 @@ class AuthenticationService { prefs.setString('refreshToken', refreshToken); } - Future accessGuest() async { + Future _accessGuest() async { + prefs = await SharedPreferences.getInstance(); String? guestId = prefs.getString('guestId'); guestId ??= const Uuid().v4(); prefs.setString('guestId', guestId); @@ -106,7 +105,7 @@ class AuthenticationService { return accessToken; } - Future refreshAccessToken() async { + Future _refreshAccessToken() async { var accessToken = prefs.getString('accessToken'); var refreshToken = prefs.getString('refreshToken'); diff --git a/lib/screens/wishlists.dart b/lib/screens/wishlists.dart new file mode 100644 index 0000000..b8ed8d9 --- /dev/null +++ b/lib/screens/wishlists.dart @@ -0,0 +1,284 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:graphql/client.dart'; +import 'package:shopping_assistant_mobile_client/models/wishlist.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +class WishlistsScreen extends StatefulWidget { + const WishlistsScreen({super.key}); + + @override + State createState() => _WishlistsScreenState(); +} + +class _WishlistsScreenState extends State { + var client = ApiClient(); + + late Future _wishlistsFuture; + late List _wishlists; + + @override + void initState() { + super.initState(); + _wishlistsFuture = _fetchWishlistPage(); + } + + Future _fetchWishlistPage() async { + const String personalWishlistsPageQuery = r''' + query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { + personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { + items { + id, name, type, createdById, + }, + hasNextPage, hasPreviousPage, pageNumber, pageSize, totalItems, totalPages, + } + } + '''; + + QueryOptions queryOptions = QueryOptions( + document: gql(personalWishlistsPageQuery), + variables: const { + 'pageNumber': 1, + 'pageSize': 200, + }); + + var result = await client.query(queryOptions); + + _wishlists = List>.from( + result?['personalWishlistsPage']['items']) + .map((e) => Wishlist.fromJson(e)) + .toList(); + + return; + } + + void _deleteWishlist(Wishlist wishlist) async { + const String deletePersonalWishlistMutation = r''' + mutation deletePersonalWishlist($wishlistId: String!) { + deletePersonalWishlist(wishlistId: $wishlistId) { + } + } + '''; + + MutationOptions mutationOptions = MutationOptions( + document: gql(deletePersonalWishlistMutation), + variables: { + 'wishlistId': wishlist.id, + }); + + var result = await client.mutate(mutationOptions); + + setState(() { + _wishlists.remove(wishlist); + }); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _wishlistsFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text('Error: ${snapshot.error}'), + ); + } else if (snapshot.connectionState == ConnectionState.done) { + // Data loaded successfully, display the widget + return Container( + color: Colors.white, + child: _wishlists.length == 0 + ? Center( + child: Text('No wishlists found'), + ) + : ListView.builder( + padding: EdgeInsets.symmetric(vertical: 30), + itemCount: _wishlists.length, + itemBuilder: (context, index) { + return WishlistItem( + wishlist: _wishlists[index], + onDelete: () => _deleteWishlist(_wishlists[index]), + ); + }, + ), + ); + } + + return Center( + child: CircularProgressIndicator(), + ); + }, + ); + } +} + +class WishlistItem extends StatefulWidget { + WishlistItem({ + super.key, + required Wishlist wishlist, + required Function() onDelete, + }) : _wishlist = wishlist, + _onDelete = onDelete; + + final Wishlist _wishlist; + final Function() _onDelete; + + @override + State createState() => _WishlistItemState(); +} + +class _WishlistItemState extends State { + double _xOffset = 0; + double _rightBorderRadius = 10.0; + + bool _isDeleting = false; + + void _transformLeft() { + setState(() { + _xOffset = -70; + _rightBorderRadius = 0; + }); + } + + void _transformRight() { + setState(() { + _xOffset = 0; + _rightBorderRadius = 10; + }); + } + + void _onDelete() async { + setState(() { + _isDeleting = true; + }); + + await widget._onDelete(); + + setState(() { + _isDeleting = false; + }); + _transformRight(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 54, + margin: EdgeInsets.only( + bottom: 10, + left: 20, + right: 20, + ), + child: Stack( + children: [ + Positioned( + child: GestureDetector( + onTap: () => _onDelete(), + child: Container( + margin: EdgeInsets.symmetric( + horizontal: .25, + vertical: .25, + ), + decoration: BoxDecoration( + color: Color.fromRGBO(0, 82, 204, 1), + borderRadius: BorderRadius.circular(10), + ), + alignment: AlignmentDirectional.centerEnd, + child: _isDeleting + ? Container( + margin: EdgeInsets.only( + right: 25, + ), + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : Container( + padding: EdgeInsets.symmetric( + horizontal: 25, + vertical: 17, + ), + child: SvgPicture.asset( + 'assets/icons/trash.svg', + color: Colors.white, + width: 20, + ), + ), + ), + ), + ), + Positioned( + child: GestureDetector( + onTap: () => print(Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + Text('Chat ' + widget._wishlist.id)))), + onHorizontalDragUpdate: (DragUpdateDetails details) => { + if (details.delta.dx < -1) + {_transformLeft()} + else if (details.delta.dx > 1) + {_transformRight()} + }, + child: AnimatedContainer( + transform: Matrix4.translationValues(_xOffset, 0, 0), + duration: Duration(milliseconds: 250), + curve: Curves.easeInOut, + decoration: BoxDecoration( + color: Color.fromRGBO(234, 234, 234, 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), + topRight: Radius.circular(_rightBorderRadius), + bottomRight: Radius.circular(_rightBorderRadius), + ), + ), + alignment: AlignmentDirectional.centerStart, + padding: EdgeInsets.only( + left: 15, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + widget._wishlist.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + GestureDetector( + onTap: () => print(Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + Text('Cart ' + widget._wishlist.id)))), + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 17, + vertical: 17, + ), + child: SvgPicture.asset( + 'assets/icons/cart.svg', + color: Color.fromRGBO(32, 32, 32, 1), + width: 20, + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 41b9539..ccd3ceb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -134,6 +134,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + url: "https://pub.dev" + source: hosted + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter @@ -312,6 +320,14 @@ 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" path_provider: dependency: transitive description: @@ -517,6 +533,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + url: "https://pub.dev" + source: hosted + version: "1.1.9+1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0840073..358522f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: shared_preferences: ^2.2.2 uuid: ^3.0.7 graphql_flutter: ^5.2.0-beta.6 + flutter_svg: ^2.0.9 dev_dependencies: flutter_test: @@ -65,9 +66,12 @@ 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/icons/cart.svg + - assets/icons/trash.svg + - assets/icons/wishlists.svg + - assets/icons/start-new-search.svg + - assets/icons/settings.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/widget_test.dart b/test/widget_test.dart index 7aca564..3e97501 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,30 @@ -// This is a basic Flutter widget test. +// // 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. // -// 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:shopping_assistant_mobile_client/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); - }); -} +// import 'package:flutter/material.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// +// import 'package:shopping_assistant_mobile_client/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 8ff63054b70b54d5b41c3214012815c3bfc2058a Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 26 Nov 2023 16:23:31 +0200 Subject: [PATCH 12/39] added logger and fixed the sending of the first message --- lib/network/search_service.dart | 25 +++++------------------- lib/screens/chat.dart | 34 ++++++++++++++++++--------------- pubspec.yaml | 1 + 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index ab54b9a..2197852 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -1,12 +1,11 @@ // search_service.dart import 'dart:async'; - +import 'package:logger/logger.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import '../models/enums/search_event_type.dart'; import '../models/server_sent_event.dart'; import '../network/api_client.dart'; import '../screens/chat.dart'; -import 'authentication_service.dart'; const String startPersonalWishlistMutations = r''' mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { @@ -16,6 +15,8 @@ const String startPersonalWishlistMutations = r''' } '''; +var logger = Logger(); + SearchEventType type = SearchEventType.message; class SearchService { @@ -60,12 +61,11 @@ class SearchService { Future startPersonalWishlist(String message) async { - // Перевіряємо, чи вже створений wishlist if (wishlistId == null) { final options = MutationOptions( document: gql(startPersonalWishlistMutations), variables: { - 'dto': {'firstMessageText': message, 'type': 'Product'}, + 'dto': {'firstMessageText': "What are you looking for?", 'type': 'Product'}, }, ); @@ -75,21 +75,6 @@ class SearchService { wishlistId = result['startPersonalWishlist']['id']; } } - - if (wishlistId != null) { - final sseStream = client.getServerSentEventStream( - 'api/productssearch/search/$wishlistId', - {'text': message}, - ); - - await for (final chunk in sseStream) { - print("Original chunk.data: ${chunk.event}"); - final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), ''); - - final event = ServerSentEvent(type, cleanedMessage); - _sseController.add(event); - } - } return wishlistId.toString(); } @@ -133,7 +118,7 @@ class SearchService { }, ); - print("DOCUMENT: ${options.document}"); + logger.d("DOCUMENT: ${options.document}"); final result = await client.query(options); diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 5955da0..a12f0c0 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,6 +1,7 @@ // search_screen.dart import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:logger/logger.dart'; import 'package:shopping_assistant_mobile_client/network/search_service.dart'; class Message { @@ -27,7 +28,7 @@ class MessageBubble extends StatelessWidget { margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), constraints: BoxConstraints( - maxWidth: 300.0, // Максимальна ширина контейнера + maxWidth: 300.0, ), decoration: BoxDecoration( color: isOutgoing ? Colors.blue : Colors.grey[200], @@ -40,10 +41,9 @@ class MessageBubble extends StatelessWidget { message, style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), ), - if (isProduct) // Виводимо кнопку тільки для повідомлень типу Product + if (isProduct) ElevatedButton( onPressed: () { - // Обробка натискання на кнопку "View Product" print('View Product button pressed'); }, style: ElevatedButton.styleFrom( @@ -66,6 +66,7 @@ class ChatScreen extends StatefulWidget { } class ChatScreenState extends State { + var logger = Logger(); final SearchService _searchService = SearchService(); List messages = []; final TextEditingController _messageController = TextEditingController(); @@ -96,22 +97,21 @@ class ChatScreenState extends State { Future _loadPreviousMessages() async { final pageNumber = 1; final pageSize = 200; - print('Previous Messages:'); try { final previousMessages = await _searchService.getMessagesFromPersonalWishlist("6560b4c210686c50ed4b9fec", pageNumber, pageSize); final reversedMessages = previousMessages.reversed.toList(); setState(() { messages.addAll(reversedMessages); }); - print('Previous Messages: $previousMessages'); + logger.d('Previous Messages: $previousMessages'); for(final message in messages) { - print("MESSAGES TEXT: ${message.text}"); - print("MESSAGES ROLE: ${message.role}"); + logger.d("MESSAGES TEXT: ${message.text}"); + logger.d("MESSAGES ROLE: ${message.role}"); } } catch (error) { - print('Error loading previous messages: $error'); + logger.d('Error loading previous messages: $error'); } } @@ -121,7 +121,7 @@ class ChatScreenState extends State { final lastMessage = messages.isNotEmpty ? messages.last : null; message.isProduct = _searchService.checkerForProduct(); message.isSuggestion = _searchService.checkerForSuggestion(); - print("Product status: ${message.isProduct}"); + logger.d("Product status: ${message.isProduct}"); if (lastMessage != null && lastMessage.role != "User" && message.role != "User") { final updatedMessage = Message( text: "${lastMessage.text}${message.text}", @@ -155,7 +155,8 @@ class ChatScreenState extends State { isWaitingForResponse = true; }); wishlistId = await _searchService.startPersonalWishlist(message); - updateChatTitle(_searchService.wishlistId.toString()); + await _sendMessageToAPI(message); + await updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); setState(() { @@ -179,13 +180,17 @@ class ChatScreenState extends State { void _sendMessage() { final message = _messageController.text; - setState(() { - messages.add(Message(text: message, role: "User")); - }); if (wishlistId.isEmpty) { + setState(() { + messages.add(Message(text: "What are you looking for?", role: "Application")); + messages.add(Message(text: message, role: "User")); + }); _startPersonalWishlist(message); } else { + setState(() { + messages.add(Message(text: message, role: "User")); + }); _sendMessageToAPI(message); } @@ -284,7 +289,7 @@ class ChatScreenState extends State { ), ), ), - SizedBox(height: 16.0), // Відступ вниз + SizedBox(height: 16.0), Visibility( visible: showButtonsContainer, child: Container( @@ -372,7 +377,6 @@ class ChatScreenState extends State { ], ), ), - // Поле введення повідомлень Container( margin: const EdgeInsets.all(8.0), child: Row( diff --git a/pubspec.yaml b/pubspec.yaml index e862f53..041a786 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: flutter: sdk: flutter flutter_spinkit: ^5.0.0 + logger: ^2.0.2+1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From 27228f5452c558e95bcf70f779a3b50c3d23a779 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 30 Nov 2023 00:21:32 +0200 Subject: [PATCH 13/39] fixed some cosmetic bugs --- lib/screens/chat.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index a12f0c0..b933ccb 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -39,7 +39,9 @@ class MessageBubble extends StatelessWidget { children: [ Text( message, - style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), + style: TextStyle(color: isOutgoing ? Colors.white : Colors.black, + fontSize: 18.0 + ), ), if (isProduct) ElevatedButton( @@ -81,7 +83,7 @@ class ChatScreenState extends State { void initState() { super.initState(); - appBarTitle = Text('New Chat'); + appBarTitle = Text('New Chat', style: TextStyle(fontSize: 18.0)); _searchService.sseStream.listen((event) { _handleSSEMessage(Message(text: '${event.data}')); }); @@ -143,7 +145,7 @@ class ChatScreenState extends State { final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId); if (wishlistName != null) { setState(() { - appBarTitle = Text(wishlistName); + appBarTitle = Text(wishlistName, style: TextStyle(fontSize: 18.0)); }); } } @@ -248,7 +250,7 @@ class ChatScreenState extends State { child: Column( children: [ Text( - 'Choose an Option', + 'What are you looking for?', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), Row( @@ -391,6 +393,7 @@ class ChatScreenState extends State { }, decoration: InputDecoration( hintText: 'Enter your message...', + contentPadding: EdgeInsets.symmetric(vertical: 20.0) ), ), ), From e866bd5487a3186b724dbbacbad2d5e6e5cf2bce Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 8 Dec 2023 15:12:13 +0200 Subject: [PATCH 14/39] fixed a bug with the header and made some changes to display the history correctly --- lib/main.dart | 10 +++++++--- lib/screens/chat.dart | 16 ++++++++++------ lib/screens/wishlists.dart | 22 ++++++++++++++-------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f0ddc6a..1edea67 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shopping_assistant_mobile_client/screens/chat.dart'; import 'package:shopping_assistant_mobile_client/screens/wishlists.dart'; void main() { @@ -15,9 +16,9 @@ class MyApp extends StatefulWidget { 'Settings', ]; - static const List _widgetOptions = [ + static List _widgetOptions = [ WishlistsScreen(), - Text(''), + ChatScreen(wishlistId: '', wishlistName: 'New Chat',), Text(''), ]; @@ -37,6 +38,7 @@ class _MyAppState extends State { }); } + @override Widget build(BuildContext context) { return MaterialApp( @@ -51,7 +53,9 @@ class _MyAppState extends State { ), ), home: Scaffold( - appBar: AppBar( + appBar: _selectedIndex == 1 + ? null + : AppBar( title: Text(MyApp._pageNameOptions[_selectedIndex]), centerTitle: true, bottom: PreferredSize( diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index b933ccb..a506d5d 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -63,6 +63,11 @@ class MessageBubble extends StatelessWidget { } class ChatScreen extends StatefulWidget { + String wishlistId; + String wishlistName; + + ChatScreen({Key? key, required this.wishlistId, required this.wishlistName}) : super(key: key); + @override State createState() => ChatScreenState(); } @@ -79,8 +84,6 @@ class ChatScreenState extends State { final ScrollController _scrollController = ScrollController(); late Widget appBarTitle; - String wishlistId = ''; - void initState() { super.initState(); appBarTitle = Text('New Chat', style: TextStyle(fontSize: 18.0)); @@ -88,7 +91,7 @@ class ChatScreenState extends State { _handleSSEMessage(Message(text: '${event.data}')); }); Future.delayed(Duration(milliseconds: 2000)); - if(!wishlistId.isEmpty) + if(!widget.wishlistId.isEmpty) { _loadPreviousMessages(); showButtonsContainer = false; @@ -99,8 +102,9 @@ class ChatScreenState extends State { Future _loadPreviousMessages() async { final pageNumber = 1; final pageSize = 200; + appBarTitle = Text(widget.wishlistName, style: TextStyle(fontSize: 18.0)); try { - final previousMessages = await _searchService.getMessagesFromPersonalWishlist("6560b4c210686c50ed4b9fec", pageNumber, pageSize); + final previousMessages = await _searchService.getMessagesFromPersonalWishlist(widget.wishlistId, pageNumber, pageSize); final reversedMessages = previousMessages.reversed.toList(); setState(() { messages.addAll(reversedMessages); @@ -156,7 +160,7 @@ class ChatScreenState extends State { showButtonsContainer = false; isWaitingForResponse = true; }); - wishlistId = await _searchService.startPersonalWishlist(message); + widget.wishlistId = await _searchService.startPersonalWishlist(message); await _sendMessageToAPI(message); await updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); @@ -183,7 +187,7 @@ class ChatScreenState extends State { void _sendMessage() { final message = _messageController.text; - if (wishlistId.isEmpty) { + if (widget.wishlistId.isEmpty) { setState(() { messages.add(Message(text: "What are you looking for?", role: "Application")); messages.add(Message(text: message, role: "User")); diff --git a/lib/screens/wishlists.dart b/lib/screens/wishlists.dart index b8ed8d9..1e48c13 100644 --- a/lib/screens/wishlists.dart +++ b/lib/screens/wishlists.dart @@ -4,6 +4,8 @@ import 'package:graphql/client.dart'; import 'package:shopping_assistant_mobile_client/models/wishlist.dart'; import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'chat.dart'; + class WishlistsScreen extends StatefulWidget { const WishlistsScreen({super.key}); @@ -211,16 +213,20 @@ class _WishlistItemState extends State { ), Positioned( child: GestureDetector( - onTap: () => print(Navigator.push( + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => - Text('Chat ' + widget._wishlist.id)))), - onHorizontalDragUpdate: (DragUpdateDetails details) => { - if (details.delta.dx < -1) - {_transformLeft()} - else if (details.delta.dx > 1) - {_transformRight()} + builder: (context) => ChatScreen(wishlistId: widget._wishlist.id, wishlistName: widget._wishlist.name), + ), + ); + }, + onHorizontalDragUpdate: (DragUpdateDetails details) { + if (details.delta.dx < -1) { + _transformLeft(); + } else if (details.delta.dx > 1) { + _transformRight(); + } }, child: AnimatedContainer( transform: Matrix4.translationValues(_xOffset, 0, 0), From 56363cacc24b78455e965a9ac994aab06117b712 Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Fri, 8 Dec 2023 16:19:32 +0200 Subject: [PATCH 15/39] Design implemented, query not working --- assets/icons/amazon.svg | 3 + lib/main.dart | 164 ++++++++++++++------------- lib/models/product.dart | 25 ++++ lib/screens/cart.dart | 245 ++++++++++++++++++++++++++++++++++++++++ pubspec.lock | 56 +++++---- 5 files changed, 396 insertions(+), 97 deletions(-) create mode 100644 assets/icons/amazon.svg create mode 100644 lib/models/product.dart create mode 100644 lib/screens/cart.dart diff --git a/assets/icons/amazon.svg b/assets/icons/amazon.svg new file mode 100644 index 0000000..ef7d110 --- /dev/null +++ b/assets/icons/amazon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/main.dart b/lib/main.dart index f0ddc6a..ef8512f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:shopping_assistant_mobile_client/screens/wishlists.dart'; +import 'package:shopping_assistant_mobile_client/screens/cart.dart'; void main() { runApp(const MyApp()); } -class MyApp extends StatefulWidget { +class MyApp extends StatelessWidget { const MyApp({super.key}); static const List _pageNameOptions = [ @@ -24,90 +25,99 @@ class MyApp extends StatefulWidget { static const Color _selectedColor = Color.fromRGBO(36, 36, 36, 1); static const Color _unselectedColor = Color.fromRGBO(144, 144, 144, 1); - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State { - int _selectedIndex = 0; - - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); - } - @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData( - useMaterial3: true, - appBarTheme: AppBarTheme(), - textTheme: TextTheme( - bodyMedium: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - home: Scaffold( - appBar: AppBar( - title: Text(MyApp._pageNameOptions[_selectedIndex]), - centerTitle: true, - bottom: PreferredSize( - preferredSize: const Size.fromHeight(1), - child: Container( - color: Color.fromRGBO(234, 234, 234, 1), - height: 1, - ), - ), - ), - body: MyApp._widgetOptions[_selectedIndex], - bottomNavigationBar: BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: SvgPicture.asset( - 'assets/icons/wishlists.svg', - color: _selectedIndex == 0 - ? MyApp._selectedColor - : MyApp._unselectedColor, - ), - label: 'Wishlists', - ), - BottomNavigationBarItem( - icon: SvgPicture.asset( - 'assets/icons/start-new-search.svg', - color: _selectedIndex == 1 - ? MyApp._selectedColor - : MyApp._unselectedColor, - ), - label: 'New Chat', - ), - BottomNavigationBarItem( - icon: SvgPicture.asset( - 'assets/icons/settings.svg', - color: _selectedIndex == 2 - ? MyApp._selectedColor - : MyApp._unselectedColor, - ), - label: 'Settings', - ), - ], - selectedItemColor: MyApp._selectedColor, - unselectedItemColor: MyApp._unselectedColor, - selectedFontSize: 14, - unselectedFontSize: 14, - currentIndex: _selectedIndex, - onTap: _onItemTapped, - ), - ), + home: CartScreen() ); } + //State createState() => _MyAppState(); } -// Use to seed wishlists for new user -// final ApiClient client = ApiClient(); +// class _MyAppState extends State { +// int _selectedIndex = 0; +// +// void _onItemTapped(int index) { +// setState(() { +// _selectedIndex = index; +// }); +// } +// +// @override +// Widget build(BuildContext context) { +// return MaterialApp( +// theme: ThemeData( +// useMaterial3: true, +// appBarTheme: AppBarTheme(), +// textTheme: TextTheme( +// bodyMedium: TextStyle( +// fontSize: 16, +// fontWeight: FontWeight.w500, +// ), +// ), +// ), +// home: Scaffold( +// appBar: AppBar( +// title: Text(MyApp._pageNameOptions[_selectedIndex]), +// centerTitle: true, +// bottom: PreferredSize( +// preferredSize: const Size.fromHeight(1), +// child: Container( +// color: Color.fromRGBO(234, 234, 234, 1), +// height: 1, +// ), +// ), +// ), +// body: MyApp._widgetOptions[_selectedIndex], +// bottomNavigationBar: BottomNavigationBar( +// items: [ +// BottomNavigationBarItem( +// icon: SvgPicture.asset( +// 'assets/icons/wishlists.svg', +// color: _selectedIndex == 0 +// ? MyApp._selectedColor +// : MyApp._unselectedColor, +// ), +// label: 'Wishlists', +// ), +// BottomNavigationBarItem( +// icon: SvgPicture.asset( +// 'assets/icons/start-new-search.svg', +// color: _selectedIndex == 1 +// ? MyApp._selectedColor +// : MyApp._unselectedColor, +// ), +// label: 'New Chat', +// ), +// BottomNavigationBarItem( +// icon: SvgPicture.asset( +// 'assets/icons/settings.svg', +// color: _selectedIndex == 2 +// ? MyApp._selectedColor +// : MyApp._unselectedColor, +// ), +// label: 'Settings', +// ), +// ], +// selectedItemColor: MyApp._selectedColor, +// unselectedItemColor: MyApp._unselectedColor, +// selectedFontSize: 14, +// unselectedFontSize: 14, +// currentIndex: _selectedIndex, +// onTap: _onItemTapped, +// ), +// ), +// ); +// } +// } + + + + +// Use to seed wishlists for new user +//final ApiClient client = ApiClient(); +// // const String startPersonalWishlistMutations = r''' // mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { // startPersonalWishlist(dto: $dto) { diff --git a/lib/models/product.dart b/lib/models/product.dart new file mode 100644 index 0000000..df75e1e --- /dev/null +++ b/lib/models/product.dart @@ -0,0 +1,25 @@ +class Product { + Product({ + required this.id, + required this.name, + required this.url, + required this.imageUrls, + required this.rating, + required this.price +}); + + String id; + String name; + String url; + List imageUrls; + double rating; + double price; + + Product.fromJson(Map json) + : id = json['id'] as String, + name = json['name'] as String, + url = json['url'] as String, + imageUrls = json['imageUrls'] as List, + rating = json['rating'] as double, + price = json['name'] as double; +} \ No newline at end of file diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart new file mode 100644 index 0000000..52c2861 --- /dev/null +++ b/lib/screens/cart.dart @@ -0,0 +1,245 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:graphql/client.dart'; +import 'package:shopping_assistant_mobile_client/models/product.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +const String defaultUrl = 'https://s3-alpha-sig.figma.com/img/b8d6/7b6f/59839f0f3abfdeed91ca32d3501cbfa3?Expires=1702252800&Signature=aDWc2xO9d01Criwp829ZjhWE1pu~XGezZiM9oNOGkVZOYyGwxfDq5lVOSV0WOEkYdBR83hW7a-I2LY-U5R9evtoKf0BRGY1VVZ0H1wkp5WOHlC196gKr5tLPfseWahP2GWsQNSxfsgxg0cg8l8LamgqS1sUmD1Qt8jWdsqVcwlvTBY8X0q~ScDeCGn1n-7Npj315r4CbVLYMLfZWjpXROcR~Jpx-sqKVaxakw5OWdjegw7YBn~MAY6~yNi~Ylf44oFLkBpzI2aA65Z-TiRMPJ7HoLqJ3id8Eq7NoJ2PKxL88aZ2cOk9ZduRU7jI8FO-PvEBT-Qiwz0tUyEzmbiziDg__&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4'; + + +class CartScreen extends StatefulWidget { + const CartScreen({super.key}); + + @override + State createState() => _CartScreenState(); +} + +class _CartScreenState extends State { + // final _products = [ + // Product(name : '1', id: "Belkin USB C to VGA + Charge Adapter - USB C to VGA Cable for MacBook", price: 12.57, rating: 4.34, url: 'a', imageUrls: [defaultUrl,'a','b']), + // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.5, url: 'a', imageUrls: [defaultUrl,'a','b']), + // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.2, url: 'a', imageUrls: [defaultUrl,'a','b']), + // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.7, url: 'a', imageUrls: [defaultUrl,'a','b']), + // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.8, url: 'a', imageUrls: [defaultUrl,'a','b']) + // ]; + + var client = ApiClient(); + + late Future _productsFuture; + late List _products; + + @override + void initState(){ + super.initState(); + _productsFuture = _fetchProducts(); + } + + Future _fetchProducts() async { + const String productsPageFromPersonalWishlistQuery = r''' + query ProductsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { + productsPageFromPersonalWishlist( + wishlistId: $wishlistId, + pageNumber: $pageNumber, + pageSize: $pageSize + ) { + items { + id + url + name + rating + price + imagesUrls + } + } +}'''; + + QueryOptions queryOptions = QueryOptions( + document: gql(productsPageFromPersonalWishlistQuery), + variables: const { + 'wishlistId': "657310c6892da98a23091bdf", + 'pageNumber': 1, + 'pageSize': 10, + }); + + var result = await client.query(queryOptions); + print(result); + + _products = List>.from( + result?['productsPageFromPersonalWishlist']['items']) + .map((e) => Product.fromJson(e)) + .toList(); + + return; + } + + @override + Widget build(BuildContext context){ + return FutureBuilder( + future: _productsFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text('Error: ${snapshot.error}'), + ); + } else if (snapshot.connectionState == ConnectionState.done) { + // Data loaded successfully, display the widget + return Scaffold( + appBar: AppBar( + title: Text("Cart"), + centerTitle: true, + //titleTextStyle: TextStyle(color: Colors.black), + //backgroundColor: , + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + print('Back button pressed'); + }, + ), + ), + body: ListView.builder( + padding: EdgeInsets.symmetric(vertical: 30), + itemCount: _products.length, + itemBuilder: (context, index){ + return CartItem(product: _products[index]); + } + ), + backgroundColor: Colors.white, + ); + }; + + return Center( + child: CircularProgressIndicator(), + ); + } + ); + // return Scaffold( + // appBar: AppBar( + // title: Text("Cart"), + // centerTitle: true, + // //titleTextStyle: TextStyle(color: Colors.black), + // //backgroundColor: , + // leading: IconButton( + // icon: Icon(Icons.arrow_back), + // onPressed: () { + // print('Back button pressed'); + // }, + // ), + // ), + // body: ListView.builder( + // padding: EdgeInsets.symmetric(vertical: 30), + // itemCount: _products.length, + // itemBuilder: (context, index){ + // return CartItem(product: _products[index]); + // } + // ), + // backgroundColor: Colors.white, + // ); + } +} + +class CartItem extends StatelessWidget{ + CartItem({ + super.key, + required Product product, +}) : _product = product; + + final Product _product; + + + Widget _buildRatingStar(int index) { + int whole = _product.rating.floor().toInt(); + double fractional = _product.rating - whole; + + if (index < whole) { + return Icon(Icons.star, color: Colors.yellow[600], size: 20); + } + if (fractional >= 0.25 && fractional <= 0.75) { + return Icon(Icons.star_half, color: Colors.yellow[600], size: 20); + } + if (fractional > 0.75) { + return Icon(Icons.star, color: Colors.yellow[600], size: 20); + }else { + return Icon(Icons.star_border, color: Colors.grey, size: 20); + } + } + + List _buildRatingStars() { + List stars = []; + for (int i = 0; i < 5; i++) { + stars.add(_buildRatingStar(i)); + } + return stars; + } + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + height: 140, + margin: EdgeInsets.all(10), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 2, + offset: Offset(1, 2), + ) + ] + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + width: 100, + alignment: Alignment.center, + child: Image(image: NetworkImage(_product.imageUrls[0]),), + ), + SizedBox(width: 20), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _product.name, + style: const TextStyle(fontSize: 13), + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + Row( + children: [ + Row( + children: [ + Text(_product.rating.toStringAsFixed(1), style: TextStyle(fontSize: 14)) + ] + _buildRatingStars(), + ), + Text("\$" + _product.price.toStringAsFixed(2), style: TextStyle(fontSize: 14)), + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + Container( + width: double.infinity, + height: 35, + child: ElevatedButton.icon( + onPressed: ()=>{}, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue,// Блакитний колір фону кнопки + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), + ), + icon: SvgPicture.asset("../assets/icons/amazon.svg", height: 15), + label: Text(""), + ), + ) + + ], + ) + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index ccd3ceb..19c09c9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: connectivity_plus - sha256: b502a681ba415272ecc41400bd04fe543ed1a62632137dc84d25a91e7746f55f + sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" + version: "0.7.10" fake_async: dependency: transitive description: @@ -134,6 +134,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + sha256: b39c753e909d4796906c5696a14daf33639a76e017136c8d82bf3e620ce5bb8e + url: "https://pub.dev" + source: hosted + version: "5.2.0" flutter_svg: dependency: "direct main" description: @@ -156,50 +164,50 @@ packages: dependency: transitive description: name: gql - sha256: e5225e3be4d7eb4027406ab07cb68ad3a089deb3f7f6dc46edbdec78f2e5549f + sha256: aa3e0be4548353007b6e6fd24fcad0ce8c1179f9cb2ae5239d392fddb84a5ce5 url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1696717343881" + version: "1.0.1-alpha+1700868214564" gql_dedupe_link: dependency: transitive description: name: gql_dedupe_link - sha256: "79625bc8029755ce6b26483adf0255c6b6114acc56e7ef81469a99f1ce2296db" + sha256: e97e3f9490add43ba96cf5cc02d9d10a3723965c0bcc7bb1e04ef4f2e7a31a00 url: "https://pub.dev" source: hosted - version: "2.0.4-alpha+1696717344020" + version: "2.0.4-alpha+1700868214643" gql_error_link: dependency: transitive description: name: gql_error_link - sha256: bfdb543137da89448cc5d003fd029c2e8718931d39d4a7dedb16f9169862fbb9 + sha256: "93901458f3c050e33386dedb0ca7173e08cebd7078e4e0deca4bf23ab7a71f63" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.0+1" gql_exec: dependency: transitive description: name: gql_exec - sha256: da419a3ebaae7672ed662c42d754ffba996347af7fe0ca031f1dd699334994d8 + sha256: "394944626fae900f1d34343ecf2d62e44eb984826189c8979d305f0ae5846e38" url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1696717343896" + version: "1.1.1-alpha+1699813812660" gql_http_link: dependency: transitive description: name: gql_http_link - sha256: "0789d397d46ce274942fcc73e18a080cd2584296dadc33d8ae53d0666d7fe981" + sha256: "1f922eed1b7078fdbfd602187663026f9f659fe9a9499e2207b5d5e01617f658" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.1+1" gql_link: dependency: transitive description: name: gql_link - sha256: bcbb09ae8b200f413aa2d21fbf6ce4c4ac1ac443e81c612f29ef1587f4c84122 + sha256: "48dbf63b4831d800a2ce9675c9fecea3c9f2801de92072c7644a4bc52aa26c13" url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1696717343909" + version: "1.0.1-alpha+1700868214578" gql_transform_link: dependency: transitive description: @@ -272,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + logger: + dependency: "direct main" + description: + name: logger + sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + url: "https://pub.dev" + source: hosted + version: "2.0.2+1" matcher: dependency: transitive description: @@ -396,10 +412,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" rxdart: dependency: transitive description: @@ -585,10 +601,10 @@ packages: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.1.1" xdg_directories: dependency: transitive description: From 6fd516e09b05a9581fca96643f6dbe3aa40b36cf Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Sat, 9 Dec 2023 14:05:11 +0200 Subject: [PATCH 16/39] Working cart screen, added call from wishlists screen. --- lib/screens/cart.dart | 52 +++++++++++++----------------- lib/screens/wishlists.dart | 3 +- pubspec.lock | 66 +++++++++++++++++++++++++++++++++++++- pubspec.yaml | 1 + 4 files changed, 90 insertions(+), 32 deletions(-) diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart index 52c2861..bc35439 100644 --- a/lib/screens/cart.dart +++ b/lib/screens/cart.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:graphql/client.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:shopping_assistant_mobile_client/models/product.dart'; import 'package:shopping_assistant_mobile_client/network/api_client.dart'; @@ -8,13 +9,16 @@ const String defaultUrl = 'https://s3-alpha-sig.figma.com/img/b8d6/7b6f/59839f0f class CartScreen extends StatefulWidget { - const CartScreen({super.key}); + CartScreen({super.key, required this.wishlistId}); + + final String wishlistId; @override - State createState() => _CartScreenState(); + State createState() => _CartScreenState(wishlistId: wishlistId); } class _CartScreenState extends State { + _CartScreenState({required this.wishlistId}); // final _products = [ // Product(name : '1', id: "Belkin USB C to VGA + Charge Adapter - USB C to VGA Cable for MacBook", price: 12.57, rating: 4.34, url: 'a', imageUrls: [defaultUrl,'a','b']), // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.5, url: 'a', imageUrls: [defaultUrl,'a','b']), @@ -25,6 +29,8 @@ class _CartScreenState extends State { var client = ApiClient(); + final String wishlistId; + late Future _productsFuture; late List _products; @@ -55,8 +61,8 @@ class _CartScreenState extends State { QueryOptions queryOptions = QueryOptions( document: gql(productsPageFromPersonalWishlistQuery), - variables: const { - 'wishlistId': "657310c6892da98a23091bdf", + variables: { + 'wishlistId': wishlistId, 'pageNumber': 1, 'pageSize': 10, }); @@ -92,17 +98,19 @@ class _CartScreenState extends State { leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - print('Back button pressed'); + Navigator.pop(context); }, ), ), - body: ListView.builder( + body: _products.length == 0 ? + Center(child: Text("The cart is empty", style: TextStyle(fontSize: 18),),) + : ListView.builder( padding: EdgeInsets.symmetric(vertical: 30), itemCount: _products.length, itemBuilder: (context, index){ return CartItem(product: _products[index]); } - ), + ), backgroundColor: Colors.white, ); }; @@ -112,28 +120,6 @@ class _CartScreenState extends State { ); } ); - // return Scaffold( - // appBar: AppBar( - // title: Text("Cart"), - // centerTitle: true, - // //titleTextStyle: TextStyle(color: Colors.black), - // //backgroundColor: , - // leading: IconButton( - // icon: Icon(Icons.arrow_back), - // onPressed: () { - // print('Back button pressed'); - // }, - // ), - // ), - // body: ListView.builder( - // padding: EdgeInsets.symmetric(vertical: 30), - // itemCount: _products.length, - // itemBuilder: (context, index){ - // return CartItem(product: _products[index]); - // } - // ), - // backgroundColor: Colors.white, - // ); } } @@ -171,6 +157,12 @@ class CartItem extends StatelessWidget{ return stars; } + + Future _launchUrl(String url) async { + final Uri uri = Uri.parse(url); + if (!await launchUrl(uri)) throw 'Could not launch $url'; + } + @override Widget build(BuildContext context) { return Container( @@ -225,7 +217,7 @@ class CartItem extends StatelessWidget{ width: double.infinity, height: 35, child: ElevatedButton.icon( - onPressed: ()=>{}, + onPressed: () => _launchUrl(_product.url), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue,// Блакитний колір фону кнопки padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), diff --git a/lib/screens/wishlists.dart b/lib/screens/wishlists.dart index b8ed8d9..3c2e4ca 100644 --- a/lib/screens/wishlists.dart +++ b/lib/screens/wishlists.dart @@ -3,6 +3,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:graphql/client.dart'; import 'package:shopping_assistant_mobile_client/models/wishlist.dart'; import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'package:shopping_assistant_mobile_client/screens/cart.dart'; class WishlistsScreen extends StatefulWidget { const WishlistsScreen({super.key}); @@ -258,7 +259,7 @@ class _WishlistItemState extends State { context, MaterialPageRoute( builder: (context) => - Text('Cart ' + widget._wishlist.id)))), + CartScreen(wishlistId: widget._wishlist.id)))), behavior: HitTestBehavior.opaque, child: Container( padding: EdgeInsets.symmetric( diff --git a/pubspec.lock b/pubspec.lock index 19c09c9..c20e2af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -541,6 +541,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + url: "https://pub.dev" + source: hosted + version: "6.2.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + url: "https://pub.dev" + source: hosted + version: "3.1.0" uuid: dependency: "direct main" description: @@ -631,4 +695,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.1.4 <4.0.0" - flutter: ">=3.7.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 70205db..cb6ac6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,6 +32,7 @@ dependencies: sdk: flutter flutter_spinkit: ^5.0.0 logger: ^2.0.2+1 + url_launcher: ^6.0.12 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From 48d79153629dff8e7c7966e8703bca51d7c82928 Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Sat, 9 Dec 2023 14:15:35 +0200 Subject: [PATCH 17/39] Test data deleted --- lib/main.dart | 160 ++++++++++++++++++++---------------------- lib/screens/cart.dart | 14 +--- 2 files changed, 78 insertions(+), 96 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ef8512f..86da098 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shopping_assistant_mobile_client/screens/chat.dart'; import 'package:shopping_assistant_mobile_client/screens/wishlists.dart'; -import 'package:shopping_assistant_mobile_client/screens/cart.dart'; void main() { runApp(const MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); static const List _pageNameOptions = [ @@ -25,98 +25,92 @@ class MyApp extends StatelessWidget { static const Color _selectedColor = Color.fromRGBO(36, 36, 36, 1); static const Color _unselectedColor = Color.fromRGBO(144, 144, 144, 1); + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + int _selectedIndex = 0; + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + }); + } + @override Widget build(BuildContext context) { return MaterialApp( - home: CartScreen() + theme: ThemeData( + useMaterial3: true, + appBarTheme: AppBarTheme(), + textTheme: TextTheme( + bodyMedium: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + home: Scaffold( + appBar: AppBar( + title: Text(MyApp._pageNameOptions[_selectedIndex]), + centerTitle: true, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Container( + color: Color.fromRGBO(234, 234, 234, 1), + height: 1, + ), + ), + ), + body: MyApp._widgetOptions[_selectedIndex], + bottomNavigationBar: BottomNavigationBar( + items: [ + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/wishlists.svg', + color: _selectedIndex == 0 + ? MyApp._selectedColor + : MyApp._unselectedColor, + ), + label: 'Wishlists', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/start-new-search.svg', + color: _selectedIndex == 1 + ? MyApp._selectedColor + : MyApp._unselectedColor, + ), + label: 'New Chat', + ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + 'assets/icons/settings.svg', + color: _selectedIndex == 2 + ? MyApp._selectedColor + : MyApp._unselectedColor, + ), + label: 'Settings', + ), + ], + selectedItemColor: MyApp._selectedColor, + unselectedItemColor: MyApp._unselectedColor, + selectedFontSize: 14, + unselectedFontSize: 14, + currentIndex: _selectedIndex, + onTap: _onItemTapped, + ), + ), ); } - //State createState() => _MyAppState(); } -// class _MyAppState extends State { -// int _selectedIndex = 0; -// -// void _onItemTapped(int index) { -// setState(() { -// _selectedIndex = index; -// }); -// } -// -// @override -// Widget build(BuildContext context) { -// return MaterialApp( -// theme: ThemeData( -// useMaterial3: true, -// appBarTheme: AppBarTheme(), -// textTheme: TextTheme( -// bodyMedium: TextStyle( -// fontSize: 16, -// fontWeight: FontWeight.w500, -// ), -// ), -// ), -// home: Scaffold( -// appBar: AppBar( -// title: Text(MyApp._pageNameOptions[_selectedIndex]), -// centerTitle: true, -// bottom: PreferredSize( -// preferredSize: const Size.fromHeight(1), -// child: Container( -// color: Color.fromRGBO(234, 234, 234, 1), -// height: 1, -// ), -// ), -// ), -// body: MyApp._widgetOptions[_selectedIndex], -// bottomNavigationBar: BottomNavigationBar( -// items: [ -// BottomNavigationBarItem( -// icon: SvgPicture.asset( -// 'assets/icons/wishlists.svg', -// color: _selectedIndex == 0 -// ? MyApp._selectedColor -// : MyApp._unselectedColor, -// ), -// label: 'Wishlists', -// ), -// BottomNavigationBarItem( -// icon: SvgPicture.asset( -// 'assets/icons/start-new-search.svg', -// color: _selectedIndex == 1 -// ? MyApp._selectedColor -// : MyApp._unselectedColor, -// ), -// label: 'New Chat', -// ), -// BottomNavigationBarItem( -// icon: SvgPicture.asset( -// 'assets/icons/settings.svg', -// color: _selectedIndex == 2 -// ? MyApp._selectedColor -// : MyApp._unselectedColor, -// ), -// label: 'Settings', -// ), -// ], -// selectedItemColor: MyApp._selectedColor, -// unselectedItemColor: MyApp._unselectedColor, -// selectedFontSize: 14, -// unselectedFontSize: 14, -// currentIndex: _selectedIndex, -// onTap: _onItemTapped, -// ), -// ), -// ); -// } -// } - // Use to seed wishlists for new user -//final ApiClient client = ApiClient(); +// final ApiClient client = ApiClient(); // // const String startPersonalWishlistMutations = r''' // mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { @@ -142,4 +136,4 @@ class MyApp extends StatelessWidget { // // .then((result) => print(jsonEncode(result))); // // sleep(Duration(milliseconds: 100)); // // } -// +// \ No newline at end of file diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart index bc35439..015f790 100644 --- a/lib/screens/cart.dart +++ b/lib/screens/cart.dart @@ -5,8 +5,6 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:shopping_assistant_mobile_client/models/product.dart'; import 'package:shopping_assistant_mobile_client/network/api_client.dart'; -const String defaultUrl = 'https://s3-alpha-sig.figma.com/img/b8d6/7b6f/59839f0f3abfdeed91ca32d3501cbfa3?Expires=1702252800&Signature=aDWc2xO9d01Criwp829ZjhWE1pu~XGezZiM9oNOGkVZOYyGwxfDq5lVOSV0WOEkYdBR83hW7a-I2LY-U5R9evtoKf0BRGY1VVZ0H1wkp5WOHlC196gKr5tLPfseWahP2GWsQNSxfsgxg0cg8l8LamgqS1sUmD1Qt8jWdsqVcwlvTBY8X0q~ScDeCGn1n-7Npj315r4CbVLYMLfZWjpXROcR~Jpx-sqKVaxakw5OWdjegw7YBn~MAY6~yNi~Ylf44oFLkBpzI2aA65Z-TiRMPJ7HoLqJ3id8Eq7NoJ2PKxL88aZ2cOk9ZduRU7jI8FO-PvEBT-Qiwz0tUyEzmbiziDg__&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4'; - class CartScreen extends StatefulWidget { CartScreen({super.key, required this.wishlistId}); @@ -19,13 +17,6 @@ class CartScreen extends StatefulWidget { class _CartScreenState extends State { _CartScreenState({required this.wishlistId}); - // final _products = [ - // Product(name : '1', id: "Belkin USB C to VGA + Charge Adapter - USB C to VGA Cable for MacBook", price: 12.57, rating: 4.34, url: 'a', imageUrls: [defaultUrl,'a','b']), - // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.5, url: 'a', imageUrls: [defaultUrl,'a','b']), - // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.2, url: 'a', imageUrls: [defaultUrl,'a','b']), - // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.7, url: 'a', imageUrls: [defaultUrl,'a','b']), - // Product(id : '1', name: "USB C to VGA 2", price: 12.57, rating: 4.8, url: 'a', imageUrls: [defaultUrl,'a','b']) - // ]; var client = ApiClient(); @@ -93,8 +84,6 @@ class _CartScreenState extends State { appBar: AppBar( title: Text("Cart"), centerTitle: true, - //titleTextStyle: TextStyle(color: Colors.black), - //backgroundColor: , leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { @@ -219,14 +208,13 @@ class CartItem extends StatelessWidget{ child: ElevatedButton.icon( onPressed: () => _launchUrl(_product.url), style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue,// Блакитний колір фону кнопки + backgroundColor: Colors.blue, padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), ), icon: SvgPicture.asset("../assets/icons/amazon.svg", height: 15), label: Text(""), ), ) - ], ) ) From c6e29ed6372fa6c5acdda60d883077a05e9ddb90 Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 10 Dec 2023 01:35:30 +0200 Subject: [PATCH 18/39] fixed cosmetic bugs --- lib/screens/chat.dart | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index a506d5d..95f8c46 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -38,7 +38,7 @@ class MessageBubble extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - message, + message.trim(), style: TextStyle(color: isOutgoing ? Colors.white : Colors.black, fontSize: 18.0 ), @@ -127,6 +127,7 @@ class ChatScreenState extends State { final lastMessage = messages.isNotEmpty ? messages.last : null; message.isProduct = _searchService.checkerForProduct(); message.isSuggestion = _searchService.checkerForSuggestion(); + bool checker = false; logger.d("Product status: ${message.isProduct}"); if (lastMessage != null && lastMessage.role != "User" && message.role != "User") { final updatedMessage = Message( @@ -388,16 +389,19 @@ class ChatScreenState extends State { child: Row( children: [ Expanded( - child: TextField( - controller: _messageController, - onChanged: (text) { - setState(() { - isSendButtonEnabled = text.isNotEmpty; - }); - }, - decoration: InputDecoration( - hintText: 'Enter your message...', - contentPadding: EdgeInsets.symmetric(vertical: 20.0) + child: Padding( + padding: const EdgeInsets.only(left: 15.0), // Adjust the left padding as needed + child: TextField( + controller: _messageController, + onChanged: (text) { + setState(() { + isSendButtonEnabled = text.isNotEmpty; + }); + }, + decoration: InputDecoration( + hintText: 'Enter your message...', + contentPadding: EdgeInsets.symmetric(vertical: 20.0), + ), ), ), ), From 7eac7fb9fb54197b6e3e2ec42d9fc26015535e02 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Thu, 14 Dec 2023 21:37:14 +0200 Subject: [PATCH 19/39] add ability to leave messages by pressing the button every time you open New Chat tab it really opens NEW chat --- lib/main.dart | 4 +-- lib/network/search_service.dart | 2 +- lib/screens/chat.dart | 45 +++++++++++++++++++++++++-------- lib/screens/wishlists.dart | 2 +- pubspec.lock | 26 +++++++++---------- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5be5953..6b06b96 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,7 @@ class MyApp extends StatefulWidget { static List _widgetOptions = [ WishlistsScreen(), - ChatScreen(wishlistId: '', wishlistName: 'New Chat',), + ChatScreen(wishlistId: '', wishlistName: 'New Chat', openedFromBottomBar: true), Text(''), ]; @@ -139,4 +139,4 @@ class _MyAppState extends State { // // .then((result) => print(jsonEncode(result))); // // sleep(Duration(milliseconds: 100)); // // } -// \ No newline at end of file +// diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 2197852..f59bdcd 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -142,4 +142,4 @@ class SearchService { } return []; } -} \ No newline at end of file +} diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 95f8c46..6eec81b 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -65,8 +65,9 @@ class MessageBubble extends StatelessWidget { class ChatScreen extends StatefulWidget { String wishlistId; String wishlistName; + bool openedFromBottomBar; - ChatScreen({Key? key, required this.wishlistId, required this.wishlistName}) : super(key: key); + ChatScreen({Key? key, required this.wishlistId, required this.wishlistName, required this.openedFromBottomBar}) : super(key: key); @override State createState() => ChatScreenState(); @@ -74,18 +75,22 @@ class ChatScreen extends StatefulWidget { class ChatScreenState extends State { var logger = Logger(); - final SearchService _searchService = SearchService(); + SearchService _searchService = SearchService(); List messages = []; - final TextEditingController _messageController = TextEditingController(); + TextEditingController _messageController = TextEditingController(); + bool showBackButton = false; bool buttonsVisible = true; bool isSendButtonEnabled = false; bool showButtonsContainer = true; bool isWaitingForResponse = false; - final ScrollController _scrollController = ScrollController(); + ScrollController _scrollController = ScrollController(); late Widget appBarTitle; void initState() { super.initState(); + if (widget.openedFromBottomBar) { + _resetState(); + } appBarTitle = Text('New Chat', style: TextStyle(fontSize: 18.0)); _searchService.sseStream.listen((event) { _handleSSEMessage(Message(text: '${event.data}')); @@ -94,11 +99,27 @@ class ChatScreenState extends State { if(!widget.wishlistId.isEmpty) { _loadPreviousMessages(); + showBackButton = true; showButtonsContainer = false; buttonsVisible = false; } } + void _resetState() { + widget.wishlistId = ''; + widget.wishlistName = ''; + _searchService = SearchService(); + messages = []; + _messageController = TextEditingController(); + showBackButton = false; + buttonsVisible = true; + isSendButtonEnabled = false; + showButtonsContainer = true; + isWaitingForResponse = false; + _scrollController = ScrollController(); + appBarTitle = const Text('New Chat', style: TextStyle(fontSize: 18.0)); + } + Future _loadPreviousMessages() async { final pageNumber = 1; final pageSize = 200; @@ -239,12 +260,14 @@ class ChatScreenState extends State { appBar: AppBar( title: appBarTitle, centerTitle: true, - leading: IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () { - print('Back button pressed'); - }, - ), + leading: showBackButton + ? IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + : null, ), body: Column( children: [ @@ -416,4 +439,4 @@ class ChatScreenState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/screens/wishlists.dart b/lib/screens/wishlists.dart index ea8e434..1cdf178 100644 --- a/lib/screens/wishlists.dart +++ b/lib/screens/wishlists.dart @@ -218,7 +218,7 @@ class _WishlistItemState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => ChatScreen(wishlistId: widget._wishlist.id, wishlistName: widget._wishlist.name), + builder: (context) => ChatScreen(wishlistId: widget._wishlist.id, wishlistName: widget._wishlist.name, openedFromBottomBar: false), ), ); }, diff --git a/pubspec.lock b/pubspec.lock index c20e2af..86567a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" connectivity_plus: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nm: dependency: transitive description: @@ -497,18 +497,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -694,5 +694,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.4 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" From 84268619fd45352ce66d790fd999499da88c3849 Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 15 Dec 2023 14:41:05 +0200 Subject: [PATCH 20/39] added suggestions display --- lib/network/search_service.dart | 2 +- lib/screens/chat.dart | 79 ++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 2197852..697ab5f 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -31,7 +31,7 @@ class SearchService { } bool checkerForSuggestion() { - return type == SearchEventType.product; + return type == SearchEventType.suggestion; } String? wishlistId; diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 95f8c46..e6384ad 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -76,6 +76,7 @@ class ChatScreenState extends State { var logger = Logger(); final SearchService _searchService = SearchService(); List messages = []; + List suggestions = []; final TextEditingController _messageController = TextEditingController(); bool buttonsVisible = true; bool isSendButtonEnabled = false; @@ -129,7 +130,11 @@ class ChatScreenState extends State { message.isSuggestion = _searchService.checkerForSuggestion(); bool checker = false; logger.d("Product status: ${message.isProduct}"); - if (lastMessage != null && lastMessage.role != "User" && message.role != "User") { + logger.d("Suggestion status: ${message.isSuggestion}"); + if(message.isSuggestion){ + suggestions.add(message.text); + } + if (lastMessage != null && lastMessage.role != "User" && message.role != "User" && !message.isSuggestion && message.text != "/n") { final updatedMessage = Message( text: "${lastMessage.text}${message.text}", role: "Application", @@ -137,7 +142,9 @@ class ChatScreenState extends State { messages.removeLast(); messages.add(updatedMessage); } else { - messages.add(message); + if(!message.isSuggestion && message.text != "/n"){ + messages.add(message); + } } }); setState(() { @@ -205,6 +212,13 @@ class ChatScreenState extends State { _scrollToBottom(); } + void _handleSuggestion(String suggestion) { + _messageController.text = suggestion; + _sendMessage(); + suggestions.clear(); + } + + void _scrollToBottom() { _scrollController.animateTo( _scrollController.position.maxScrollExtent, @@ -233,6 +247,46 @@ class ChatScreenState extends State { ); } + Widget _generateSuggestionButtons() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + 'Several possible options', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey), + ), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: suggestions.map((suggestion) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 5), + child: ElevatedButton( + onPressed: () { + _handleSuggestion(suggestion); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + primary: Colors.white, + onPrimary: Colors.blue, + side: BorderSide(color: Colors.blue, width: 2.0), + ), + child: Text(suggestion, style: TextStyle(color: Colors.black)), + ), + ); + }).toList(), + ), + ), + ], + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -366,24 +420,8 @@ class ChatScreenState extends State { color: Colors.blue, size: 25.0, ), - if (messages.any((message) => message.isSuggestion)) - Container( - padding: EdgeInsets.all(8.0), - color: Colors.grey[300], - child: Row( - children: [ - Icon(Icons.lightbulb), - SizedBox(width: 8.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: messages - .where((message) => message.isSuggestion) - .map((message) => Text(message.text)) - .toList(), - ), - ], - ), - ), + if (suggestions.isNotEmpty) + _generateSuggestionButtons(), Container( margin: const EdgeInsets.all(8.0), child: Row( @@ -403,6 +441,7 @@ class ChatScreenState extends State { contentPadding: EdgeInsets.symmetric(vertical: 20.0), ), ), + ), ), IconButton( From a47a5ba490a735d7ae36a826485d545cbb35457e Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 15 Dec 2023 15:20:05 +0200 Subject: [PATCH 21/39] deleted display "\n" --- lib/screens/chat.dart | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index e6384ad..fd78bc3 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -128,25 +128,30 @@ class ChatScreenState extends State { final lastMessage = messages.isNotEmpty ? messages.last : null; message.isProduct = _searchService.checkerForProduct(); message.isSuggestion = _searchService.checkerForSuggestion(); - bool checker = false; - logger.d("Product status: ${message.isProduct}"); - logger.d("Suggestion status: ${message.isSuggestion}"); if(message.isSuggestion){ suggestions.add(message.text); } - if (lastMessage != null && lastMessage.role != "User" && message.role != "User" && !message.isSuggestion && message.text != "/n") { + logger.d("Product status: ${message.isProduct}"); + logger.d("Suggestion status: ${message.isSuggestion}"); + logger.d("Message text: ${message.text}"); + if (lastMessage != null && lastMessage.role != "User" && message.role != "User" && !message.isSuggestion) { + String fullMessageText = lastMessage.text + message.text; + fullMessageText = fullMessageText.replaceAll("\\n", ""); + logger.d("fullMessageText: $fullMessageText"); final updatedMessage = Message( - text: "${lastMessage.text}${message.text}", + text: fullMessageText, role: "Application", isProduct: message.isProduct); messages.removeLast(); messages.add(updatedMessage); } else { - if(!message.isSuggestion && message.text != "/n"){ - messages.add(message); + String messageText = message.text.replaceAll("\\n", ""); + if (!message.isSuggestion) { + messages.add(Message(text: messageText, role: message.role, isProduct: message.isProduct, isSuggestion: message.isSuggestion)); } } }); + setState(() { isWaitingForResponse = false; }); @@ -209,6 +214,7 @@ class ChatScreenState extends State { } _messageController.clear(); + suggestions.clear(); _scrollToBottom(); } From 19813c2f66a711496f40584fc371cb7d265be8da Mon Sep 17 00:00:00 2001 From: Mykhailo Bilodid Date: Fri, 15 Dec 2023 21:41:15 +0200 Subject: [PATCH 22/39] SA-178 cards screen created --- lib/main.dart | 35 +----------------- lib/network/search_service.dart | 7 ++++ lib/screens/cards.dart | 60 ++++++++++++++++++++++++++++++ lib/screens/chat.dart | 65 ++++++++++++++++----------------- pubspec.lock | 26 ++++++------- 5 files changed, 113 insertions(+), 80 deletions(-) create mode 100644 lib/screens/cards.dart diff --git a/lib/main.dart b/lib/main.dart index 5be5953..0b4314b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -106,37 +106,4 @@ class _MyAppState extends State { ), ); } -} - - - - - -// Use to seed wishlists for new user -// final ApiClient client = ApiClient(); -// -// const String startPersonalWishlistMutations = r''' -// mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { -// startPersonalWishlist(dto: $dto) { -// createdById, id, name, type -// } -// } -// '''; -// -// MutationOptions mutationOptions = MutationOptions( -// document: gql(startPersonalWishlistMutations), -// variables: const { -// 'dto': { -// 'firstMessageText': 'Gaming mechanical keyboard', -// 'type': 'Product' -// }, -// }); -// -// var client = ApiClient(); -// // for (var i = 0; i < 5; i++) { -// // client -// // .mutate(mutationOptions) -// // .then((result) => print(jsonEncode(result))); -// // sleep(Duration(milliseconds: 100)); -// // } -// \ No newline at end of file +} \ No newline at end of file diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 2197852..f0c8463 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -22,6 +22,8 @@ SearchEventType type = SearchEventType.message; class SearchService { final ApiClient client = ApiClient(); + List products = []; + late final _sseController = StreamController(); Stream get sseStream => _sseController.stream; @@ -92,6 +94,11 @@ class SearchService { final event = ServerSentEvent(chunk.event, cleanedMessage); type = chunk.event; + if(type == SearchEventType.product) { + String pattern = r'[\\\"]'; + String product = event.data.replaceAll(RegExp(pattern), ''); + products.add(product); + } _sseController.add(event); } } diff --git a/lib/screens/cards.dart b/lib/screens/cards.dart new file mode 100644 index 0000000..542a20c --- /dev/null +++ b/lib/screens/cards.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +class Cards extends StatefulWidget { + final String wishlistName; + List products = []; + + Cards({required this.wishlistName, required this.products}); + + @override + _CardsState createState() => _CardsState(); +} + +class _CardsState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.wishlistName), + centerTitle: true, + ), + body: Center( + child: Stack( + children: [ + // Back Card with increased rotation effect + Positioned( + left: 10, // Adjust the left position as needed + child: Transform.rotate( + angle: -5 * 3.1415926535 / 180, // Rotation angle + child: Container( + width: 400, // Increased width + height: 600, // Increased height + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + border: Border.all( + color: Color(0xFF009FFF), + ), + ), + ), + ), + ), + // Main Card + Container( + width: 300, // Set the width of the main card + height: 500, // Set the height of the main card + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + border: Border.all( + color: Color(0xFF009FFF), + ), + ), + ), + ], + ), + ), + ); + } +} + diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 95f8c46..eace5c8 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:logger/logger.dart'; import 'package:shopping_assistant_mobile_client/network/search_service.dart'; +import 'package:shopping_assistant_mobile_client/screens/cards.dart'; class Message { final String text; @@ -23,41 +24,30 @@ class MessageBubble extends StatelessWidget { @override Widget build(BuildContext context) { return Align( - alignment: isOutgoing ? Alignment.centerRight : Alignment.centerLeft, - child: Container( - margin: const EdgeInsets.all(8.0), - padding: const EdgeInsets.all(16.0), - constraints: BoxConstraints( - maxWidth: 300.0, - ), - decoration: BoxDecoration( - color: isOutgoing ? Colors.blue : Colors.grey[200], - borderRadius: BorderRadius.circular(10.0), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( + alignment: isOutgoing ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(16.0), + constraints: BoxConstraints( + maxWidth: 300.0, + ), + decoration: BoxDecoration( + color: isOutgoing ? Colors.blue : Colors.grey[200], + borderRadius: BorderRadius.circular(10.0), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( message.trim(), - style: TextStyle(color: isOutgoing ? Colors.white : Colors.black, - fontSize: 18.0 - ), - ), - if (isProduct) - ElevatedButton( - onPressed: () { - print('View Product button pressed'); - }, - style: ElevatedButton.styleFrom( - primary: Colors.indigo, - onPrimary: Colors.white, - minimumSize: Size(300, 50) - ), - child: Text('View Product'), - ), - ], + style: TextStyle( + color: isOutgoing ? Colors.white : Colors.black, + fontSize: 18.0, + ), ), - ), + ], + ), + ), ); } } @@ -151,6 +141,7 @@ class ChatScreenState extends State { if (wishlistName != null) { setState(() { appBarTitle = Text(wishlistName, style: TextStyle(fontSize: 18.0)); + this.widget.wishlistName = wishlistName; }); } } @@ -181,6 +172,14 @@ class ChatScreenState extends State { _scrollToBottom(); setState(() { + if(_searchService.checkerForProduct()){ + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Cards(wishlistName: this.widget.wishlistName, products: this._searchService.products,), + ), + ); + } isWaitingForResponse = false; }); } diff --git a/pubspec.lock b/pubspec.lock index c20e2af..86567a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" connectivity_plus: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nm: dependency: transitive description: @@ -497,18 +497,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -694,5 +694,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.4 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" From 7bcab820aeab29aaaa540b62f0735012240e33b8 Mon Sep 17 00:00:00 2001 From: Mykhailo Bilodid Date: Fri, 15 Dec 2023 23:16:01 +0200 Subject: [PATCH 23/39] SA-178 svgs added --- assets/icons/add-products.svg | 3 +++ assets/icons/back.svg | 4 ++++ assets/icons/exit-cards.svg | 3 +++ assets/icons/heart.svg | 3 +++ assets/icons/x.svg | 3 +++ 5 files changed, 16 insertions(+) create mode 100644 assets/icons/add-products.svg create mode 100644 assets/icons/back.svg create mode 100644 assets/icons/exit-cards.svg create mode 100644 assets/icons/heart.svg create mode 100644 assets/icons/x.svg diff --git a/assets/icons/add-products.svg b/assets/icons/add-products.svg new file mode 100644 index 0000000..d5d9fe0 --- /dev/null +++ b/assets/icons/add-products.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/back.svg b/assets/icons/back.svg new file mode 100644 index 0000000..46ad245 --- /dev/null +++ b/assets/icons/back.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/exit-cards.svg b/assets/icons/exit-cards.svg new file mode 100644 index 0000000..7f64e76 --- /dev/null +++ b/assets/icons/exit-cards.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/heart.svg b/assets/icons/heart.svg new file mode 100644 index 0000000..ddfb53e --- /dev/null +++ b/assets/icons/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/x.svg b/assets/icons/x.svg new file mode 100644 index 0000000..3226ed1 --- /dev/null +++ b/assets/icons/x.svg @@ -0,0 +1,3 @@ + + + From a6c9513820dd34fb48a680534b26ac9efa4898e0 Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Fri, 15 Dec 2023 18:04:42 -0500 Subject: [PATCH 24/39] SA-225 Configure app for iOS deployment - Add App icon - Change name in Info.plist --- ios/Podfile.lock | 6 + ios/Runner.xcodeproj/project.pbxproj | 7 +- .../AppIcon.appiconset/Contents.json | 118 +++++++++--------- .../AppIcon.appiconset/Icon-120 1.png | Bin 0 -> 3191 bytes .../AppIcon.appiconset/Icon-120.png | Bin 0 -> 3191 bytes .../AppIcon.appiconset/Icon-152.png | Bin 0 -> 4084 bytes .../AppIcon.appiconset/Icon-167.png | Bin 0 -> 4535 bytes .../AppIcon.appiconset/Icon-180.png | Bin 0 -> 4963 bytes .../AppIcon.appiconset/Icon-20.png | Bin 0 -> 439 bytes .../AppIcon.appiconset/Icon-29 1.png | Bin 0 -> 676 bytes .../AppIcon.appiconset/Icon-29.png | Bin 0 -> 676 bytes .../AppIcon.appiconset/Icon-40 1.png | Bin 0 -> 915 bytes .../AppIcon.appiconset/Icon-40 2.png | Bin 0 -> 915 bytes .../AppIcon.appiconset/Icon-40.png | Bin 0 -> 915 bytes .../AppIcon.appiconset/Icon-58 1.png | Bin 0 -> 1505 bytes .../AppIcon.appiconset/Icon-58.png | Bin 0 -> 1505 bytes .../AppIcon.appiconset/Icon-60.png | Bin 0 -> 1555 bytes .../AppIcon.appiconset/Icon-76.png | Bin 0 -> 2043 bytes .../AppIcon.appiconset/Icon-80 1.png | Bin 0 -> 2110 bytes .../AppIcon.appiconset/Icon-80.png | Bin 0 -> 2110 bytes .../AppIcon.appiconset/Icon-87.png | Bin 0 -> 2301 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes ios/Runner/Assets.xcassets/Contents.json | 6 + ios/Runner/Info.plist | 10 +- pubspec.lock | 26 ++-- 38 files changed, 92 insertions(+), 81 deletions(-) create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-120 1.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-120.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-152.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-167.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-180.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29 1.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40 1.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40 2.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-58 1.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-58.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-80 1.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-80.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-87.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/Contents.json diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4823387..70ea866 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -10,12 +10,15 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - Flutter (from `Flutter`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: @@ -30,6 +33,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d @@ -37,6 +42,7 @@ SPEC CHECKSUMS: path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3df735e..fcf7c9a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -161,7 +161,6 @@ 9D611B8839E482A01080BA68 /* Pods-RunnerTests.release.xcconfig */, 6962B5FE101B87DBED54C956 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -476,7 +475,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.shoppingAssistantMobileClient; + PRODUCT_BUNDLE_IDENTIFIER = com.shchoholiev.ShoppingAssistant; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -655,7 +654,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.shoppingAssistantMobileClient; + PRODUCT_BUNDLE_IDENTIFIER = com.shchoholiev.ShoppingAssistant; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -678,7 +677,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.shoppingAssistantMobileClient; + PRODUCT_BUNDLE_IDENTIFIER = com.shchoholiev.ShoppingAssistant; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..31a25a0 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,122 @@ { "images" : [ { - "size" : "20x20", + "filename" : "Icon-40 2.png", "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "Icon-60.png", "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "Icon-29 1.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Icon-58 1.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Icon-87.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" + "scale" : "3x", + "size" : "29x29" }, { - "size" : "40x40", + "filename" : "Icon-80 1.png", "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Icon-120 1.png", "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" + "scale" : "3x", + "size" : "40x40" }, { - "size" : "60x60", + "filename" : "Icon-120.png", "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", + "filename" : "Icon-180.png", "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" + "scale" : "3x", + "size" : "60x60" }, { - "size" : "20x20", + "filename" : "Icon-20.png", "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "Icon-40.png", "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "Icon-29.png", "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Icon-58.png", "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "40x40", + "filename" : "Icon-40 1.png", "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Icon-80.png", "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "76x76", + "filename" : "Icon-76.png", "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "76x76" }, { - "size" : "76x76", + "filename" : "Icon-152.png", "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "76x76" }, { - "size" : "83.5x83.5", + "filename" : "Icon-167.png", "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "83.5x83.5" }, { - "size" : "1024x1024", - "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-120 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-120 1.png new file mode 100644 index 0000000000000000000000000000000000000000..05fadfdecce99cc304030ed409ff592fd3a31d61 GIT binary patch literal 3191 zcmbtX=Q|sW9*r6?Us`+bof@Sz+ma%LpmwM&wpz7QBlc|jR;}2|TSS$H+ABfPqOBP# zc~h~fVincub^n9=Jom%zoadbL{Jx))X=x5&V-{cr003+z#s*fG9R1&8ymE={3{|O@ zMCYRq(+2=rp0NCKr@z!R;Z_hZ;Ps@?)};{hG`4~P0I@OvKw=62@bl72+ynq3Q~-c& zHvmAp2ms&>Debzi3jnYjn;7WZM3?TC#RWRe3Xk@0^m&Yiu4X>J^BD_6=O5`iwY}Q0 zyU#J}Mo4V3xxye27+7*^8k=6EotxXrq^egUlTw#7qIV@zwMCpS-~^}`Q2(kj|1%Kw z)A7*PV`E*Wz4>wT-tBW-E*cy8OLId6y&Hd#)%+x3qcAuxVdGdw_T}SH;Db_#LL2QL zuFR-Do=_cR_s9)J&E}`qrI>G{}fq5clG*;`u^%A9j5g!9bl~%O+n6i z?0(j=J>>J*_w`nBg@RT;x`|SAJwdI%mom3)0b@3a{?AAKC-2(xFSA*GT3v_1S;~%- z__`^X(JLcJ=C~nIb)i4?)hAlJwxRFaB93c+h^y^)pxFF->0`eUd;YtjI;b%um8(JDM_398;u3hC9gFayL}483q&-L35KWl zS}rJcLaFTvSMJUK{`gtoi^WVfB*5Yhr8C<2Z*R8eflGPw7`CX3WsLLlcsVq=X>UzI zHf=bj6u8c(Jz{q!$fnt>AM-7)b8GWNF^47P!6N@Pu#MzqH*BLs26cg_1TLeXveUnQ><2WaZ+_15gkw8^~nw%s;q4QrA8RaMQdxf39s zzmkGz*zG%-f*u9ln|j?y2xVXtKkn_f@@9P{>$4yagx+=+|GW0)Qme*s*7%E9T)`2# zZ*U&8hg1yqv`ubK)rqQ$pA&57yLjj^Y^5yiRLCiAi7q6kTD49-g_e)~07@KwH;#22 zo{^J9T@2bcPThrII*J5*tOTlp+LSuJ)8p34y5)kg^%k_EkMG?WrJr{y;k!-vZD3-< z;C5=C6M6R;;?|y+G>7*6ZkOO?g}znwSn_cu;TWZOJ!!gSL=bNuTgwisek_+XZ8D|1 z|H5S*ha9|F;1LbU`n_|lQe=kxy%>`i13u9pG`ptS$)SGq)0s6JfhJn1w+qumzp2M6 zrG32@E&$x1s7YW&k4ivImJAJv4g8V%{8q?%N+<0nPHDBCrT`nC8i>DsVI zHkU6|F4t#j(qh}5eZkM1&5gk}U(V?+BHw;@^|Zrs-F^L@uIYnw8WR}PAk*7m;@TS1 z1$7eMJ1k$Ze@rz*{L(Fio?;-Q{u&HAkt2+&OyZGzY9h}zLSw9IOBy+0L=bJ2w)kFcC`QYj$2SRVh&2k1yU{1t9CX@A5<5Em6(<*p z8H*Wi{__C^JvePI`)2-zIe(^vvk#GDMDZ|6lo_}pYB%TM&h2kw=_ZkMcweYY?`in_ zO_rLrr9j7qyi#w~AqDJ~Uj%lnHg?E1qNADC^&!qGA#FS`i_^VlHT`bcNT)nxSw)bi z(81Tz$H#ex=ihz7&@Vlm7e&_w$F(sS+>{-8YJ>XTy_BN#uFOk)K%i#!^yxkR;1bkm ze^zpo=TfP{lz~h~IMba@w!7cb@W%mldF7ti0R?Pep6M84&9g?o19F)tiKGmcU)FEV z`A36z+(ZrCuFAY>JM=rp$RSmxvSD%?&yAfSu2c}i&tz5*l(}0yF_JbSYO)buSzF+e zQa}h+bBfN~mHy&Ftjkfeh(O3{e*3%4%C|}+Qc@^|vQ%Bs$r7;C0q-szHOdnBDyE*U zQnznva`D-JxmzB>pIyBn!QlCnk;fJxLailT)9x@E)M9KJ0RqO|ze|zocf;ZQpZC!< z(PtndZ>a$gm4u$4CI?ue{a_&pPd~|5BEji_J5Z1OmC4Ehe!{qZ6z3B$n_AFTKX z^thhH=hy}ld#7KBYTeA-C|ar(;(zAJiLr~`z2Bhw6So&Q_H$0rr^QoQCI>^$P~G9j zUVg^aMGxQ^UZP#-5`6yQJl+3Q;eBfX1OS9=T&T3lL(PVAy!&>j;tKa=ckO%PTnfrBy-Rj5`8TER6x&vqi zvB;;ZHs9lYP9l8XLX&pkC7gaxSJZb@rX3cB38D7tJml;=mBuDAU2gL*Z5(@gt08d<; z5`~*{U>_HjKe*3U@ZFdG5ac@s^S#nAk{z}4RVB8yRDN|e@BDDdr$2%JXB2VZ`a%K= zLlkk4+pwGVf#aUfih>zVEh-z)n{U-~6i<1_NIi#j_)JP8uY7xuyP;9zuDw)egxhW~@H6Ex|c(8_I%Y~oxY}k5~^Cq`={36{v zGat0}we?w{iQcYQ=o4w=^}1eZyg)!T#q7yhalH|X7=}z}JbE8#c5B1|goiEvmZ&=w z1~2lK25`sy^>Q=i+WH%G=%N_JiP(}5jbx7cW{84Zd`U^#Lu|Q)SErhXQQa6yQ|%Tt z{2wo z4`U4S2@YFicU$C29}Ow;J1SB}ZluVcR@|P_y84gz+^+nnMb|p8(5qXKsQl=AQo3rU zSAj`r4CBYBgR&u-s&IB)x!lqTAwA^sihSCX(dN^Ss$=7`&mBTz_oZ^Em_S8(%g%0V z6?~2&XQ}lL$-u`LV7q5w6R|GmBlu{WRf@ay$@Kl97-UN63Q@3|*E^2?vvonR` ziBzMzEz{thDy_2}*+U^*Bs$>U=&67iIe)ru|3s-BnY%kFsvzhI#aPTvHO5xdoz!QF zXUbUfPFq8R;5T=81(VF*#&q&;LK(U+0_{#|uXc5QG9=@|NyN4h&!#9?8?qK)k11+6 zQEc&plWDWK#igVJ^U5Ni^0hMuGkma#4+CK}oob7pUj({5jB7ZIs@x`;=EjLk^dt2( z<<#l<%c3mQL&A-U8DZo5EdCga+%kLeBEouee5oN3%#ac-|Sd@l(*~hw^#Fw$!uxC@);7cD1Xqbqw*+CB>eEmOPhd2 zf1JqGv%fK2jB%yYsdv_-oNu;`C*8}7mH5}_$n+x%hfKg7#vf4pZ68E!u)v_piDebzi3jnYjn;7WZM3?TC#RWRe3Xk@0^m&Yiu4X>J^BD_6=O5`iwY}Q0 zyU#J}Mo4V3xxye27+7*^8k=6EotxXrq^egUlTw#7qIV@zwMCpS-~^}`Q2(kj|1%Kw z)A7*PV`E*Wz4>wT-tBW-E*cy8OLId6y&Hd#)%+x3qcAuxVdGdw_T}SH;Db_#LL2QL zuFR-Do=_cR_s9)J&E}`qrI>G{}fq5clG*;`u^%A9j5g!9bl~%O+n6i z?0(j=J>>J*_w`nBg@RT;x`|SAJwdI%mom3)0b@3a{?AAKC-2(xFSA*GT3v_1S;~%- z__`^X(JLcJ=C~nIb)i4?)hAlJwxRFaB93c+h^y^)pxFF->0`eUd;YtjI;b%um8(JDM_398;u3hC9gFayL}483q&-L35KWl zS}rJcLaFTvSMJUK{`gtoi^WVfB*5Yhr8C<2Z*R8eflGPw7`CX3WsLLlcsVq=X>UzI zHf=bj6u8c(Jz{q!$fnt>AM-7)b8GWNF^47P!6N@Pu#MzqH*BLs26cg_1TLeXveUnQ><2WaZ+_15gkw8^~nw%s;q4QrA8RaMQdxf39s zzmkGz*zG%-f*u9ln|j?y2xVXtKkn_f@@9P{>$4yagx+=+|GW0)Qme*s*7%E9T)`2# zZ*U&8hg1yqv`ubK)rqQ$pA&57yLjj^Y^5yiRLCiAi7q6kTD49-g_e)~07@KwH;#22 zo{^J9T@2bcPThrII*J5*tOTlp+LSuJ)8p34y5)kg^%k_EkMG?WrJr{y;k!-vZD3-< z;C5=C6M6R;;?|y+G>7*6ZkOO?g}znwSn_cu;TWZOJ!!gSL=bNuTgwisek_+XZ8D|1 z|H5S*ha9|F;1LbU`n_|lQe=kxy%>`i13u9pG`ptS$)SGq)0s6JfhJn1w+qumzp2M6 zrG32@E&$x1s7YW&k4ivImJAJv4g8V%{8q?%N+<0nPHDBCrT`nC8i>DsVI zHkU6|F4t#j(qh}5eZkM1&5gk}U(V?+BHw;@^|Zrs-F^L@uIYnw8WR}PAk*7m;@TS1 z1$7eMJ1k$Ze@rz*{L(Fio?;-Q{u&HAkt2+&OyZGzY9h}zLSw9IOBy+0L=bJ2w)kFcC`QYj$2SRVh&2k1yU{1t9CX@A5<5Em6(<*p z8H*Wi{__C^JvePI`)2-zIe(^vvk#GDMDZ|6lo_}pYB%TM&h2kw=_ZkMcweYY?`in_ zO_rLrr9j7qyi#w~AqDJ~Uj%lnHg?E1qNADC^&!qGA#FS`i_^VlHT`bcNT)nxSw)bi z(81Tz$H#ex=ihz7&@Vlm7e&_w$F(sS+>{-8YJ>XTy_BN#uFOk)K%i#!^yxkR;1bkm ze^zpo=TfP{lz~h~IMba@w!7cb@W%mldF7ti0R?Pep6M84&9g?o19F)tiKGmcU)FEV z`A36z+(ZrCuFAY>JM=rp$RSmxvSD%?&yAfSu2c}i&tz5*l(}0yF_JbSYO)buSzF+e zQa}h+bBfN~mHy&Ftjkfeh(O3{e*3%4%C|}+Qc@^|vQ%Bs$r7;C0q-szHOdnBDyE*U zQnznva`D-JxmzB>pIyBn!QlCnk;fJxLailT)9x@E)M9KJ0RqO|ze|zocf;ZQpZC!< z(PtndZ>a$gm4u$4CI?ue{a_&pPd~|5BEji_J5Z1OmC4Ehe!{qZ6z3B$n_AFTKX z^thhH=hy}ld#7KBYTeA-C|ar(;(zAJiLr~`z2Bhw6So&Q_H$0rr^QoQCI>^$P~G9j zUVg^aMGxQ^UZP#-5`6yQJl+3Q;eBfX1OS9=T&T3lL(PVAy!&>j;tKa=ckO%PTnfrBy-Rj5`8TER6x&vqi zvB;;ZHs9lYP9l8XLX&pkC7gaxSJZb@rX3cB38D7tJml;=mBuDAU2gL*Z5(@gt08d<; z5`~*{U>_HjKe*3U@ZFdG5ac@s^S#nAk{z}4RVB8yRDN|e@BDDdr$2%JXB2VZ`a%K= zLlkk4+pwGVf#aUfih>zVEh-z)n{U-~6i<1_NIi#j_)JP8uY7xuyP;9zuDw)egxhW~@H6Ex|c(8_I%Y~oxY}k5~^Cq`={36{v zGat0}we?w{iQcYQ=o4w=^}1eZyg)!T#q7yhalH|X7=}z}JbE8#c5B1|goiEvmZ&=w z1~2lK25`sy^>Q=i+WH%G=%N_JiP(}5jbx7cW{84Zd`U^#Lu|Q)SErhXQQa6yQ|%Tt z{2wo z4`U4S2@YFicU$C29}Ow;J1SB}ZluVcR@|P_y84gz+^+nnMb|p8(5qXKsQl=AQo3rU zSAj`r4CBYBgR&u-s&IB)x!lqTAwA^sihSCX(dN^Ss$=7`&mBTz_oZ^Em_S8(%g%0V z6?~2&XQ}lL$-u`LV7q5w6R|GmBlu{WRf@ay$@Kl97-UN63Q@3|*E^2?vvonR` ziBzMzEz{thDy_2}*+U^*Bs$>U=&67iIe)ru|3s-BnY%kFsvzhI#aPTvHO5xdoz!QF zXUbUfPFq8R;5T=81(VF*#&q&;LK(U+0_{#|uXc5QG9=@|NyN4h&!#9?8?qK)k11+6 zQEc&plWDWK#igVJ^U5Ni^0hMuGkma#4+CK}oob7pUj({5jB7ZIs@x`;=EjLk^dt2( z<<#l<%c3mQL&A-U8DZo5EdCga+%kLeBEouee5oN3%#ac-|Sd@l(*~hw^#Fw$!uxC@);7cD1Xqbqw*+CB>eEmOPhd2 zf1JqGv%fK2jB%yYsdv_-oNu;`C*8}7mH5}_$n+x%hfKg7#vf4pZ68E!u)v_pib=vO~oiM?{|K%x5Z3of7ecDJ+)RvOfdqa(|D}<*@wrSi%+S?j$rlgw_?N%e&5yqdT3j zUj$0yy=z3=XkgybnZuV`%tzpvolD6sKQN^uDi2(OOx3f9q<30>kzL zN6%ZIQ|oMJx+0o36mcx7G^;8jdIycm$0f5*IVDvL;@JrZPP$u{(k|vvt)i=&DfwiE z(nl7OHmSu{xqVH^wMwL)vRhx3%9$YM_w@cX-F)}brsIwhxk-_yk_ve`Nts+?ayWYR z`D6S1@mOe@RR4Eps{z<3A%6Qu5X*`}+hK(q^Sl3)%zRFS6+m()Y@-jK@(Y2r=Tf>G9K>(s&?Rt3z zXNM;xfLaMuZLkz1E9$$cl;dUx8F?^&_=b^Ca`PIc>iMcqXr}xwoFbb`i;EN$zW70A zK`$8cSOxz#k+ImW&+d(qd6647P0taRj#&Uth_ zB4zJL`sV5T>i)BUT$s5~twvAf&5)gsm%D1RM}fsHN;=Fq@yso??sdqQR^BQXZuSM| zS-+e+j-J(geKDD&tbHNZG>~7pBX7?u&kr*2?fvMjdRDm#^|A8JTQih(K3IJ37)+Gi zV+l1U-8=7i5BJlM)^QsyNU=*Gb7^8GlncWW%2b05Ef0(FwCW_g?&pk4ASM{M%^ z{Q}MM-9D_t#<|Y&oX{s;rKk)o3Tp*Y|E#M48W)(;MXU$W7|>Jkq@J#0WRJGpNhzUw zu{P0q?j4)H>$PE|ny}2PDYA?jdu6UP_eQUT)xhebso=c&t+dZ|GnvPJ$??zaCe1!Z zStV?Gv9A8ho3ZeOr!$K|o&|(r zky@4TYvaw^(D~ucAH(YLHLKBsD$vjb*+~8~zDRf{v(g<8Ylu4qrmRONU@a8}WIm?t zHxO9x@^uzRsZpvkODsYO#udZ+kDP9hV)WosQt&s?839er;x`E`redUcQrIwxmm@N` zb)B}Bm;Bu_O*XKn%$u$#q^9o<530|4;1=1vdTjA>KmFKLG5BEmIE=&Ts(#?fYM`?p zd4WRsl#FVohB%UsHyKKtx6Rwq2*tk{z9nJIPmIrzRg5P0g}!vrtF5J#Y2bR-~Q_e5t9UXLS z($v!O7LR!EVVd7m5hQ)=?CpPCFi=(gn$nJ6@gI@rEyxNu@`AQIFOGT4et7)2At1%d z?(GtBU0|GQ(`Q}l40o|3r}jn)Z*6jStLWtp!b27v+nwRSyO4+mJ!x!!Qg1;|wuRAO z-$zN~H$+4+Mn!!BQ+P_;a+K-BjjBUzn|va_o<`D3Vp+lRNxAR{=kc{~zzwFqMS=T# zjd%FBlSBC(L_$bBx*H~I^eY^M&c?csRTdshar6w~gfqaW?jn?sGL==AUt8db$*Dg5 z^$P3fapigG&7@TM+w67T%G2)W&_S&sjW}2ljkyW)u1Qw_tFhnq#0QA>*nlssRiE4f z2)L2onG}MQXCPL0AR5-pzl`-ZPaN|p5U#d0_!QnM6FQl$-@)Y(V(z6c!atND-oAo<=Q3wU z5TyodlpLXdtHb8y5@pbXeVZ{~R_R+gI?_~5{DWmXqCK8Tm9_CjCljx#5H5jD7OK!6^PJwy+bXyYPYnXY^kP{{JE!aq3s}n z(tJeLVRwO8)a~vc`y-+DAb|ogEL0_S&`t3zM<;|%)-@=q@aw-YX31#R6~$K4anBgz zUN0u2Ki|YcUQH4R;BmC!Aq}7zf!&zdTEup=@aU~<*2}TPsf9(K6(9%5x2|X9}{#U-alrIfgNmJ$nNLFf1C*Z z^FfIcFjTtrjy*cGj?X3YXjz?)#Q-#3GIVUqAi>lD+&SYBx_!sg#K*V9@vUz)+5GGA z!d2Jxpr~%OmsIBo+CJnisY`9fqM>nD6sKJr*wpXEq3S$(s3{}eg%P^rc2#2%8iOyJ zBhK0EA7ZS!X?A)P;Qq7^nLxX!t$N^&6f)%eVLNBH{d<<~?8N7sUn!a_t$DS;VR?xU z{WgH>HFZ+Nw2T=om1X1`y%dLG?DW7f3C@Jg^lICT$pwzZ=2$;9o~W(b&tirKJHJVP z1ra*ucp4~3$K_YAQMCO`!G{>fx~2ltSWR%T$heJ6)xZMmn10MgaB1x+BZVY;=v!w~ z$=Fw^S)`7wyc+ml8U-~eL+&V-&gGwtW_q3EYc1i(I!|zExgwq^2AeF z#OHtnTUnFTB_72@?C0By@lQq6qzBf&nUowc1`<9$tGE(MIVFLPInwZ<>~yvLeGa+@|g;aS8 zlQ~o90YH8>s-(SFQ7Nu}>B{~yaOG6C1%+2`L4|6-Q4EN@VJ?gt;gh1Y&gPqL8ON%XZ@XR9x&3hjrh$63RCXOd6gHOywf7TN9w>9dEGx*-bfcU z;3b9;>}#7J4lwSQr2{Vn#5KxN=%g~vn)jSd?v`u3eejtD0LqzqN2>2Q6T_bTom`7a zp0w886Pz4=91}EmRUmj`3#i}H6p0?`^QVFF1WDCu@>%Maz5qoMAsG)F7H1X0R0RBA z@e1^UT{tEoM5U{`^($jNk9~AalLZG=q@>D9-q%m3*y+;Arl|q+wAp#g6WKL_t62`E zdP0qx$36E7wF@_chIqX9X{hVo(uktqhenpaGVXsuY-X9mNI6>N-2UhLnX@uAzt@C- zl))Px(XhUx#9RMJJ7XS#00st=VsIVOX0QLj`z}Nx+ za@hdD1MhsaJ_rDymDE&KGKA;v7x;Tys)Y=7UDwHSCwnF*QGKoKzv!N~JjsOsiQjaz z5jSfL%Y`dK%jfA+mywVWPmPnR@?PW2)LKV6WzvdFgm$mKg|MzRaDyk&tUD}R(zH?7 zg6l=5v){xk=an&exloPFA@uKWyCLEM2L?B>mD0=Omz{raj!~i4f#n{v=B+aKdj1l( z&WF{Y>0TSXeN>0h?KfJEZ$-+}n!xxrkbhMzXA9!~FOsT_ZFucspdP>TfWAe?;SU@U z#eDsKstKpVI8S9*o5c`WY?ba*crtEKA^zf_pN+WU?-fp_*OT+|PAVi<>yiDCbLsbd zj$eu#d0qG4;by53f-S$#2$UXDL={p-ozQYkG&xB%<{L?aD9cz^HD^{Oytb`@nygiS zt_npD!NM%g^7clsbfzX~5-=2^R^{SZI1NGm9f1jPtSW;C3Bc+cgt7nPk5 zhN-ZuzBF+)T7(|Yiu5B}I5yS95ajl}9h>Tp-5P?0*}*|g z$JT=Ir>MA*@SiESrriE{t5K;5Shrs<3kzWiKYS)F6HDmLc##H5@u=3|9|K6b)eqyQ zc0~7;kpVZ>+EGtMS+qP#Zz}NfOtmx0*$Wdo&4P7n(W4NNT;$d~nb$uqgK9j*$wg|5J7c~ht+X@Z`kH0);%SgiD7+R8>!LC|(Z4Vnso7{I4$6uz9 z2ZT?POKUk;r_{B3!TYEaEPMdmY)h;w=*f)HeNRnBE!$re#x~;Z>t5SQO==12WGpMr zp=ZU*lx9h#p&6=77Pqy~_dyBp%`B+V7tifQ4~;i0 zH>;%xAa!gfsXjVT0%`yRAzEyw=dTkmepXazlZhl)!0h%a0UboNkba6{4?{0D0qJ0@ zYOLbtTr5u|OnA0bv13>nQ7Y8agyPwEn{9tw-OWfbYT5AbDWmg`a>a0oR2tr4c7I~< z`@VuhQ}*v^!WOC0%{97y2;(CuJ(v8G&#r}oW2sD!G9P~?rZqLU3<^oD)(lsDyCfg1 z^kUS3Q+5e)4<@)SB0I&SbFcRYAaF&#w!*Z+KX0n@hf7Y!c!go-FpeJDNnVK9r#1@n zG>76bs#F(0|De$V{A<`-;L~alpMO7X6O`9ojdCkwXt>Cu3$I027Ph!m&09J~Wp`?Q zhUNW4dt8u9BI5B@NBY9cz#q7lfw|pwl}6dG+8;M!)(y=rHGsdy}*Gl|aVML2oR-Iff;2@2Bjf56jYl1O)d^4e< zofYX$vS_-0+Y8-b!$Dc5r)Khv+BYf@w778oX=Fv*N-UShZ-=v*F- z@ia6K*g2CnSua?gx1D|GA(i3nf~@2AfOITHny_`VTYhy^w0_XNE@~U75Lvoi9MyI= zsu5Mm)xVXSJI%F@An_LZO5{*Lf?5jW*qtG&;G{0HHvy^4l4;BOXIh>6BKx{lBCU!X zto6UedU@JfM1y?HzcI(`HXiqV1(KYTc=fN;$j*MQjIPxb{RCX;F^8%Bavbz>?_FK$ zsT2g5uE+Ksj*JNF$kwmqu1(JoMX-lbt^~cyWuoURlaWgB)qKV4O7Ca5acG|L>jcCr zxg)%3pOfojTZ`^>8VMOP-m}cw9p@6bIwLIHAFP%o;~90@^Bk0xUPGeKLvngV`<`SY zCwv;HejpQ{YtIbc%UQ1|;a^*9^F^oQ>%CpqTOAt4e=}7c^FVhKJh9edvaW7Y=8RXA zq>Jmv;alWUM6jfn65JEARY1`}emGwp@3#uaI-t<;U{J(miZK8AJ>CJmK<{I>o&4-= z6g~VOK|8BJd9~~#%A!ZS7G2<@ZZ+A3uT58%Z=Abgp-ZWmUPccC?!SG|6&q6XLjV9?WBZ~V#pu{P{F{oflf$cNtKM~UTg zy?g9Q*m<~9hdTkuY)-)D+mC7-d3hhf;o!h7tRQhZ{KJ9BeFN$%`aSit8QC{BV;4bG zd@^j8gt+^1$=W)yX}7Hzd}Brrw2ibIo`y6#CQ56vz~7aC7X$iDO&vq2V!mLa5GZGl zW~N#$yCej&(FCEaPa5PmNtr+J8FXHIKP@cAJ=Xjg&`w`mMJC&4txT@0m_;)RY=JE8 zgkI9HyP=loEidHqXbWjb7v6MEAu@HMBJrr7Az%Egx8)WV{UPyw;&(4nxVqg*Rd1xs zy};Z63$ZFjmz%TWs+r@QC}zz}w#+I-p92pbOo4uL>#C2KW#f1U7QPFg?z!_v6kp}9 zuO${lq+^oC&h$PdbL@YZx7^G5CfYp|Ml#en(Vqm)?c+Y9JbCYD+%uFxFw_~6sdf|H zS1^p3nr|asbfrKkbIe;N%P(R-Fg87TMm{MQ3H1` z$Q|(#HSlhZW*khplq`fjGnzM4@Br@chJ-LucSX_=q`!caO%X2ICQ!g|)iL$VTRI8X zvOz{JDh$@`Y&W1)?5a)d?a%1(*#~<{n%y3?GL3m7Q;ICII@-F6s#jA=Vuyp^BgQFN zP>ogA+Hv0Z42$~&@7E?p`_kn(#!un7SMZacdsL-C>(1SgmH+Mgjo*5Bf4aKJt6Mz~ zdrSo%AUfdeW`M~A33Z0b_D92(ty2n&*(bK|>b-1UGUQ{gi&VxvChCsY*ilNSCb)Tq zRy!gL2tqiFKg#Wn9GsK)KBIuYm3~9~>A~^CL>4-t?(qzQNkavLbq3$gFR$>H?XIxL zmRwGF66d(#=b%Q~j7JrH#r)}q&t@S`wWv7>8A5}xZWM~*d4v^wz=pMZ=zM77eneQ3 zDV$pjW|r8A`a06s$#8 zy|s`1&Y?!iK(q+C$=x?I+ka^k5VN|k!L(>SKvDVWV0>%1n#nVSHT0Y_{f`RpfKFo9 zA(=7!wB@0h&i*5B9PLb8LR(1qa;&$o#n!-vb~$d@p>^*iJksn&+G&SyIzPg9y#QjS z**K5F4>|(3-0ls9Qm+Zs!m&`xhn8a_a#<^ySBsigzI?q&27<4p)_QDU#vu#QZ#!WL zS%EkoQDc*}X5*!)D4|_QN7zdpaohsoicD-#G}buV;J(>J1<-wG*Z-hrOXB?$P-;5B z9)2am2BtDoe8K#~xcGal53~LAiJf$PZP#}2D4*|=M_xn=rc_E~N8e!7Kl10;emC@) z|C%J7g4jP(jI#h!n(pxC)!`?dp2gg+bD}RkFZBzI@7)5nG^FjWJc_=|W3P{|-u1pp zf6{1XfH==d5isRr&U^6CaMBm|+~v_CU}w7>+i<^-Q$l=xYVJs4bDB;=1Z^s^u+I91 z30q%@s=PQ#POFH=V>z(cG5NxT9U%YJWg7b!Nl_t)h&jB;VX2@iQcfOon9Ukhzxd5D z_{iQqDYQe%RLNjL$j|GhXh;ew!KMtP;Z99TRriI(zdQ74O~js10h<58goD4%&e)ZU zT)lXzjU1=9xCfs{pa%k{JfpybK=GPzfG<+#5vU}KO?ffhB(>In4 zm>dg>CY4(YjEfRIkQPPRTGHPC=vU;U8(LXwO4{u%3OWZGJmw-a;Y38lLt-*iDM?|} zcx^(eivXerJR=+l-U6cSLAWy@={Xo zu+7yXm`$HXo1wp!0IyO1ePw=L9u=QQ+?AKy{>mt?>4@E9+gL_->94LzXUQZR z@UDJ^Y~S7-_p zXU)1C?>5=CvF&|hSv*Ed&KOM8c{mMOZ?J2N5x7`G9RTlVPGzzs zj+4k6k)-}>dj7M^A%SSG0HTzanihQMXF@enjrU!fMwyh>XGSK=oWCG|L0d&&&ecSx zi?HWStzCziwMlomA~gLNe6DcYtQ5Uo!VG z>d9v=ZG?BrZQlc-l|bDIF-#VZT8SJ$tJ)^Wa1g8ekNu1LXryVDH^d7yF=q;y4=Wx0xiQb}Lxabi?$*Cda1R;o) zAjc`u6WrJ5pLpLNp557JW@q-9*?DGXlMMAWXn^cM002OvrKx6oom>9{73pnQ^OO&Qb|juh85iNCRi3IH>~@%y^qan>}}0{}wr0RYi)0KngCDtZe52)qvf z;2i(}xjX>iwqH@F5gY(uSk+QfejHLnC=T-^a7Io23ftKLuip z6pu9qT8@rx^0$dgbjZN>3)WIo(3MIfmU-RrkkHP9>P+Tfi( z-?L}5Gs2t`(lE{HtMBD)bn{I!&zH4|E%u;N+*jF7G|jb^HxVqOtL`t6gea*Wt=_zJ zMh!8Ys!?pequyJzL7q;B=rwG~?e`^NbZ!1VX+JTc4yQG(SWlCoSNVwD1GItzg)Jw5 zupJ9eCv2}=CbPetdeSCHMyHliWrIBhl+`e=dyF1g`7uZsUMp>oU45FQQ|hNm({hy~ zyS_SbSbjy6Hl}=hDDX@VwD$X%eCK!cQjT{g4%6c>dq=};QESobVvb*ATP#og;YOK9 zr7zaRZ02viBHx7D;6W5-E7bUKh{}kH_v>C&$+l4oel63#*Js8Sw!OZs7{U{T5JV>D zlBaZAv)TPi3&7U6M*7_9wj@rJ4&AWBCCa~H15oiMzh({M`~FWjYqt%rNHLffq}6_s zY_SF#)M~2habP+8^WHSDe1%7h}0-MJ{3oqhj-j@Z0E+m^#!Yl>53T=Wb#(C6|nd6P(EkA2LU#eJ#%fY9%dO z5qUGYE}k+5{|pTXZ{?2~(M)G3E`9Da?hwDBTk(}M>JK@gu4_);Y%Cm&dJ>EmDPqxq?`3>t+#sSS|b5w7m=g>$V3iKj5bhm*5qM+~H1g z)%zFH?Y>P~Y7?7t@c0#^)3OC-684hOyWGPKp8=P0F|(H-ghu=<+Zb;am6+E8MgL6| zt%=kZs>i3m8xVL3w3J&aC;X5LZGkMuk!4;fSZ%DAEu(Y5=CCQ6QZA2(FG(0C+st#` z>Ga}o>ByZhv=mRL*tt;x0u<;*ka<)PoQTngtaW+4O6+-(%Ee%t2?zH+7~U`V_i5r5 zz^C@X^U=wo5eXdgC{iLDo9e6CaFMlT#gyndO;dC#ljkkfE-3_sC72T_5+R)|@!1U@ zB&_}8x2(vkihlZi9BXdNO~DMRuvv$uY(f6x?yuir=@(02TLyOmm4yUhMf=7$Q!pvU zNatnab=T{npJi&dZR1~;eUV_37QNDJJU5C7{ld-ztc|9wjFHr2*Cey#>E&D;#9E4L zkZox@cw|?SIhF-ESuRmDo7iZPRli6EuGGImVn{bQhia8>CF;%B1m7!o^^Aw+Nn>={ zkF1TImq;->hoO2mC1yjZa%u_@9-36jy`)e4E|*>}`GMmRhP<-mR3yA*Lp6fzl=bipIs%VYN=OLkz-GUZ}!`5uotY z4!$kNO~9%$kd|VF1EE+Y_?qgtez_w3UfL5>3EW9QK2;4Rees|P8on*5Sj9v6?6`y3 zy`ArKP)Izpdyq<634^1BgU=@jEgG*Mp3i8X;BSC>LoeJw7g!v0cfMRut`#Rxy8>$k zz`(eA%@k(ae3O3Kw|7>oWhw|GX5))@bln`$4Bk_oS;3p^0Y`T9e< zeXYAkYB}I&N;cGzR&=<&qp9FoZcT`^I%+}U& zO$hRAjYGwLG)}#q8{D1hOl&x4mitvBqvam+RrNNUgh&(sG&ijg58BJQTf7HM_OgVJ;; zw?B9Syv-L|q2YIWezt#jc2x}@&jXT3i}#yYH~I>p<$QALs#d#iaJKK_n!N#+zUN~NjeOvjAYOJfun-u6fakron6*v&0fn!J9ZX$J6d88u|6E61N9zZE($k9lvEY?zcKT^Z6&Wt? zvoKI0LV7`{>IlD?(G1ucjjsj+N$FrmsjRS`K*pf)0_#^a1+kQ_lR-A&scMucGTc|&#h(_vw}NepmqmT=9c(%7T(QGD8j4`QvY_h*7yDdxx{f&rE? zy|1a23#cHe%N2 za=Fh%I^s>xyNOOU@M7=%Ec6O>KLYYWCVIghnNK_`Q%sp8^dIXy^Q8WcUNPig{n*Pn zywDv)mH2WN1tUB~WFe#R^FG;49NQE9cz|b4b&C8c%fp6!u-(pJ$_I)cJxWJc&T-A$ zN2DwHC!?Cd&0=eR9lGol6=KO$>$g7wACB3>&PK~!^E0%NJ0o38%ZjqZr+2B};lLg3 z96N#{o0L@M5zwUdyG(_zWI|)V!dYyrXa)Zq+#%H4&3X|n~E?d z8LT9}29l~7?N@R-7Su|jq(}EG@gP60lp_vQ9d_FE1wKrTII5mEje&(`ef{{H7IP<8 zS5Smx8NZp+mzf>A3Ns~fh2`m6=n8(ZtiU5CV!ix0(qePAEtY~xP zQc=qlb}xcJH3fbB-USiKK_|L%OIe4a=N|PG$iu2SC)t09GcG)6hNoHk)Dkx|*O(4DUzJtnLw?qfq>qEiO6Z-0g51rT>@%q`r zz9=*)FM>5y^`kE3=_P5`?#)i--1S8WypfAeq~i!&$Si$TVMO)Y?8u9b6*cnk^kGQ| z(*0&nkwa60mIAF-i3^_+?cis(9skR4f_r$MUsYrfxG_b!rFK8<;Jni2N}WOF=+>d; z5l*DNH}1)+K$vS|&F!}m_or5R{7O{EB_uM){2=M`9p`s%QCdJnff^cqn|Am1BA6sr zqQS5{vDc;dNI~5Zz+NI>#`yFe-RfVW6EkuoY88$udxgZ7|4TY5>m>}&Os*OxI~s`} zIgA}{xt#P2x4ilFrQW38|57)?{2Pwm>x!lXZgwHP{Aa)T)bFd#Ip@d(^;+IvK7^bi0+etI?a&5V>!uYi0**+2(QcqTDpTybF zQ7x3wS88-;@^s~ZKcp#ld134P$;%>UM?|B7ocwZEr|xY7&S8^b9P58(qQ@^i?Wo5L zX@g3;i_!^EqfA0Jne2XJ-$bzHiyxZcR{GslY&(#kd7C%++iYH)U7Oo)>i;GGC`)?FaznB5%5koigP-KdF2J1WG>G6Kx6uk zIc+9_33RLq{cz}eii-XNZS6)dOckL8>UXYX| z8%6!vx)+7E+3CfA-wAdkQfd>8-7F4Q6Q;CVH@Wuk1!S2Bz!&IJ{u$|y7IjVwY@YOu zCcYF^+L&weBQo2sEyHvuC(~sXer6)EZ&jjx2>T6U_&3$7F;I9_IWpl=D z>tI}!_q`}~_;6u2Rzo^M?%q0BZUV#^m>L2_DX~zIB**SQBntWNziQ~Xc6Y;+@ zcHDFhxb+kUxn`kD-{XLx?HUKV>b!BFXpl;okO}2R97xHCB%)OYK817^596weu8H62 zoC$I+{5t0%Q@kl+xosw`Bot|p8HC3spO%XLb*7GPt@mKIr38$yj%5>;tMH`n7KEO) zSXFBn1bgHc(JU1}Rj5YDsp|!c$p?}K2nlWas)Z3&xn}HW3kL(Wo$)L|pNx{l-o8+l zc;if@u^gAJ%-r8VgEu3a(&l3)?$=V6>T2YVz23PT3;X#;kn=i7-7GLB(-sWndo4G! zIhRHVQY~DX+mLMATwtX4uRr4b`Um z?$z!Tv^ccdWby=3QU_AWNqVRvnKNbcZ_Nnr?%3tiwludZSyK4#Tf_c#lG_$mX1%MI zkqa*p@0(c>_j6>mvxLnqvu@sNa5lt>`9MT8>`%bLi%SdtM>op{@%(4^s{~{3G&JSb zx7}ITNsz9IWS*(e4%OUK0KqO)I`+!YiT2q=+M}bH$k6vjJQNQP%51#ya)sK9u41*5 z+%qc#T!f*#K%~3UsG-L>=f&sMD_0=#c4zd3O DjNWSt literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20.png new file mode 100644 index 0000000000000000000000000000000000000000..7dfb2b509905a7a659b1a3f71dc3f2d8427b4d5d GIT binary patch literal 439 zcmV;o0Z9IdP)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUzHAzH4R5*>zlRZnrP!xurq!p`1K@kjy7)5bX5EMdJom?e*e}Lfb zDEJTj0iuh8I7k)T90chgD2fO{NI@vlLQ6qNHR*L|+B9iGB_Q}t=bU>U&U?QkZEk_2 zk`t4E2|GhG_PCw|E^fR^jY_6^SKKt?dZgY1`%;!ldOlPf`M8>V+U-BNymHtI#4;j_ zmgA&Vs0bCn%9?<-zZe0Fpo?{lLax2HBflPx#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBU!B1uF+R7i>Kl}|{MVI0RlkMC@@O>GTkYPm8MVog~?s3ZdG)H%;~ z>ugYg1s$Z5E$GlCIuvw>Jaj4w)PV&R5n`;eJj76`E!Jk)$ZhZYcG#Nw?A`kg!-LT8 z^giF``};g!e(yiAcV7paP&JzY{m(6xtxDfw-$Z_Uo6z&Sr;gO*(w6?J^2SciHO_^7 zx#p&MVLQlsjf>o=l+$3fFtpwps4odl^sE6{IUDvh|sbd;Vv9|uG*VS zPW6pAine66O0A z>$y{aN@WvV2yo7iDBH?(^~Z*zvR_&&+3)$f7D;@2g@did>u@iVUpVn}eb>)~Mc(>k zL{dgi7opdp+?)mIZ9E7gd*WrP>odto%xvBwy+BH2^71x=5^{qfnMTI+{djYZ< zBQxb*0Oz|$<)Yzy|K`!Mdh!kNxRGbIgsxSUSFZO9d|0H_UAr@#^98N_g~JTLld9tS z+Hu*Ddg)3=YSpE3L(bt@4i04er<(2!+A^tXx&K~U?SH)0xPJgQccD|#Az)ho0000< KMNUMnLSTZ>A~^>D literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29.png new file mode 100644 index 0000000000000000000000000000000000000000..8acc0b68c90cb679011b51d71d6cf9c72279dae5 GIT binary patch literal 676 zcmV;V0$crwP)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBU!B1uF+R7i>Kl}|{MVI0RlkMC@@O>GTkYPm8MVog~?s3ZdG)H%;~ z>ugYg1s$Z5E$GlCIuvw>Jaj4w)PV&R5n`;eJj76`E!Jk)$ZhZYcG#Nw?A`kg!-LT8 z^giF``};g!e(yiAcV7paP&JzY{m(6xtxDfw-$Z_Uo6z&Sr;gO*(w6?J^2SciHO_^7 zx#p&MVLQlsjf>o=l+$3fFtpwps4odl^sE6{IUDvh|sbd;Vv9|uG*VS zPW6pAine66O0A z>$y{aN@WvV2yo7iDBH?(^~Z*zvR_&&+3)$f7D;@2g@did>u@iVUpVn}eb>)~Mc(>k zL{dgi7opdp+?)mIZ9E7gd*WrP>odto%xvBwy+BH2^71x=5^{qfnMTI+{djYZ< zBQxb*0Oz|$<)Yzy|K`!Mdh!kNxRGbIgsxSUSFZO9d|0H_UAr@#^98N_g~JTLld9tS z+Hu*Ddg)3=YSpE3L(bt@4i04er<(2!+A^tXx&K~U?SH)0xPJgQccD|#Az)ho0000< KMNUMnLSTZ>A~^>D literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40 1.png new file mode 100644 index 0000000000000000000000000000000000000000..a2244fa6ef201110e45e2ec0cc4f2dda0a7e1290 GIT binary patch literal 915 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 Hu0mjfAGpPf literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-58.png new file mode 100644 index 0000000000000000000000000000000000000000..42a140308a4e750fe43308fb4337fe8733450d47 GIT binary patch literal 1505 zcmV<71s?i|P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBU%Ur9tkRA_ zDdklOXjP;NaVhlB9x8#v1#v)?IJO)R5(hX0^@IefN@+_A6cHk&Ee8lqRFtMRc{I+0 z#7^v8du{J<$is={-5EPJsFMH6VRrYM`F{Q~^UXKATuZPOkB<2N8=y4RaI2{rZZ%cI zt)^YJh#Y5M(b zhxT_xTvQBqcE{O;z;S@M#=cS~ukzV>!yTG$Ihj*8^Vx*|)?tS}zJ4#2!A*JhO?Gs1 zrqOigxO1U@vy!?rA7pm2(H+cGXOmI&&k>_5nL;O5R{FB z@AH|sQTIf;zJmxZ!i(#1SAKMp{@4E5{vi?6Qad{5`WpW@#b*;`I2Gy}yk&IDsKLqI zU!tr~WtGRWYKv>~Y8idYoDBM==gRB2+`Y5Bj%DUuyD8WRvKbHn4M>7Z07*b!lY9v$ zl;^ixTuv(fpdO@cxm^d;t%5Xw1ds%m1j?DW03$Bp<+K_!l%S!u7)sD^2A~GGqLxk8 zqey@k@GkxUA<}VAnz=7c3`)^KX<|T*4seD>C81}AnEzPJccfsig5u-UKoQ&yz)5vl zOoVuPKWq5uyuB4=siih-kNJ)kvf5~wmAS*RI4*wRRH|iC;1VkUpMn!@xce%; z*bfIo&J-4=>vrNN0d1l{PoMj1z*7_c{>{A4LLO7Q>y_~vvW zYmUHNTjd_~!A;u)#i@Jt1{7S5WQ9!J-sZ)29Nd<_{5guyML4=4bYt&#xI4FWe_TT% zV~e84`Ao{*7d`JvdRP~C+8Dgsm2cm8i07~8w}!rH?EV(-wPT}>^W z8EnsP6qV#nJ z#sX5(!pVkxR!=YYqh| z9Txji-w*;`Y;wb+!mWTW;ULVX=vDcON_dN-%=ag~2P_|t&k7Iz614}i7M9F$cof4Q zGJGMUIh3mp*qr2@4|>@dO2GAwb*EjeS=qOE$>)+{GAwCfNe@d}L`*+i6Vn;EhJRUX znn1=IH2fjM|G41`Wt!Vf=hA;km>cz8Im^ya+WTDsq?A?wQfh=Z(;Qdw+YbPQ5K~l) zrfuNh9Pj5!`Os3yPbvR_9F00000NkvXX Hu0mjfAGpPf literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60.png new file mode 100644 index 0000000000000000000000000000000000000000..9a5536ab05fbf2aef9768a9a6e39d2a0ae120b40 GIT binary patch literal 1555 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-80 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-80 1.png new file mode 100644 index 0000000000000000000000000000000000000000..4b1f229acf43f4a7cc4cc97de9ab038cee65e07b GIT binary patch literal 2110 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 X{$%j}UP;DE00000NkvXXu0mjf-MK&- literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 7353c41ecf9ca08017312dc233d9830079b50717..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmV+?0oeYDP)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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 3e23e9c..b2f451e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,10 +2,12 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Shopping Assistant Mobile Client + Cartaid CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -24,6 +26,8 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +45,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/pubspec.lock b/pubspec.lock index c20e2af..86567a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" connectivity_plus: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nm: dependency: transitive description: @@ -497,18 +497,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -694,5 +694,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.4 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" From eb4dbbd28bd456c41f50c158258c54f56746dda6 Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Sat, 16 Dec 2023 12:27:06 -0500 Subject: [PATCH 25/39] SA-226 Prepare for Google Play deployment - Add App icon - Change app name - Sign the app --- android/app/build.gradle | 15 ++++++++++++--- android/app/src/main/AndroidManifest.xml | 2 +- .../MainActivity.kt | 2 +- android/app/src/main/res/ic_launcher.png | Bin 0 -> 11502 bytes .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 5371 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 3433 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 7415 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 11502 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 15442 bytes android/app/src/main/res/values/colors.xml | 4 ++++ android/app/src/profile/Icon-96.png | Bin 0 -> 3721 bytes ios/Runner.xcodeproj/project.pbxproj | 6 +++--- 12 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 android/app/src/main/res/ic_launcher.png create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/profile/Icon-96.png diff --git a/android/app/build.gradle b/android/app/build.gradle index 3cbfab0..41132a6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - namespace "com.example.shopping_assistant_mobile_client" + namespace "com.shchoholiev.shopping_assistant_mobile_client" compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion @@ -42,7 +42,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.shopping_assistant_mobile_client" + applicationId "com.shchoholiev.shopping_assistant_mobile_client" // 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 @@ -51,11 +51,20 @@ android { versionName flutterVersionName } + signingConfigs { + release { + keyAlias 'cartaid' + keyPassword 'xxx' + storeFile file('/Users/shchoholiev/Desktop/Cartaid/release-key.jks') + storePassword 'xxx' + } + } + 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 + signingConfig signingConfigs.release } } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7fc7dc0..5756457 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ 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 Z{|7%U{J{zAShWBE002ovPDHLkV1k~>9994T 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{ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..e6f38a5562b66292a4da4bf1b46d48517ee14a64 100644 GIT binary patch literal 3433 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 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..213d2dcb89bfbd072146da7bf4937e1e834a4132 100644 GIT binary patch literal 11502 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 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372eebdb28e45604e46eeda8dd24651419bc0..f16e9bb81052033230eaf258e8358bba29f3243b 100644 GIT binary patch literal 15442 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 diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..de3775f --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #202124 + diff --git a/android/app/src/profile/Icon-96.png b/android/app/src/profile/Icon-96.png new file mode 100644 index 0000000000000000000000000000000000000000..ab3e70f663dc1e67b3b9e71c0b1353b42fa638f0 GIT binary patch literal 3721 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 Date: Sat, 16 Dec 2023 13:06:05 -0500 Subject: [PATCH 26/39] SA-226 Add Internet permissions --- android/app/src/main/AndroidManifest.xml | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5756457..e0c875c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,4 +30,5 @@ android:name="flutterEmbedding" android:value="2" /> + diff --git a/pubspec.yaml b/pubspec.yaml index cb6ac6d..9e4c749 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # 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 +version: 1.0.0+2 environment: sdk: '>=3.1.4 <4.0.0' From 947154c59a5c86117be9ec4660635a6f3e683a34 Mon Sep 17 00:00:00 2001 From: Mykhailo Bilodid Date: Sun, 17 Dec 2023 01:34:22 +0200 Subject: [PATCH 27/39] SA-178 product selection added --- assets/icons/empty-star.svg | 10 ++ assets/icons/half-star.svg | 9 + assets/icons/heart.svg | 4 +- assets/icons/star.svg | 10 ++ lib/models/product.dart | 14 +- lib/network/product_service.dart | 31 ++++ lib/screens/cards.dart | 279 ++++++++++++++++++++++++++++--- lib/screens/chat.dart | 3 +- pubspec.lock | 32 ++-- pubspec.yaml | 8 + 10 files changed, 352 insertions(+), 48 deletions(-) create mode 100644 assets/icons/empty-star.svg create mode 100644 assets/icons/half-star.svg create mode 100644 assets/icons/star.svg create mode 100644 lib/network/product_service.dart diff --git a/assets/icons/empty-star.svg b/assets/icons/empty-star.svg new file mode 100644 index 0000000..35785c4 --- /dev/null +++ b/assets/icons/empty-star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/half-star.svg b/assets/icons/half-star.svg new file mode 100644 index 0000000..a22bcee --- /dev/null +++ b/assets/icons/half-star.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/heart.svg b/assets/icons/heart.svg index ddfb53e..4915c98 100644 --- a/assets/icons/heart.svg +++ b/assets/icons/heart.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/star.svg b/assets/icons/star.svg new file mode 100644 index 0000000..77a992f --- /dev/null +++ b/assets/icons/star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/models/product.dart b/lib/models/product.dart index df75e1e..f024e27 100644 --- a/lib/models/product.dart +++ b/lib/models/product.dart @@ -1,3 +1,5 @@ +import 'dart:ffi'; + class Product { Product({ required this.id, @@ -5,8 +7,10 @@ class Product { required this.url, required this.imageUrls, required this.rating, - required this.price -}); + required this.price, + required this.description, + required this.wasOpened, + }); String id; String name; @@ -14,6 +18,8 @@ class Product { List imageUrls; double rating; double price; + String description; + bool wasOpened; Product.fromJson(Map json) : id = json['id'] as String, @@ -21,5 +27,7 @@ class Product { url = json['url'] as String, imageUrls = json['imageUrls'] as List, rating = json['rating'] as double, - price = json['name'] as double; + price = json['name'] as double, + description = json['description'] as String, + wasOpened = json['wasOpened'] as bool; } \ No newline at end of file diff --git a/lib/network/product_service.dart b/lib/network/product_service.dart new file mode 100644 index 0000000..54d8c93 --- /dev/null +++ b/lib/network/product_service.dart @@ -0,0 +1,31 @@ +import 'package:graphql/client.dart'; +import 'package:shopping_assistant_mobile_client/models/product.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +class ProductService { + final ApiClient client = ApiClient(); + + Future addProductToPersonalWishlist(Product product, String wishlisId) async { + final options = MutationOptions( + document: gql(''' + mutation AddProductToPersonalWishlist(\$wishlistId: String!, \$dto: ProductCreateDtoInput!) { + addProductToPersonalWishlist(wishlistId: \$wishlistId, dto: \$dto) { + + } + } + '''), + variables: {'wishlistId': wishlisId, + 'dto': { + 'wasOpened': product.wasOpened, + 'url': product.url, + 'rating': product.rating, + 'price': product.price, + 'name': product.name, + 'imagesUrls': product.imageUrls, + 'description': product.description, + }}, + ); + + final result = await client.mutate(options); + } +} \ No newline at end of file diff --git a/lib/screens/cards.dart b/lib/screens/cards.dart index 542a20c..8264211 100644 --- a/lib/screens/cards.dart +++ b/lib/screens/cards.dart @@ -1,18 +1,86 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:shopping_assistant_mobile_client/models/product.dart'; +import 'package:shopping_assistant_mobile_client/network/product_service.dart'; + class Cards extends StatefulWidget { + final String wishlistId; final String wishlistName; - List products = []; + List inputProducts = []; + List products = []; - Cards({required this.wishlistName, required this.products}); + Cards({required this.wishlistName, required this.inputProducts, required this.wishlistId}); @override _CardsState createState() => _CardsState(); } class _CardsState extends State { + int currentProduct = 0; + ProductService productService = ProductService(); + List cart = []; + + @override + void initState(){ + for(String productName in this.widget.inputProducts){ + this.widget.products.add( + Product( + id: '', + name: productName, + url: 'link', + imageUrls: [], + rating: 0.0, + price: 0.0, + description: '', + wasOpened: false, + ) + ); + } + } + + Widget buildRatingStars(double rating) { + int whole = rating.floor(); + double fractal = rating - whole; + + List stars = []; + + for (int i = 0; i < 5; i++) { + if (i < whole) { + // Whole star + stars.add(SvgPicture.asset( + 'assets/icons/star.svg', + width: 19, + height: 19, + )); + } else if (fractal != 0.0) { + // Half star + stars.add(SvgPicture.asset( + 'assets/icons/half-star.svg', + width: 19, + height: 19, + )); + fractal -= fractal; + } else { + // Empty star + stars.add(SvgPicture.asset( + 'assets/icons/empty-star.svg', + width: 19, + height: 19, + )); + } + } + + return Row( + children: stars, + ); + } + @override Widget build(BuildContext context) { + bool hasCards = widget.products.isNotEmpty; + bool isLastProduct = currentProduct == widget.products.length; + return Scaffold( appBar: AppBar( title: Text(widget.wishlistName), @@ -21,33 +89,177 @@ class _CardsState extends State { body: Center( child: Stack( children: [ - // Back Card with increased rotation effect - Positioned( - left: 10, // Adjust the left position as needed - child: Transform.rotate( - angle: -5 * 3.1415926535 / 180, // Rotation angle - child: Container( - width: 400, // Increased width - height: 600, // Increased height - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - border: Border.all( - color: Color(0xFF009FFF), + if (currentProduct < widget.products.length - 1) + Positioned( + child: Transform.rotate( + angle: -5 * 3.1415926535 / 180, + child: Container( + width: 300, + height: 600, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + border: Border.all( + color: Color(0xFF009FFF), + ), + boxShadow: [ + BoxShadow( + color: Color(0xFF0165FF), + blurRadius: 8.0, + spreadRadius: 0.0, + offset: Offset(0, 0), + ), + ], ), ), ), ), - ), - // Main Card - Container( - width: 300, // Set the width of the main card - height: 500, // Set the height of the main card - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - border: Border.all( - color: Color(0xFF009FFF), + Positioned( + child: Container( + width: 300, + height: 600, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + border: Border.all( + color: Color(0xFF009FFF), + ), + boxShadow: [ + BoxShadow( + color: Color(0xFF0165FF), + blurRadius: 8.0, + spreadRadius: 0.0, + offset: Offset(0, 0), + ), + ], + ), + child: Column( + children: [ + Container( + height: 300, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/product_image_placeholder.jpg'), // Replace with your image path + fit: BoxFit.cover, + ), + ), + ), + SizedBox(height: 10), + Text( + currentProduct < widget.products.length + ? widget.products[currentProduct].name + : "The cards ended.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + if (currentProduct == widget.products.length) + Text( + "Swipe right to show more or left to exit.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 10), + if (!isLastProduct) + Column( + children: [ + Text( + currentProduct < widget.products.length + ? widget.products[currentProduct].description + : "Product description not available.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildRatingStars(currentProduct < widget.products.length + ? widget.products[currentProduct].rating + : 0.0), + SizedBox(width: 20), + Text( + '\$${currentProduct < widget.products.length ? widget.products[currentProduct].price : "-"}', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: currentProduct < widget.products.length + ? SvgPicture.asset( + 'assets/icons/x.svg', + width: 30, + height: 30, + ) + : SvgPicture.asset( + 'assets/icons/exit-cards.svg', + width: 30, + height: 30, + ), + onPressed: () { + if (currentProduct < widget.products.length) { + showNextProduct(); + } else { + Navigator.of(context).pop(); + } + }, + ), + IconButton( + icon: SvgPicture.asset( + 'assets/icons/back.svg', + width: 30, + height: 30, + ), + onPressed: () { + showPreviousProduct(); + }, + ), + IconButton( + icon: currentProduct < widget.products.length + ? SvgPicture.asset( + 'assets/icons/heart.svg', + width: 30, + height: 30, + color: Colors.blue, + ) + : SvgPicture.asset( + 'assets/icons/add-products.svg', + width: 30, + height: 30, + ), + onPressed: () async { + if (currentProduct < widget.products.length) { + if (!cart.contains(widget.products[currentProduct])) { + await productService.addProductToPersonalWishlist( + widget.products[currentProduct], + widget.wishlistId, + ); + cart.add(widget.products[currentProduct]); + } + showNextProduct(); + } + }, + ), + ], + ), + ], ), ), ), @@ -56,5 +268,20 @@ class _CardsState extends State { ), ); } -} + void showNextProduct() { + setState(() { + if(currentProduct < widget.products.length) { + ++currentProduct; + } + }); + } + + void showPreviousProduct() { + setState(() { + if(currentProduct > 0 ) { + --currentProduct; + } + }); + } +} diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index b8f5f9a..6fae431 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -206,10 +206,11 @@ class ChatScreenState extends State { setState(() { if(_searchService.checkerForProduct()){ + messages.removeLast(); Navigator.push( context, MaterialPageRoute( - builder: (context) => Cards(wishlistName: this.widget.wishlistName, products: this._searchService.products,), + builder: (context) => Cards(wishlistName: this.widget.wishlistName, inputProducts: this._searchService.products, wishlistId: this.widget.wishlistId,), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 86567a6..19bca57 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -244,10 +244,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" http_parser: dependency: transitive description: @@ -396,10 +396,10 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" platform: dependency: transitive description: @@ -468,10 +468,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_windows: dependency: transitive description: @@ -569,10 +569,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -593,18 +593,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" uuid: dependency: "direct main" description: @@ -681,10 +681,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -694,5 +694,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index cb6ac6d..42a23d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,14 @@ flutter: - assets/icons/wishlists.svg - assets/icons/start-new-search.svg - assets/icons/settings.svg + - assets/icons/heart.svg + - assets/icons/x.svg + - assets/icons/exit-cards.svg + - assets/icons/back.svg + - assets/icons/add-products.svg + - assets/icons/star.svg + - assets/icons/half-star.svg + - assets/icons/empty-star.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware From 31833cc819853e6d6e1ad5648b4a240a7e99f51a Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 17 Dec 2023 16:12:06 +0200 Subject: [PATCH 28/39] fix sending of messages --- lib/network/search_service.dart | 4 ++-- lib/screens/chat.dart | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 3d63048..e3556a0 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -62,7 +62,7 @@ class SearchService { } Future startPersonalWishlist(String message) async { - + logger.d('WISHLIST ID: $wishlistId'); if (wishlistId == null) { final options = MutationOptions( document: gql(startPersonalWishlistMutations), @@ -81,7 +81,7 @@ class SearchService { } Future sendMessages(String message) async { - + logger.d('WISHLIST ID: $wishlistId'); if (wishlistId != null) { final sseStream = client.getServerSentEventStream( 'api/productssearch/search/$wishlistId', diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 6fae431..7d487f4 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -114,6 +114,7 @@ class ChatScreenState extends State { Future _loadPreviousMessages() async { final pageNumber = 1; final pageSize = 200; + logger.d('WISH LIST ID: $widget.wishlistId'); appBarTitle = Text(widget.wishlistName, style: TextStyle(fontSize: 18.0)); try { final previousMessages = await _searchService.getMessagesFromPersonalWishlist(widget.wishlistId, pageNumber, pageSize); @@ -220,7 +221,7 @@ class ChatScreenState extends State { void _sendMessage() { final message = _messageController.text; - + logger.d('WISH LIST ID: $widget.wishlistId'); if (widget.wishlistId.isEmpty) { setState(() { messages.add(Message(text: "What are you looking for?", role: "Application")); @@ -231,6 +232,7 @@ class ChatScreenState extends State { setState(() { messages.add(Message(text: message, role: "User")); }); + _searchService.wishlistId = widget.wishlistId.toString(); _sendMessageToAPI(message); } From 374b18a364c40ce69d7cfe21a1d7392d5f19cef0 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 17 Dec 2023 22:00:06 +0200 Subject: [PATCH 29/39] add personal account deletion --- lib/main.dart | 5 +- lib/screens/settings.dart | 127 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 lib/screens/settings.dart diff --git a/lib/main.dart b/lib/main.dart index 6b06b96..a63570c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:shopping_assistant_mobile_client/screens/chat.dart'; +import 'package:shopping_assistant_mobile_client/screens/settings.dart'; import 'package:shopping_assistant_mobile_client/screens/wishlists.dart'; void main() { @@ -19,7 +20,7 @@ class MyApp extends StatefulWidget { static List _widgetOptions = [ WishlistsScreen(), ChatScreen(wishlistId: '', wishlistName: 'New Chat', openedFromBottomBar: true), - Text(''), + SettingsScreen(), ]; static const Color _selectedColor = Color.fromRGBO(36, 36, 36, 1); @@ -29,7 +30,7 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - int _selectedIndex = 0; + int _selectedIndex = 1; void _onItemTapped(int index) { setState(() { diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart new file mode 100644 index 0000000..cee9b3a --- /dev/null +++ b/lib/screens/settings.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:graphql/client.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + var client = ApiClient(); + + @override + void initState() { + super.initState(); + } + + void _showAccountDeletionConfirmation() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Confirm Action'), + content: Text( + 'Do you really want to delete your account? You will not be able to restore it.'), + actions: [ + TextButton( + child: Text('Yes'), + onPressed: () async { + await _deleteAccount(); + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + Future _deleteAccount() async { + var prefs = await SharedPreferences.getInstance(); + + const String deleteAccountMutation = r''' + mutation deletePersonalUser($guestId: String!) { + deletePersonalUser(guestId: $guestId) + } + '''; + + MutationOptions mutationOptions = MutationOptions( + document: gql(deleteAccountMutation), + variables: { + 'guestId': prefs.getString('guestId'), + }); + + await client.mutate(mutationOptions); + + prefs.remove('accessToken'); + prefs.remove('refreshToken'); + prefs.remove('guestId'); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric( + vertical: 30, + horizontal: 20, + ), + child: Container( + height: 85, + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 20, + margin: EdgeInsets.only( + left: 15, + ), + child: Text( + 'Account', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + GestureDetector( + onTap: _showAccountDeletionConfirmation, + child: Container( + height: 55, + width: double.infinity, + margin: EdgeInsets.only( + top: 10, + ), + padding: EdgeInsets.only( + left: 15, + ), + alignment: AlignmentDirectional.centerStart, + decoration: BoxDecoration( + color: Color.fromRGBO(234, 234, 234, 1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + 'Delete Account', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ), + ); + } +} From f365654bb510a3968abf8090f6c277590774ae86 Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Mon, 18 Dec 2023 20:18:28 +0200 Subject: [PATCH 30/39] bugs fixed, now products are displayed correctly --- lib/models/product.dart | 12 +++++------ pubspec.lock | 48 ++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/models/product.dart b/lib/models/product.dart index f024e27..6930f02 100644 --- a/lib/models/product.dart +++ b/lib/models/product.dart @@ -1,4 +1,4 @@ -import 'dart:ffi'; +//import 'dart:ffi'; class Product { Product({ @@ -25,9 +25,9 @@ class Product { : id = json['id'] as String, name = json['name'] as String, url = json['url'] as String, - imageUrls = json['imageUrls'] as List, - rating = json['rating'] as double, - price = json['name'] as double, - description = json['description'] as String, - wasOpened = json['wasOpened'] as bool; + imageUrls = (json['imageUrls'] is Null) ? [''] : json['imageUrls'] as List, + rating = (json['rating'] is int) ? (json['rating'] as int).toDouble() : json['rating'] as double, + price = (json['price'] is int) ? (json['price'] as int).toDouble() : json['price'] as double, + description = (json['description'] is Null) ? '' : json['description'] as String, + wasOpened = (json['wasOpened'] is Null) ? false : json['wasOpened'] as bool; } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 19bca57..bb2f44b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" connectivity_plus: dependency: transitive description: @@ -244,10 +244,10 @@ packages: dependency: "direct main" description: name: http - sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.0" http_parser: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" nm: dependency: transitive description: @@ -396,10 +396,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "5.4.0" platform: dependency: transitive description: @@ -468,10 +468,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.1" shared_preferences_windows: dependency: transitive description: @@ -497,18 +497,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" typed_data: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.0" url_launcher_windows: dependency: transitive description: @@ -649,10 +649,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -681,10 +681,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.3.0" yaml: dependency: transitive description: @@ -694,5 +694,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.1.4 <4.0.0" + flutter: ">=3.13.0" From 6cbe514192a76d00c8a57f30a235d50f83a3c2c2 Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Mon, 18 Dec 2023 23:56:17 +0200 Subject: [PATCH 31/39] default image added --- assets/img/default-white.png | Bin 0 -> 1145 bytes lib/screens/cart.dart | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 assets/img/default-white.png diff --git a/assets/img/default-white.png b/assets/img/default-white.png new file mode 100644 index 0000000000000000000000000000000000000000..d13a4df7070961f8659464db2b3edbef71aa9fab GIT binary patch literal 1145 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)K$^Ea{HEjtmSN`?>!lvI4mVo-U3d z6>)E`ZWLrtK-Vt>Z}%gv&mA_a|U$M4Lv+{?8_{72$ftqpMx*65o>G=Ap|Tgc?n zm>?K>`%O}6gXV>!NxNQ!H!oE`z``MRIds32jxD2_!-g5_eqBv?$!WmYbhPB^dNyzW z22KH;WvlBS-kmpq~d}#z_NtF)78&qol`;+05&Yw4*&oF literal 0 HcmV?d00001 diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart index 015f790..2e3cb05 100644 --- a/lib/screens/cart.dart +++ b/lib/screens/cart.dart @@ -177,7 +177,23 @@ class CartItem extends StatelessWidget{ Container( width: 100, alignment: Alignment.center, - child: Image(image: NetworkImage(_product.imageUrls[0]),), + child: Image.network( + _product.imageUrls[0], + loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! + : null, + ), + ); + }, + errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + // Повертає зображення за замовчуванням у випадку помилки + return Image.asset('../assets/img/default-white.png'); + }, + ), ), SizedBox(width: 20), Expanded( From a99e181412c57ca07b41b01d00f1b9b415212e9b Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Tue, 19 Dec 2023 01:06:28 +0200 Subject: [PATCH 32/39] cache image library added --- pubspec.lock | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 2 files changed, 65 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index bb2f44b..611ac12 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f + url: "https://pub.dev" + source: hosted + version: "3.3.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" + url: "https://pub.dev" + source: hosted + version: "1.1.0" characters: dependency: transitive description: @@ -118,6 +142,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_hooks: dependency: transitive description: @@ -328,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.2+1" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -493,6 +533,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 + url: "https://pub.dev" + source: hosted + version: "2.5.0+2" stack_trace: dependency: transitive description: @@ -517,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5c7ea09..7c2feb7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: flutter_spinkit: ^5.0.0 logger: ^2.0.2+1 url_launcher: ^6.0.12 + cached_network_image: ^3.3.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. From d1e8f3c4f2351d6488539c3cc21956276677ecfd Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Tue, 19 Dec 2023 01:22:45 +0200 Subject: [PATCH 33/39] Changed method for displaying images --- lib/screens/cart.dart | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart index 2e3cb05..6627c90 100644 --- a/lib/screens/cart.dart +++ b/lib/screens/cart.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:graphql/client.dart'; @@ -177,22 +178,10 @@ class CartItem extends StatelessWidget{ Container( width: 100, alignment: Alignment.center, - child: Image.network( - _product.imageUrls[0], - loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { - if (loadingProgress == null) return child; - return Center( - child: CircularProgressIndicator( - value: loadingProgress.expectedTotalBytes != null - ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! - : null, - ), - ); - }, - errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { - // Повертає зображення за замовчуванням у випадку помилки - return Image.asset('../assets/img/default-white.png'); - }, + child: CachedNetworkImage( + imageUrl: _product.imageUrls[0], + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => Image.asset('../assets/img/default-white.png'), ), ), SizedBox(width: 20), From c29a10c74936d6960a5a7b63cbf9d3fd739dc80f Mon Sep 17 00:00:00 2001 From: Mykyta Dubovyi Date: Tue, 19 Dec 2023 02:17:28 +0200 Subject: [PATCH 34/39] Changed method for displaying images --- lib/screens/cart.dart | 2 +- pubspec.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart index 6627c90..27b1072 100644 --- a/lib/screens/cart.dart +++ b/lib/screens/cart.dart @@ -181,7 +181,7 @@ class CartItem extends StatelessWidget{ child: CachedNetworkImage( imageUrl: _product.imageUrls[0], placeholder: (context, url) => CircularProgressIndicator(), - errorWidget: (context, url, error) => Image.asset('../assets/img/default-white.png'), + errorWidget: (context, url, error) => Container(color: Colors.white,), ), ), SizedBox(width: 20), diff --git a/pubspec.yaml b/pubspec.yaml index 7c2feb7..e8d5be0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,7 @@ flutter: - assets/icons/star.svg - assets/icons/half-star.svg - assets/icons/empty-star.svg + - assets/img/default-white.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware From dd150c50a507f545f878dbb75ea88ea5d979f4d5 Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Mon, 18 Dec 2023 19:45:56 -0500 Subject: [PATCH 35/39] SA-226 Change placeholder path for product card - Update cards.dart - Update version in pubspec.yaml --- lib/screens/cards.dart | 4 ++-- pubspec.lock | 26 +++++++++++++------------- pubspec.yaml | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/screens/cards.dart b/lib/screens/cards.dart index 8264211..09e7c7c 100644 --- a/lib/screens/cards.dart +++ b/lib/screens/cards.dart @@ -139,8 +139,8 @@ class _CardsState extends State { height: 300, decoration: BoxDecoration( image: DecorationImage( - image: AssetImage('assets/images/product_image_placeholder.jpg'), // Replace with your image path - fit: BoxFit.cover, + image: AssetImage('assets/img/default-white.png'), // Replace with your image path + fit: BoxFit.scaleDown, ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 611ac12..120fc2e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" connectivity_plus: dependency: transitive description: @@ -340,10 +340,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nm: dependency: transitive description: @@ -553,18 +553,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -713,10 +713,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -758,5 +758,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.4 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index e8d5be0..9c72f27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # 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+2 +version: 1.0.1+1 environment: sdk: '>=3.1.4 <4.0.0' From d469249520aea19f7ac39b3a790e441d047867ba Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Mon, 18 Dec 2023 19:52:27 -0500 Subject: [PATCH 36/39] SA-226 Update version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9c72f27..b1cec1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # 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.1+1 +version: 1.0.1+4 environment: sdk: '>=3.1.4 <4.0.0' From 070996ea7b87e6f534b80439d76e910d1e6d631c Mon Sep 17 00:00:00 2001 From: stasex Date: Tue, 19 Dec 2023 15:57:53 +0200 Subject: [PATCH 37/39] fix displaying the Amazon logo --- lib/screens/cart.dart | 2 +- pubspec.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/cart.dart b/lib/screens/cart.dart index 27b1072..8455f2f 100644 --- a/lib/screens/cart.dart +++ b/lib/screens/cart.dart @@ -216,7 +216,7 @@ class CartItem extends StatelessWidget{ backgroundColor: Colors.blue, padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), ), - icon: SvgPicture.asset("../assets/icons/amazon.svg", height: 15), + icon: SvgPicture.asset("assets/icons/amazon.svg", height: 15), label: Text(""), ), ) diff --git a/pubspec.yaml b/pubspec.yaml index b1cec1c..2d84328 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,7 @@ flutter: - assets/icons/star.svg - assets/icons/half-star.svg - assets/icons/empty-star.svg + - assets/icons/amazon.svg - assets/img/default-white.png # An image asset can refer to one or more resolution-specific "variants", see From 75327e708b1e4a25903965dec615e68b3af8345a Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Tue, 19 Dec 2023 20:59:58 -0500 Subject: [PATCH 38/39] SA-225 Update Podfile.lock --- ios/Podfile.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 70ea866..d49b07d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,6 +3,9 @@ PODS: - Flutter - ReachabilitySwift - Flutter (1.0.0) + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -10,6 +13,9 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FMDB (>= 2.7.5) - url_launcher_ios (0.0.1): - Flutter @@ -18,10 +24,12 @@ DEPENDENCIES: - Flutter (from `Flutter`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: + - FMDB - ReachabilitySwift EXTERNAL SOURCES: @@ -33,15 +41,19 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76 From b577569807893cafccf78d4d71e21d5190be1a66 Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Tue, 19 Dec 2023 21:04:58 -0500 Subject: [PATCH 39/39] SA-226 Update version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2d84328..874ec93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # 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.1+4 +version: 1.0.2+5 environment: sdk: '>=3.1.4 <4.0.0'