diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..9ba6aed Binary files /dev/null and b/.DS_Store differ diff --git a/.flutter-plugins b/.flutter-plugins new file mode 100644 index 0000000..e0db90a --- /dev/null +++ b/.flutter-plugins @@ -0,0 +1,32 @@ +# This is a generated file; do not edit or check into version control. +cloud_firestore=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-3.4.3/ +cloud_firestore_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore_web-2.8.3/ +connectivity_plus=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/ +connectivity_plus_linux=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/ +connectivity_plus_macos=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/ +connectivity_plus_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/ +connectivity_plus_windows=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/ +firebase_auth=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-3.6.2/ +firebase_auth_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth_web-4.2.2/ +firebase_core=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.20.0/ +firebase_core_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core_web-1.7.1/ +firebase_storage=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.4/ +firebase_storage_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_storage_web-3.3.2/ +flutter_image_compress=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_image_compress-1.1.1/ +flutter_plugin_android_lifecycle=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_plugin_android_lifecycle-2.0.7/ +image_picker=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker-0.8.5+3/ +image_picker_android=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker_android-0.8.5+2/ +image_picker_for_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker_for_web-2.1.8/ +image_picker_ios=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker_ios-0.8.5+6/ +path_provider=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.11/ +path_provider_android=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.17/ +path_provider_ios=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_ios-2.0.11/ +path_provider_linux=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.7/ +path_provider_macos=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.6/ +path_provider_windows=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.1.2/ +sqflite=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.3+1/ +video_compress=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_compress-3.1.1/ +video_player=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player-2.4.6/ +video_player_android=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_android-2.3.8/ +video_player_avfoundation=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_avfoundation-2.3.5/ +video_player_web=/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_web-2.0.12/ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies new file mode 100644 index 0000000..ec5b629 --- /dev/null +++ b/.flutter-plugins-dependencies @@ -0,0 +1 @@ +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"cloud_firestore","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-3.4.3/","native_build":true,"dependencies":["firebase_core"]},{"name":"connectivity_plus","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]},{"name":"firebase_auth","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-3.6.2/","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.20.0/","native_build":true,"dependencies":[]},{"name":"firebase_storage","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.4/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_image_compress","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_image_compress-1.1.1/","native_build":true,"dependencies":[]},{"name":"image_picker_ios","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker_ios-0.8.5+6/","native_build":true,"dependencies":[]},{"name":"path_provider_ios","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_ios-2.0.11/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"video_compress","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_compress-3.1.1/","native_build":true,"dependencies":[]},{"name":"video_player_avfoundation","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_avfoundation-2.3.5/","native_build":true,"dependencies":[]}],"android":[{"name":"cloud_firestore","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-3.4.3/","native_build":true,"dependencies":["firebase_core"]},{"name":"connectivity_plus","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus-2.3.6/","native_build":true,"dependencies":[]},{"name":"firebase_auth","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-3.6.2/","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.20.0/","native_build":true,"dependencies":[]},{"name":"firebase_storage","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.4/","native_build":true,"dependencies":["firebase_core"]},{"name":"flutter_image_compress","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_image_compress-1.1.1/","native_build":true,"dependencies":[]},{"name":"flutter_plugin_android_lifecycle","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_plugin_android_lifecycle-2.0.7/","native_build":true,"dependencies":[]},{"name":"image_picker_android","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker_android-0.8.5+2/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"]},{"name":"path_provider_android","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.17/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"video_compress","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_compress-3.1.1/","native_build":true,"dependencies":[]},{"name":"video_player_android","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_android-2.3.8/","native_build":true,"dependencies":[]}],"macos":[{"name":"cloud_firestore","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-3.4.3/","native_build":true,"dependencies":["firebase_core"]},{"name":"connectivity_plus_macos","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_macos-1.2.4/","native_build":true,"dependencies":[]},{"name":"firebase_auth","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-3.6.2/","native_build":true,"dependencies":["firebase_core"]},{"name":"firebase_core","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.20.0/","native_build":true,"dependencies":[]},{"name":"firebase_storage","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.4/","native_build":true,"dependencies":["firebase_core"]},{"name":"path_provider_macos","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.6/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.3+1/","native_build":true,"dependencies":[]},{"name":"video_compress","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_compress-3.1.1/","native_build":true,"dependencies":[]}],"linux":[{"name":"connectivity_plus_linux","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_linux-1.3.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.7/","native_build":false,"dependencies":[]}],"windows":[{"name":"connectivity_plus_windows","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_windows-1.2.2/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.1.2/","native_build":false,"dependencies":[]}],"web":[{"name":"cloud_firestore_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore_web-2.8.3/","dependencies":["firebase_core_web"]},{"name":"connectivity_plus_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_plus_web-1.2.3/","dependencies":[]},{"name":"firebase_auth_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth_web-4.2.2/","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core_web-1.7.1/","dependencies":[]},{"name":"firebase_storage_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_storage_web-3.3.2/","dependencies":["firebase_core_web"]},{"name":"image_picker_for_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/image_picker_for_web-2.1.8/","dependencies":[]},{"name":"video_player_web","path":"/Users/vlad/Development/flutter/.pub-cache/hosted/pub.dartlang.org/video_player_web-2.0.12/","dependencies":[]}]},"dependencyGraph":[{"name":"cloud_firestore","dependencies":["cloud_firestore_web","firebase_core"]},{"name":"cloud_firestore_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"connectivity_plus","dependencies":["connectivity_plus_linux","connectivity_plus_macos","connectivity_plus_web","connectivity_plus_windows"]},{"name":"connectivity_plus_linux","dependencies":[]},{"name":"connectivity_plus_macos","dependencies":[]},{"name":"connectivity_plus_web","dependencies":[]},{"name":"connectivity_plus_windows","dependencies":[]},{"name":"firebase_auth","dependencies":["firebase_auth_web","firebase_core"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_storage","dependencies":["firebase_core","firebase_storage_web"]},{"name":"firebase_storage_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"flutter_image_compress","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"image_picker","dependencies":["image_picker_android","image_picker_for_web","image_picker_ios"]},{"name":"image_picker_android","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"image_picker_for_web","dependencies":[]},{"name":"image_picker_ios","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_ios","path_provider_linux","path_provider_macos","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_ios","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":[]},{"name":"video_compress","dependencies":[]},{"name":"video_player","dependencies":["video_player_android","video_player_avfoundation","video_player_web"]},{"name":"video_player_android","dependencies":[]},{"name":"video_player_avfoundation","dependencies":[]},{"name":"video_player_web","dependencies":[]}],"date_created":"2022-08-24 16:03:12.238434","version":"3.0.5"} \ No newline at end of file diff --git a/.gitignore b/.gitignore index dbef116..dd95d8e 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ doc/api/ *.js_ *.js.deps *.js.map +.idea/workspace.xml +.flutter-plugins-dependencies + +/key.properties + diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml new file mode 100644 index 0000000..6ab20e9 --- /dev/null +++ b/.idea/libraries/Dart_Packages.xml @@ -0,0 +1,1196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml new file mode 100644 index 0000000..71fcae3 --- /dev/null +++ b/.idea/libraries/Dart_SDK.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml new file mode 100644 index 0000000..5b911a5 --- /dev/null +++ b/.idea/libraries/Flutter_Plugins.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5c38005 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/redting.iml b/.idea/redting.iml new file mode 100644 index 0000000..9a97596 --- /dev/null +++ b/.idea/redting.iml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..9832dda --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1657733964576 + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index d9bc81c..d068526 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ -# redting \ No newline at end of file +# redting + +IT's A REAL WORLD OUTSIDE - Dating App + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# 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-lang.github.io/linter/lints/index.html. + # + # 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/.DS_Store b/android/.DS_Store new file mode 100644 index 0000000..da07aa6 Binary files /dev/null and b/android/.DS_Store differ 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/.idea/compiler.xml b/android/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/android/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml new file mode 100644 index 0000000..2e16799 --- /dev/null +++ b/android/.idea/gradle.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/jarRepositories.xml b/android/.idea/jarRepositories.xml new file mode 100644 index 0000000..42fc634 --- /dev/null +++ b/android/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle_______build_app_intermediates_flutter_debug_libs_jar.xml b/android/.idea/libraries/Gradle_______build_app_intermediates_flutter_debug_libs_jar.xml new file mode 100644 index 0000000..106c5b3 --- /dev/null +++ b/android/.idea/libraries/Gradle_______build_app_intermediates_flutter_debug_libs_jar.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml new file mode 100644 index 0000000..249dead --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_annotation_annotation_1_2_0.xml b/android/.idea/libraries/Gradle__androidx_annotation_annotation_1_2_0.xml new file mode 100644 index 0000000..74437d7 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_annotation_annotation_1_2_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml b/android/.idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml new file mode 100644 index 0000000..7aace70 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml b/android/.idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml new file mode 100644 index 0000000..5d31667 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml b/android/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml new file mode 100644 index 0000000..2208415 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml new file mode 100644 index 0000000..9ba5531 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml b/android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml new file mode 100644 index 0000000..46ca48f --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_browser_browser_1_4_0_aar.xml b/android/.idea/libraries/Gradle__androidx_browser_browser_1_4_0_aar.xml new file mode 100644 index 0000000..3bee9c8 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_browser_browser_1_4_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml b/android/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml new file mode 100644 index 0000000..eafc05e --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_core_core_1_6_0_aar.xml b/android/.idea/libraries/Gradle__androidx_core_core_1_6_0_aar.xml new file mode 100644 index 0000000..30c5a07 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_core_core_1_6_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml b/android/.idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml new file mode 100644 index 0000000..1fbe5ff --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_core_core_1_8_0_aar.xml b/android/.idea/libraries/Gradle__androidx_core_core_1_8_0_aar.xml new file mode 100644 index 0000000..1bed272 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_core_core_1_8_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml new file mode 100644 index 0000000..3e4cdde --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_3_0_aar.xml b/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_3_0_aar.xml new file mode 100644 index 0000000..5ede1d6 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_3_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_3_3_aar.xml b/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_3_3_aar.xml new file mode 100644 index 0000000..1205d38 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_exifinterface_exifinterface_1_3_3_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml b/android/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml new file mode 100644 index 0000000..7b2e0ca --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_heifwriter_heifwriter_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_heifwriter_heifwriter_1_0_0_aar.xml new file mode 100644 index 0000000..11e8186 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_heifwriter_heifwriter_1_0_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_2_0.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_2_0.xml new file mode 100644 index 0000000..f7d6479 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_2_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_3_1.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_3_1.xml new file mode 100644 index 0000000..a1e1912 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_3_1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_java8_2_2_0.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_java8_2_2_0.xml new file mode 100644 index 0000000..fc8d677 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_java8_2_2_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml new file mode 100644 index 0000000..215c3b0 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml new file mode 100644 index 0000000..5151b21 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_2_0_aar.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_2_0_aar.xml new file mode 100644 index 0000000..873c138 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_2_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml new file mode 100644 index 0000000..e7c2f25 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml new file mode 100644 index 0000000..1ae74fe --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml new file mode 100644 index 0000000..922cecc --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_localbroadcastmanager_localbroadcastmanager_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_localbroadcastmanager_localbroadcastmanager_1_0_0_aar.xml new file mode 100644 index 0000000..34f9038 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_localbroadcastmanager_localbroadcastmanager_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml new file mode 100644 index 0000000..5ceab26 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_test_core_1_3_0_aar.xml b/android/.idea/libraries/Gradle__androidx_test_core_1_3_0_aar.xml new file mode 100644 index 0000000..0b7c7a3 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_test_core_1_3_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml b/android/.idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml new file mode 100644 index 0000000..3411293 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_test_monitor_1_3_0_aar.xml b/android/.idea/libraries/Gradle__androidx_test_monitor_1_3_0_aar.xml new file mode 100644 index 0000000..b08adcb --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_test_monitor_1_3_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml b/android/.idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml new file mode 100644 index 0000000..9514645 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_tracing_tracing_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_tracing_tracing_1_0_0_aar.xml new file mode 100644 index 0000000..1822f67 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_tracing_tracing_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml b/android/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml new file mode 100644 index 0000000..089ea15 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml b/android/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml new file mode 100644 index 0000000..fb9e9e1 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_window_window_1_0_0_beta04_aar.xml b/android/.idea/libraries/Gradle__androidx_window_window_1_0_0_beta04_aar.xml new file mode 100644 index 0000000..0abc73c --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_window_window_1_0_0_beta04_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__androidx_window_window_java_1_0_0_beta04_aar.xml b/android/.idea/libraries/Gradle__androidx_window_window_java_1_0_0_beta04_aar.xml new file mode 100644 index 0000000..ab97c75 --- /dev/null +++ b/android/.idea/libraries/Gradle__androidx_window_window_java_1_0_0_beta04_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_almworks_sqlite4java_sqlite4java_1_0_392.xml b/android/.idea/libraries/Gradle__com_almworks_sqlite4java_sqlite4java_1_0_392.xml new file mode 100644 index 0000000..7b6742f --- /dev/null +++ b/android/.idea/libraries/Gradle__com_almworks_sqlite4java_sqlite4java_1_0_392.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_common_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_common_2_18_0_aar.xml new file mode 100644 index 0000000..dc2d0b0 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_common_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_core_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_core_2_18_0_aar.xml new file mode 100644 index 0000000..7f470c1 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_core_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_dash_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_dash_2_18_0_aar.xml new file mode 100644 index 0000000..b6f63cd --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_dash_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_database_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_database_2_18_0_aar.xml new file mode 100644 index 0000000..83271c2 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_database_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_datasource_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_datasource_2_18_0_aar.xml new file mode 100644 index 0000000..7b195e7 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_datasource_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_decoder_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_decoder_2_18_0_aar.xml new file mode 100644 index 0000000..679b61d --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_decoder_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_extractor_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_extractor_2_18_0_aar.xml new file mode 100644 index 0000000..3961110 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_extractor_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_hls_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_hls_2_18_0_aar.xml new file mode 100644 index 0000000..380ec33 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_hls_2_18_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_smoothstreaming_2_18_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_smoothstreaming_2_18_0_aar.xml new file mode 100644 index 0000000..7d7812d --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_exoplayer_exoplayer_smoothstreaming_2_18_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_gms_play_services_auth_api_phone_17_4_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_auth_api_phone_17_4_0_aar.xml new file mode 100644 index 0000000..bc395eb --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_auth_api_phone_17_4_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_gms_play_services_base_17_1_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_base_17_1_0_aar.xml new file mode 100644 index 0000000..637c8e4 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_base_17_1_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_gms_play_services_base_18_0_1_aar.xml b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_base_18_0_1_aar.xml new file mode 100644 index 0000000..6cb31c0 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_base_18_0_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_gms_play_services_basement_18_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_basement_18_0_0_aar.xml new file mode 100644 index 0000000..3828a36 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_basement_18_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_gms_play_services_safetynet_17_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_safetynet_17_0_0_aar.xml new file mode 100644 index 0000000..afe8361 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_safetynet_17_0_0_aar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_android_gms_play_services_tasks_18_0_1_aar.xml b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_tasks_18_0_1_aar.xml new file mode 100644 index 0000000..7758016 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_android_gms_play_services_tasks_18_0_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_auto_value_auto_value_annotations_1_6_2.xml b/android/.idea/libraries/Gradle__com_google_auto_value_auto_value_annotations_1_6_2.xml new file mode 100644 index 0000000..1f83590 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_auto_value_auto_value_annotations_1_6_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_auto_value_auto_value_annotations_1_7_4.xml b/android/.idea/libraries/Gradle__com_google_auto_value_auto_value_annotations_1_7_4.xml new file mode 100644 index 0000000..37be6e2 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_auto_value_auto_value_annotations_1_7_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_3_0_2.xml b/android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_3_0_2.xml new file mode 100644 index 0000000..a96d725 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_3_0_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_3_2.xml b/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_3_2.xml new file mode 100644 index 0000000..61dea6f --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_7_1.xml b/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_7_1.xml new file mode 100644 index 0000000..26276e5 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_7_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_9_0.xml b/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_9_0.xml new file mode 100644 index 0000000..8f77a9e --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_errorprone_error_prone_annotations_2_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_annotations_16_0_0.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_annotations_16_0_0.xml new file mode 100644 index 0000000..0d56cda --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_annotations_16_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_annotations_16_1_0.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_annotations_16_1_0.xml new file mode 100644 index 0000000..69943d1 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_annotations_16_1_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_appcheck_interop_16_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_appcheck_interop_16_0_0_aar.xml new file mode 100644 index 0000000..5a9ec3f --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_appcheck_interop_16_0_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_appcheck_interop_16_0_0_beta02_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_appcheck_interop_16_0_0_beta02_aar.xml new file mode 100644 index 0000000..7e4e963 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_appcheck_interop_16_0_0_beta02_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_21_0_6_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_21_0_6_aar.xml new file mode 100644 index 0000000..4446b5f --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_21_0_6_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_18_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_18_0_0_aar.xml new file mode 100644 index 0000000..bfe3cbb --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_18_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_19_0_2_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_19_0_2_aar.xml new file mode 100644 index 0000000..c13ba31 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_19_0_2_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_20_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_20_0_0_aar.xml new file mode 100644 index 0000000..2c457c1 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_auth_interop_20_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_common_20_1_1_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_common_20_1_1_aar.xml new file mode 100644 index 0000000..69606de --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_common_20_1_1_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_components_17_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_components_17_0_0_aar.xml new file mode 100644 index 0000000..535522a --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_components_17_0_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_database_collection_18_0_1_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_database_collection_18_0_1_aar.xml new file mode 100644 index 0000000..1e5cbc2 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_database_collection_18_0_1_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_firestore_24_2_1_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_firestore_24_2_1_aar.xml new file mode 100644 index 0000000..e8565c8 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_firestore_24_2_1_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_firebase_storage_20_0_1_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_firebase_storage_20_0_1_aar.xml new file mode 100644 index 0000000..8e29b59 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_firebase_storage_20_0_1_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_firebase_protolite_well_known_types_18_0_0_aar.xml b/android/.idea/libraries/Gradle__com_google_firebase_protolite_well_known_types_18_0_0_aar.xml new file mode 100644 index 0000000..81a6583 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_firebase_protolite_well_known_types_18_0_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_failureaccess_1_0_1.xml b/android/.idea/libraries/Gradle__com_google_guava_failureaccess_1_0_1.xml new file mode 100644 index 0000000..aeb2fc7 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_failureaccess_1_0_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_guava_27_0_1_jre.xml b/android/.idea/libraries/Gradle__com_google_guava_guava_27_0_1_jre.xml new file mode 100644 index 0000000..bb089ad --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_guava_27_0_1_jre.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_guava_28_1_android.xml b/android/.idea/libraries/Gradle__com_google_guava_guava_28_1_android.xml new file mode 100644 index 0000000..703e775 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_guava_28_1_android.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_guava_30_1_1_android.xml b/android/.idea/libraries/Gradle__com_google_guava_guava_30_1_1_android.xml new file mode 100644 index 0000000..24aafb3 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_guava_30_1_1_android.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_guava_31_0_1_android.xml b/android/.idea/libraries/Gradle__com_google_guava_guava_31_0_1_android.xml new file mode 100644 index 0000000..e47ffef --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_guava_31_0_1_android.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_listenablefuture_1_0.xml b/android/.idea/libraries/Gradle__com_google_guava_listenablefuture_1_0.xml new file mode 100644 index 0000000..09da23b --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_listenablefuture_1_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_guava_listenablefuture_9999_0_empty_to_avoid_conflict_with_guava.xml b/android/.idea/libraries/Gradle__com_google_guava_listenablefuture_9999_0_empty_to_avoid_conflict_with_guava.xml new file mode 100644 index 0000000..11f8cce --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_guava_listenablefuture_9999_0_empty_to_avoid_conflict_with_guava.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_j2objc_j2objc_annotations_1_1.xml b/android/.idea/libraries/Gradle__com_google_j2objc_j2objc_annotations_1_1.xml new file mode 100644 index 0000000..51270bf --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_j2objc_j2objc_annotations_1_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_j2objc_j2objc_annotations_1_3.xml b/android/.idea/libraries/Gradle__com_google_j2objc_j2objc_annotations_1_3.xml new file mode 100644 index 0000000..7a61cf9 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_j2objc_j2objc_annotations_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_google_protobuf_protobuf_javalite_3_19_2.xml b/android/.idea/libraries/Gradle__com_google_protobuf_protobuf_javalite_3_19_2.xml new file mode 100644 index 0000000..d601d3e --- /dev/null +++ b/android/.idea/libraries/Gradle__com_google_protobuf_protobuf_javalite_3_19_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_ibm_icu_icu4j_53_1.xml b/android/.idea/libraries/Gradle__com_ibm_icu_icu4j_53_1.xml new file mode 100644 index 0000000..d8c1108 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_ibm_icu_icu4j_53_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_ibm_icu_icu4j_70_1.xml b/android/.idea/libraries/Gradle__com_ibm_icu_icu4j_70_1.xml new file mode 100644 index 0000000..2b8a65d --- /dev/null +++ b/android/.idea/libraries/Gradle__com_ibm_icu_icu4j_70_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_otaliastudios_opengl_egloo_0_6_1_aar.xml b/android/.idea/libraries/Gradle__com_otaliastudios_opengl_egloo_0_6_1_aar.xml new file mode 100644 index 0000000..72670e3 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_otaliastudios_opengl_egloo_0_6_1_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_otaliastudios_transcoder_0_10_4_aar.xml b/android/.idea/libraries/Gradle__com_otaliastudios_transcoder_0_10_4_aar.xml new file mode 100644 index 0000000..5470196 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_otaliastudios_transcoder_0_10_4_aar.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_5.xml b/android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_5.xml new file mode 100644 index 0000000..980c165 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_squareup_okhttp_okhttp_2_7_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__com_squareup_okio_okio_1_17_5.xml b/android/.idea/libraries/Gradle__com_squareup_okio_okio_1_17_5.xml new file mode 100644 index 0000000..a3ff7d3 --- /dev/null +++ b/android/.idea/libraries/Gradle__com_squareup_okio_okio_1_17_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__commons_io_commons_io_2_6.xml b/android/.idea/libraries/Gradle__commons_io_commons_io_2_6.xml new file mode 100644 index 0000000..1e3c689 --- /dev/null +++ b/android/.idea/libraries/Gradle__commons_io_commons_io_2_6.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_flutter_arm64_v8a_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml b/android/.idea/libraries/Gradle__io_flutter_arm64_v8a_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml new file mode 100644 index 0000000..0845be5 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_flutter_arm64_v8a_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_flutter_armeabi_v7a_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml b/android/.idea/libraries/Gradle__io_flutter_armeabi_v7a_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml new file mode 100644 index 0000000..81b6cef --- /dev/null +++ b/android/.idea/libraries/Gradle__io_flutter_armeabi_v7a_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_flutter_flutter_embedding_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml b/android/.idea/libraries/Gradle__io_flutter_flutter_embedding_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml new file mode 100644 index 0000000..6cd0e7e --- /dev/null +++ b/android/.idea/libraries/Gradle__io_flutter_flutter_embedding_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_flutter_x86_64_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml b/android/.idea/libraries/Gradle__io_flutter_x86_64_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml new file mode 100644 index 0000000..e6985f8 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_flutter_x86_64_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_flutter_x86_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml b/android/.idea/libraries/Gradle__io_flutter_x86_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml new file mode 100644 index 0000000..f760090 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_flutter_x86_debug_1_0_0_e85ea0e79c6d894c120cda4ee8ee10fe6745e187.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_android_1_44_1_aar.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_android_1_44_1_aar.xml new file mode 100644 index 0000000..aad87a2 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_android_1_44_1_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_api_1_44_1.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_api_1_44_1.xml new file mode 100644 index 0000000..62e0a0a --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_api_1_44_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_context_1_44_1.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_context_1_44_1.xml new file mode 100644 index 0000000..53e615a --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_context_1_44_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_core_1_44_1.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_core_1_44_1.xml new file mode 100644 index 0000000..be04a9f --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_core_1_44_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_okhttp_1_44_1.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_okhttp_1_44_1.xml new file mode 100644 index 0000000..97d64a8 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_okhttp_1_44_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_protobuf_lite_1_44_1.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_protobuf_lite_1_44_1.xml new file mode 100644 index 0000000..aea5e81 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_protobuf_lite_1_44_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__io_grpc_grpc_stub_1_44_1.xml b/android/.idea/libraries/Gradle__io_grpc_grpc_stub_1_44_1.xml new file mode 100644 index 0000000..15ed0c7 --- /dev/null +++ b/android/.idea/libraries/Gradle__io_grpc_grpc_stub_1_44_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__javax_annotation_javax_annotation_api_1_3_2.xml b/android/.idea/libraries/Gradle__javax_annotation_javax_annotation_api_1_3_2.xml new file mode 100644 index 0000000..10c7813 --- /dev/null +++ b/android/.idea/libraries/Gradle__javax_annotation_javax_annotation_api_1_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__javax_inject_javax_inject_1.xml b/android/.idea/libraries/Gradle__javax_inject_javax_inject_1.xml new file mode 100644 index 0000000..62012ea --- /dev/null +++ b/android/.idea/libraries/Gradle__javax_inject_javax_inject_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__junit_junit_4_12.xml b/android/.idea/libraries/Gradle__junit_junit_4_12.xml new file mode 100644 index 0000000..f7d27c4 --- /dev/null +++ b/android/.idea/libraries/Gradle__junit_junit_4_12.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__junit_junit_4_13_1.xml b/android/.idea/libraries/Gradle__junit_junit_4_13_1.xml new file mode 100644 index 0000000..4be8bb6 --- /dev/null +++ b/android/.idea/libraries/Gradle__junit_junit_4_13_1.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__junit_junit_4_13_2.xml b/android/.idea/libraries/Gradle__junit_junit_4_13_2.xml new file mode 100644 index 0000000..198592d --- /dev/null +++ b/android/.idea/libraries/Gradle__junit_junit_4_13_2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_1_10_20.xml b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_1_10_20.xml new file mode 100644 index 0000000..3a6cfb2 --- /dev/null +++ b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_1_10_20.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_1_11_0.xml b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_1_11_0.xml new file mode 100644 index 0000000..354c6c1 --- /dev/null +++ b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_1_11_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_agent_1_10_20.xml b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_agent_1_10_20.xml new file mode 100644 index 0000000..ccb3d37 --- /dev/null +++ b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_agent_1_10_20.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_agent_1_11_0.xml b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_agent_1_11_0.xml new file mode 100644 index 0000000..118a32e --- /dev/null +++ b/android/.idea/libraries/Gradle__net_bytebuddy_byte_buddy_agent_1_11_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_bouncycastle_bcprov_jdk15on_1_65.xml b/android/.idea/libraries/Gradle__org_bouncycastle_bcprov_jdk15on_1_65.xml new file mode 100644 index 0000000..03bd52e --- /dev/null +++ b/android/.idea/libraries/Gradle__org_bouncycastle_bcprov_jdk15on_1_65.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_bouncycastle_bcprov_jdk15on_1_68.xml b/android/.idea/libraries/Gradle__org_bouncycastle_bcprov_jdk15on_1_68.xml new file mode 100644 index 0000000..baebd92 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_bouncycastle_bcprov_jdk15on_1_68.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_checkerframework_checker_compat_qual_2_5_5.xml b/android/.idea/libraries/Gradle__org_checkerframework_checker_compat_qual_2_5_5.xml new file mode 100644 index 0000000..b511655 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_checkerframework_checker_compat_qual_2_5_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_checkerframework_checker_qual_2_5_2.xml b/android/.idea/libraries/Gradle__org_checkerframework_checker_qual_2_5_2.xml new file mode 100644 index 0000000..3029dfe --- /dev/null +++ b/android/.idea/libraries/Gradle__org_checkerframework_checker_qual_2_5_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_checkerframework_checker_qual_3_12_0.xml b/android/.idea/libraries/Gradle__org_checkerframework_checker_qual_3_12_0.xml new file mode 100644 index 0000000..017b7b8 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_checkerframework_checker_qual_3_12_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_codehaus_mojo_animal_sniffer_annotations_1_17.xml b/android/.idea/libraries/Gradle__org_codehaus_mojo_animal_sniffer_annotations_1_17.xml new file mode 100644 index 0000000..63ace91 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_codehaus_mojo_animal_sniffer_annotations_1_17.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_codehaus_mojo_animal_sniffer_annotations_1_18.xml b/android/.idea/libraries/Gradle__org_codehaus_mojo_animal_sniffer_annotations_1_18.xml new file mode 100644 index 0000000..5a17a38 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_codehaus_mojo_animal_sniffer_annotations_1_18.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml b/android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml new file mode 100644 index 0000000..09cf23d --- /dev/null +++ b/android/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml b/android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml new file mode 100644 index 0000000..1fa0fa9 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_5_31.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_5_31.xml new file mode 100644 index 0000000..8ce5bf1 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_5_31.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_10.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_10.xml new file mode 100644 index 0000000..1953391 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_10.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_5_31.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_5_31.xml new file mode 100644 index 0000000..082826f --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_5_31.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_10.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_10.xml new file mode 100644 index 0000000..68fff8a --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_10.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_5_30.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_5_30.xml new file mode 100644 index 0000000..82a2923 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_5_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_10.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_10.xml new file mode 100644 index 0000000..92ea1ff --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_10.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_5_30.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_5_30.xml new file mode 100644 index 0000000..0017eb9 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_5_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlinx_kotlinx_coroutines_android_1_5_2.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlinx_kotlinx_coroutines_android_1_5_2.xml new file mode 100644 index 0000000..cdb2e8d --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlinx_kotlinx_coroutines_android_1_5_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm_1_5_2.xml b/android/.idea/libraries/Gradle__org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm_1_5_2.xml new file mode 100644 index 0000000..4cd20a6 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm_1_5_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_mockito_mockito_core_1_10_19.xml b/android/.idea/libraries/Gradle__org_mockito_mockito_core_1_10_19.xml new file mode 100644 index 0000000..2cbfdd6 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_mockito_mockito_core_1_10_19.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_mockito_mockito_core_3_10_0.xml b/android/.idea/libraries/Gradle__org_mockito_mockito_core_3_10_0.xml new file mode 100644 index 0000000..0d9496b --- /dev/null +++ b/android/.idea/libraries/Gradle__org_mockito_mockito_core_3_10_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_mockito_mockito_core_3_9_0.xml b/android/.idea/libraries/Gradle__org_mockito_mockito_core_3_9_0.xml new file mode 100644 index 0000000..31d2f83 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_mockito_mockito_core_3_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_mockito_mockito_inline_3_9_0.xml b/android/.idea/libraries/Gradle__org_mockito_mockito_inline_3_9_0.xml new file mode 100644 index 0000000..448d71f --- /dev/null +++ b/android/.idea/libraries/Gradle__org_mockito_mockito_inline_3_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_objenesis_objenesis_3_2.xml b/android/.idea/libraries/Gradle__org_objenesis_objenesis_3_2.xml new file mode 100644 index 0000000..755fe5c --- /dev/null +++ b/android/.idea/libraries/Gradle__org_objenesis_objenesis_3_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_9_0.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_9_0.xml new file mode 100644 index 0000000..7300543 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_9_2.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_9_2.xml new file mode 100644 index 0000000..a7eae70 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_9_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_analysis_9_0.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_analysis_9_0.xml new file mode 100644 index 0000000..c9c7eee --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_analysis_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_analysis_9_2.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_analysis_9_2.xml new file mode 100644 index 0000000..5493dfc --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_analysis_9_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_commons_9_0.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_commons_9_0.xml new file mode 100644 index 0000000..8902b7a --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_commons_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_commons_9_2.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_commons_9_2.xml new file mode 100644 index 0000000..c87617b --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_commons_9_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_tree_9_0.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_tree_9_0.xml new file mode 100644 index 0000000..7ab42de --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_tree_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_tree_9_2.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_tree_9_2.xml new file mode 100644 index 0000000..f8e9bf0 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_tree_9_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_util_9_0.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_util_9_0.xml new file mode 100644 index 0000000..b45140b --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_util_9_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_ow2_asm_asm_util_9_2.xml b/android/.idea/libraries/Gradle__org_ow2_asm_asm_util_9_2.xml new file mode 100644 index 0000000..db0550d --- /dev/null +++ b/android/.idea/libraries/Gradle__org_ow2_asm_asm_util_9_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_annotations_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_annotations_4_5.xml new file mode 100644 index 0000000..0495dc4 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_annotations_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_annotations_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_annotations_4_8_1.xml new file mode 100644 index 0000000..21ebbdf --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_annotations_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_junit_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_junit_4_5.xml new file mode 100644 index 0000000..8ab20ad --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_junit_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_junit_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_junit_4_8_1.xml new file mode 100644 index 0000000..76e3225 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_junit_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_nativeruntime_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_nativeruntime_4_8_1.xml new file mode 100644 index 0000000..185200a --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_nativeruntime_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_pluginapi_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_pluginapi_4_5.xml new file mode 100644 index 0000000..76a8211 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_pluginapi_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_pluginapi_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_pluginapi_4_8_1.xml new file mode 100644 index 0000000..d2a9f2b --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_pluginapi_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_plugins_maven_dependency_resolver_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_plugins_maven_dependency_resolver_4_5.xml new file mode 100644 index 0000000..1fcbb7e --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_plugins_maven_dependency_resolver_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_plugins_maven_dependency_resolver_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_plugins_maven_dependency_resolver_4_8_1.xml new file mode 100644 index 0000000..7bdc4ea --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_plugins_maven_dependency_resolver_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_resources_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_resources_4_5.xml new file mode 100644 index 0000000..7620028 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_resources_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_resources_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_resources_4_8_1.xml new file mode 100644 index 0000000..21e8626 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_resources_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_robolectric_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_robolectric_4_5.xml new file mode 100644 index 0000000..9ac58c4 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_robolectric_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_robolectric_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_robolectric_4_8_1.xml new file mode 100644 index 0000000..b6111b5 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_robolectric_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_sandbox_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_sandbox_4_5.xml new file mode 100644 index 0000000..e8fe986 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_sandbox_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_sandbox_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_sandbox_4_8_1.xml new file mode 100644 index 0000000..a0957f1 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_sandbox_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_shadowapi_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_shadowapi_4_5.xml new file mode 100644 index 0000000..13fe3c0 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_shadowapi_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_shadowapi_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_shadowapi_4_8_1.xml new file mode 100644 index 0000000..a7c5b92 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_shadowapi_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_shadows_framework_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_shadows_framework_4_5.xml new file mode 100644 index 0000000..a93a9b0 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_shadows_framework_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_shadows_framework_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_shadows_framework_4_8_1.xml new file mode 100644 index 0000000..7b56596 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_shadows_framework_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_utils_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_utils_4_5.xml new file mode 100644 index 0000000..b70b458 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_utils_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_utils_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_utils_4_8_1.xml new file mode 100644 index 0000000..67d08a6 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_utils_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_utils_reflector_4_5.xml b/android/.idea/libraries/Gradle__org_robolectric_utils_reflector_4_5.xml new file mode 100644 index 0000000..065e422 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_utils_reflector_4_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/libraries/Gradle__org_robolectric_utils_reflector_4_8_1.xml b/android/.idea/libraries/Gradle__org_robolectric_utils_reflector_4_8_1.xml new file mode 100644 index 0000000..35b1006 --- /dev/null +++ b/android/.idea/libraries/Gradle__org_robolectric_utils_reflector_4_8_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml new file mode 100644 index 0000000..2a4d5b5 --- /dev/null +++ b/android/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules.xml b/android/.idea/modules.xml new file mode 100644 index 0000000..cbc5c32 --- /dev/null +++ b/android/.idea/modules.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1203171619/android.connectivity_plus.androidTest.iml b/android/.idea/modules/-1203171619/android.connectivity_plus.androidTest.iml new file mode 100644 index 0000000..79d3c0f --- /dev/null +++ b/android/.idea/modules/-1203171619/android.connectivity_plus.androidTest.iml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1203171619/android.connectivity_plus.iml b/android/.idea/modules/-1203171619/android.connectivity_plus.iml new file mode 100644 index 0000000..3412ebb --- /dev/null +++ b/android/.idea/modules/-1203171619/android.connectivity_plus.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1203171619/android.connectivity_plus.main.iml b/android/.idea/modules/-1203171619/android.connectivity_plus.main.iml new file mode 100644 index 0000000..509e204 --- /dev/null +++ b/android/.idea/modules/-1203171619/android.connectivity_plus.main.iml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1203171619/android.connectivity_plus.unitTest.iml b/android/.idea/modules/-1203171619/android.connectivity_plus.unitTest.iml new file mode 100644 index 0000000..91a66a6 --- /dev/null +++ b/android/.idea/modules/-1203171619/android.connectivity_plus.unitTest.iml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1979289219/android.firebase_storage.androidTest.iml b/android/.idea/modules/-1979289219/android.firebase_storage.androidTest.iml new file mode 100644 index 0000000..3fbcf9f --- /dev/null +++ b/android/.idea/modules/-1979289219/android.firebase_storage.androidTest.iml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1979289219/android.firebase_storage.iml b/android/.idea/modules/-1979289219/android.firebase_storage.iml new file mode 100644 index 0000000..150b09c --- /dev/null +++ b/android/.idea/modules/-1979289219/android.firebase_storage.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1979289219/android.firebase_storage.main.iml b/android/.idea/modules/-1979289219/android.firebase_storage.main.iml new file mode 100644 index 0000000..1bc5896 --- /dev/null +++ b/android/.idea/modules/-1979289219/android.firebase_storage.main.iml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-1979289219/android.firebase_storage.unitTest.iml b/android/.idea/modules/-1979289219/android.firebase_storage.unitTest.iml new file mode 100644 index 0000000..bd01124 --- /dev/null +++ b/android/.idea/modules/-1979289219/android.firebase_storage.unitTest.iml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-208994597/android.cloud_firestore.androidTest.iml b/android/.idea/modules/-208994597/android.cloud_firestore.androidTest.iml new file mode 100644 index 0000000..7d4c612 --- /dev/null +++ b/android/.idea/modules/-208994597/android.cloud_firestore.androidTest.iml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-208994597/android.cloud_firestore.iml b/android/.idea/modules/-208994597/android.cloud_firestore.iml new file mode 100644 index 0000000..a13024c --- /dev/null +++ b/android/.idea/modules/-208994597/android.cloud_firestore.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-208994597/android.cloud_firestore.main.iml b/android/.idea/modules/-208994597/android.cloud_firestore.main.iml new file mode 100644 index 0000000..eaa75e3 --- /dev/null +++ b/android/.idea/modules/-208994597/android.cloud_firestore.main.iml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-208994597/android.cloud_firestore.unitTest.iml b/android/.idea/modules/-208994597/android.cloud_firestore.unitTest.iml new file mode 100644 index 0000000..f679e70 --- /dev/null +++ b/android/.idea/modules/-208994597/android.cloud_firestore.unitTest.iml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-834682231/android.path_provider_android.androidTest.iml b/android/.idea/modules/-834682231/android.path_provider_android.androidTest.iml new file mode 100644 index 0000000..9bc6778 --- /dev/null +++ b/android/.idea/modules/-834682231/android.path_provider_android.androidTest.iml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-834682231/android.path_provider_android.iml b/android/.idea/modules/-834682231/android.path_provider_android.iml new file mode 100644 index 0000000..df812ea --- /dev/null +++ b/android/.idea/modules/-834682231/android.path_provider_android.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-834682231/android.path_provider_android.main.iml b/android/.idea/modules/-834682231/android.path_provider_android.main.iml new file mode 100644 index 0000000..3304b13 --- /dev/null +++ b/android/.idea/modules/-834682231/android.path_provider_android.main.iml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/-834682231/android.path_provider_android.unitTest.iml b/android/.idea/modules/-834682231/android.path_provider_android.unitTest.iml new file mode 100644 index 0000000..5dd8f62 --- /dev/null +++ b/android/.idea/modules/-834682231/android.path_provider_android.unitTest.iml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/100380499/android.image_picker_android.androidTest.iml b/android/.idea/modules/100380499/android.image_picker_android.androidTest.iml new file mode 100644 index 0000000..e6fc605 --- /dev/null +++ b/android/.idea/modules/100380499/android.image_picker_android.androidTest.iml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/100380499/android.image_picker_android.iml b/android/.idea/modules/100380499/android.image_picker_android.iml new file mode 100644 index 0000000..885ed7b --- /dev/null +++ b/android/.idea/modules/100380499/android.image_picker_android.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/100380499/android.image_picker_android.main.iml b/android/.idea/modules/100380499/android.image_picker_android.main.iml new file mode 100644 index 0000000..5c32588 --- /dev/null +++ b/android/.idea/modules/100380499/android.image_picker_android.main.iml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/100380499/android.image_picker_android.unitTest.iml b/android/.idea/modules/100380499/android.image_picker_android.unitTest.iml new file mode 100644 index 0000000..2b713e8 --- /dev/null +++ b/android/.idea/modules/100380499/android.image_picker_android.unitTest.iml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1083963691/android.sqflite.androidTest.iml b/android/.idea/modules/1083963691/android.sqflite.androidTest.iml new file mode 100644 index 0000000..4e9ade7 --- /dev/null +++ b/android/.idea/modules/1083963691/android.sqflite.androidTest.iml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1083963691/android.sqflite.iml b/android/.idea/modules/1083963691/android.sqflite.iml new file mode 100644 index 0000000..2091371 --- /dev/null +++ b/android/.idea/modules/1083963691/android.sqflite.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1083963691/android.sqflite.main.iml b/android/.idea/modules/1083963691/android.sqflite.main.iml new file mode 100644 index 0000000..0905902 --- /dev/null +++ b/android/.idea/modules/1083963691/android.sqflite.main.iml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1083963691/android.sqflite.unitTest.iml b/android/.idea/modules/1083963691/android.sqflite.unitTest.iml new file mode 100644 index 0000000..a668c9a --- /dev/null +++ b/android/.idea/modules/1083963691/android.sqflite.unitTest.iml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/158619945/android.video_compress.androidTest.iml b/android/.idea/modules/158619945/android.video_compress.androidTest.iml new file mode 100644 index 0000000..342a258 --- /dev/null +++ b/android/.idea/modules/158619945/android.video_compress.androidTest.iml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/6c4ce65d1864b2b25391e287af628272/transformed/jetified-transcoder-0.10.4-api.jar + $USER_HOME$/.gradle/caches/transforms-3/3994ed2168e56435d27ecc38a4bb4147/transformed/jetified-egloo-0.6.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/e4aeadcae3a018c74f492201906593cb/transformed/jetified-kotlin-stdlib-jdk7-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/e4a9755878718b95645fc38409db5a7d/transformed/jetified-kotlin-stdlib-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/c476e70c03f7771018e9ac07efab644c/transformed/jetified-kotlin-stdlib-common-1.6.10.jar + $USER_HOME$/Library/Android/sdk/platforms/android-31/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + $MODULE_DIR$/../../../../build/video_compress/tmp/kotlin-classes/debug + $MODULE_DIR$/../../../../build/video_compress/intermediates/javac/debug/classes + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/158619945/android.video_compress.iml b/android/.idea/modules/158619945/android.video_compress.iml new file mode 100644 index 0000000..418c7ff --- /dev/null +++ b/android/.idea/modules/158619945/android.video_compress.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/158619945/android.video_compress.main.iml b/android/.idea/modules/158619945/android.video_compress.main.iml new file mode 100644 index 0000000..7960bf5 --- /dev/null +++ b/android/.idea/modules/158619945/android.video_compress.main.iml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_r_class_jar/debug/R.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/6c4ce65d1864b2b25391e287af628272/transformed/jetified-transcoder-0.10.4-api.jar + $USER_HOME$/.gradle/caches/transforms-3/3994ed2168e56435d27ecc38a4bb4147/transformed/jetified-egloo-0.6.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/e4aeadcae3a018c74f492201906593cb/transformed/jetified-kotlin-stdlib-jdk7-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/e4a9755878718b95645fc38409db5a7d/transformed/jetified-kotlin-stdlib-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/c476e70c03f7771018e9ac07efab644c/transformed/jetified-kotlin-stdlib-common-1.6.10.jar + $USER_HOME$/Library/Android/sdk/platforms/android-31/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/158619945/android.video_compress.unitTest.iml b/android/.idea/modules/158619945/android.video_compress.unitTest.iml new file mode 100644 index 0000000..f08d28c --- /dev/null +++ b/android/.idea/modules/158619945/android.video_compress.unitTest.iml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/6c4ce65d1864b2b25391e287af628272/transformed/jetified-transcoder-0.10.4-api.jar + $USER_HOME$/.gradle/caches/transforms-3/3994ed2168e56435d27ecc38a4bb4147/transformed/jetified-egloo-0.6.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/e4aeadcae3a018c74f492201906593cb/transformed/jetified-kotlin-stdlib-jdk7-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/e4a9755878718b95645fc38409db5a7d/transformed/jetified-kotlin-stdlib-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/c476e70c03f7771018e9ac07efab644c/transformed/jetified-kotlin-stdlib-common-1.6.10.jar + $USER_HOME$/Library/Android/sdk/platforms/android-31/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + $MODULE_DIR$/../../../../build/video_compress/tmp/kotlin-classes/debug + $MODULE_DIR$/../../../../build/video_compress/intermediates/javac/debug/classes + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/164148832/android.video_player_android.androidTest.iml b/android/.idea/modules/164148832/android.video_player_android.androidTest.iml new file mode 100644 index 0000000..be9c54e --- /dev/null +++ b/android/.idea/modules/164148832/android.video_player_android.androidTest.iml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/164148832/android.video_player_android.iml b/android/.idea/modules/164148832/android.video_player_android.iml new file mode 100644 index 0000000..329d938 --- /dev/null +++ b/android/.idea/modules/164148832/android.video_player_android.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/164148832/android.video_player_android.main.iml b/android/.idea/modules/164148832/android.video_player_android.main.iml new file mode 100644 index 0000000..e00fda8 --- /dev/null +++ b/android/.idea/modules/164148832/android.video_player_android.main.iml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/164148832/android.video_player_android.unitTest.iml b/android/.idea/modules/164148832/android.video_player_android.unitTest.iml new file mode 100644 index 0000000..7ffc422 --- /dev/null +++ b/android/.idea/modules/164148832/android.video_player_android.unitTest.iml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1676520554/android.firebase_core.androidTest.iml b/android/.idea/modules/1676520554/android.firebase_core.androidTest.iml new file mode 100644 index 0000000..ac6a63b --- /dev/null +++ b/android/.idea/modules/1676520554/android.firebase_core.androidTest.iml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1676520554/android.firebase_core.iml b/android/.idea/modules/1676520554/android.firebase_core.iml new file mode 100644 index 0000000..a1a4d66 --- /dev/null +++ b/android/.idea/modules/1676520554/android.firebase_core.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1676520554/android.firebase_core.main.iml b/android/.idea/modules/1676520554/android.firebase_core.main.iml new file mode 100644 index 0000000..bfe4fba --- /dev/null +++ b/android/.idea/modules/1676520554/android.firebase_core.main.iml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1676520554/android.firebase_core.unitTest.iml b/android/.idea/modules/1676520554/android.firebase_core.unitTest.iml new file mode 100644 index 0000000..098cb50 --- /dev/null +++ b/android/.idea/modules/1676520554/android.firebase_core.unitTest.iml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.androidTest.iml b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.androidTest.iml new file mode 100644 index 0000000..ff8ba77 --- /dev/null +++ b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.androidTest.iml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.iml b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.iml new file mode 100644 index 0000000..0e1acb5 --- /dev/null +++ b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.main.iml b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.main.iml new file mode 100644 index 0000000..7eca202 --- /dev/null +++ b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.main.iml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.unitTest.iml b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.unitTest.iml new file mode 100644 index 0000000..f048e87 --- /dev/null +++ b/android/.idea/modules/1894559384/android.flutter_plugin_android_lifecycle.unitTest.iml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/212706139/android.firebase_auth.androidTest.iml b/android/.idea/modules/212706139/android.firebase_auth.androidTest.iml new file mode 100644 index 0000000..8ff495d --- /dev/null +++ b/android/.idea/modules/212706139/android.firebase_auth.androidTest.iml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/212706139/android.firebase_auth.iml b/android/.idea/modules/212706139/android.firebase_auth.iml new file mode 100644 index 0000000..0bb94a1 --- /dev/null +++ b/android/.idea/modules/212706139/android.firebase_auth.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/212706139/android.firebase_auth.main.iml b/android/.idea/modules/212706139/android.firebase_auth.main.iml new file mode 100644 index 0000000..b939458 --- /dev/null +++ b/android/.idea/modules/212706139/android.firebase_auth.main.iml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/212706139/android.firebase_auth.unitTest.iml b/android/.idea/modules/212706139/android.firebase_auth.unitTest.iml new file mode 100644 index 0000000..0a3b108 --- /dev/null +++ b/android/.idea/modules/212706139/android.firebase_auth.unitTest.iml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/51314313/android.flutter_image_compress.androidTest.iml b/android/.idea/modules/51314313/android.flutter_image_compress.androidTest.iml new file mode 100644 index 0000000..8644733 --- /dev/null +++ b/android/.idea/modules/51314313/android.flutter_image_compress.androidTest.iml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/83a3cce54180dbdb05ff439879cd749b/transformed/exifinterface-1.3.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/b83a7b2aa5c83d0183736fb1825fe9a5/transformed/heifwriter-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/8374f25560ec8783b806b53c56f37749/transformed/jetified-kotlin-stdlib-jdk7-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/674fa2fd66bea25ff66379aaca94dcce/transformed/jetified-kotlin-stdlib-1.5.31.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/2bc6d5b54aaa419934ce0bbc7ab781fe/transformed/jetified-kotlin-stdlib-common-1.5.31.jar + $USER_HOME$/.gradle/caches/transforms-3/86286787d50103456404afba9fe1c64a/transformed/jetified-commons-io-2.6.jar + $USER_HOME$/Library/Android/sdk/platforms/android-29/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + $MODULE_DIR$/../../../../build/flutter_image_compress/tmp/kotlin-classes/debug + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/javac/debug/classes + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/51314313/android.flutter_image_compress.iml b/android/.idea/modules/51314313/android.flutter_image_compress.iml new file mode 100644 index 0000000..4951eeb --- /dev/null +++ b/android/.idea/modules/51314313/android.flutter_image_compress.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/51314313/android.flutter_image_compress.main.iml b/android/.idea/modules/51314313/android.flutter_image_compress.main.iml new file mode 100644 index 0000000..ef1006b --- /dev/null +++ b/android/.idea/modules/51314313/android.flutter_image_compress.main.iml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_r_class_jar/debug/R.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/83a3cce54180dbdb05ff439879cd749b/transformed/exifinterface-1.3.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/b83a7b2aa5c83d0183736fb1825fe9a5/transformed/heifwriter-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/8374f25560ec8783b806b53c56f37749/transformed/jetified-kotlin-stdlib-jdk7-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/674fa2fd66bea25ff66379aaca94dcce/transformed/jetified-kotlin-stdlib-1.5.31.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/2bc6d5b54aaa419934ce0bbc7ab781fe/transformed/jetified-kotlin-stdlib-common-1.5.31.jar + $USER_HOME$/.gradle/caches/transforms-3/86286787d50103456404afba9fe1c64a/transformed/jetified-commons-io-2.6.jar + $USER_HOME$/Library/Android/sdk/platforms/android-29/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/51314313/android.flutter_image_compress.unitTest.iml b/android/.idea/modules/51314313/android.flutter_image_compress.unitTest.iml new file mode 100644 index 0000000..01c4d67 --- /dev/null +++ b/android/.idea/modules/51314313/android.flutter_image_compress.unitTest.iml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/83a3cce54180dbdb05ff439879cd749b/transformed/exifinterface-1.3.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/b83a7b2aa5c83d0183736fb1825fe9a5/transformed/heifwriter-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/8374f25560ec8783b806b53c56f37749/transformed/jetified-kotlin-stdlib-jdk7-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/674fa2fd66bea25ff66379aaca94dcce/transformed/jetified-kotlin-stdlib-1.5.31.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/2bc6d5b54aaa419934ce0bbc7ab781fe/transformed/jetified-kotlin-stdlib-common-1.5.31.jar + $USER_HOME$/.gradle/caches/transforms-3/86286787d50103456404afba9fe1c64a/transformed/jetified-commons-io-2.6.jar + $USER_HOME$/Library/Android/sdk/platforms/android-29/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + $MODULE_DIR$/../../../../build/flutter_image_compress/tmp/kotlin-classes/debug + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/javac/debug/classes + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/android.iml b/android/.idea/modules/android.iml new file mode 100644 index 0000000..08c63a9 --- /dev/null +++ b/android/.idea/modules/android.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/app/android.app.androidTest.iml b/android/.idea/modules/app/android.app.androidTest.iml new file mode 100644 index 0000000..f675a85 --- /dev/null +++ b/android/.idea/modules/app/android.app.androidTest.iml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + $USER_HOME$/.gradle/caches/transforms-3/29096659b19afdda65e14af6c610854d/transformed/jetified-libs.jar + $MODULE_DIR$/../../../../build/cloud_firestore/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_auth/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_storage/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_core/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/flutter_plugin_android_lifecycle/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/sqflite/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/path_provider_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/connectivity_plus/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/image_picker_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/video_player_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/f0f1adb04a33d004cb528af2313996da/transformed/jetified-armeabi_v7a_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/e23ce0413c7cc0bd66de138df26a3b67/transformed/jetified-arm64_v8a_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/784cea0f329118fd018911acfccfdcbc/transformed/jetified-x86_64_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/1cbd902874ea368e6f454f9df4a3bdd6/transformed/jetified-x86_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/e4aeadcae3a018c74f492201906593cb/transformed/jetified-kotlin-stdlib-jdk7-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/e4a9755878718b95645fc38409db5a7d/transformed/jetified-kotlin-stdlib-1.6.10.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/ea82903f22705eb01f55cff279083203/transformed/core-1.6.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/1df11d4e51c8ca72b5b3c5ae616977a1/transformed/lifecycle-runtime-2.2.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.2.0/4ef09a745007778eef83b92f8f23987a8ea59496/lifecycle-common-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/6e6ef72044f18608c623cb90f396afb3/transformed/core-runtime-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.2.0/57136ff68ee784c6e19db34ed4a175338fadfde1/annotation-1.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/c476e70c03f7771018e9ac07efab644c/transformed/jetified-kotlin-stdlib-common-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/Library/Android/sdk/platforms/android-31/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + $MODULE_DIR$/../../../../build/app/tmp/kotlin-classes/debug + $MODULE_DIR$/../../../../build/app/intermediates/javac/debug/classes + $MODULE_DIR$/../../../../build/app/intermediates/compile_app_classes_jar/debug/classes.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/app/android.app.iml b/android/.idea/modules/app/android.app.iml new file mode 100644 index 0000000..5e5137c --- /dev/null +++ b/android/.idea/modules/app/android.app.iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/app/android.app.main.iml b/android/.idea/modules/app/android.app.main.iml new file mode 100644 index 0000000..69de9ed --- /dev/null +++ b/android/.idea/modules/app/android.app.main.iml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/app/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar + $USER_HOME$/.gradle/caches/transforms-3/29096659b19afdda65e14af6c610854d/transformed/jetified-libs.jar + $MODULE_DIR$/../../../../build/cloud_firestore/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_auth/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_storage/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_core/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/flutter_plugin_android_lifecycle/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/sqflite/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/path_provider_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/connectivity_plus/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/image_picker_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/video_player_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/f0f1adb04a33d004cb528af2313996da/transformed/jetified-armeabi_v7a_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/e23ce0413c7cc0bd66de138df26a3b67/transformed/jetified-arm64_v8a_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/784cea0f329118fd018911acfccfdcbc/transformed/jetified-x86_64_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/1cbd902874ea368e6f454f9df4a3bdd6/transformed/jetified-x86_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/32bbc044f6ffa1e2db985c34112193a5/transformed/core-1.8.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/142061a8a65da7a6ebbeb271aca847d5/transformed/lifecycle-runtime-2.3.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.3.1/fc466261d52f4433863642fb40d12441ae274a98/lifecycle-common-2.3.1.jar + $USER_HOME$/.gradle/caches/transforms-3/61f04b6d3d9300c9ac037c43c00da909/transformed/core-runtime-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.3.0/21f49f5f9b85fc49de712539f79123119740595/annotation-1.3.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/e4aeadcae3a018c74f492201906593cb/transformed/jetified-kotlin-stdlib-jdk7-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/e4a9755878718b95645fc38409db5a7d/transformed/jetified-kotlin-stdlib-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/c476e70c03f7771018e9ac07efab644c/transformed/jetified-kotlin-stdlib-common-1.6.10.jar + $USER_HOME$/Library/Android/sdk/platforms/android-31/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/modules/app/android.app.unitTest.iml b/android/.idea/modules/app/android.app.unitTest.iml new file mode 100644 index 0000000..c5c9644 --- /dev/null +++ b/android/.idea/modules/app/android.app.unitTest.iml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + $MODULE_DIR$/../../../../build/app/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar + $USER_HOME$/.gradle/caches/transforms-3/29096659b19afdda65e14af6c610854d/transformed/jetified-libs.jar + $MODULE_DIR$/../../../../build/cloud_firestore/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_auth/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_storage/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/firebase_core/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/flutter_plugin_android_lifecycle/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/sqflite/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/path_provider_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/connectivity_plus/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/video_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/image_picker_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/video_player_android/intermediates/compile_library_classes_jar/debug/classes.jar + $MODULE_DIR$/../../../../build/flutter_image_compress/intermediates/compile_library_classes_jar/debug/classes.jar + $USER_HOME$/.gradle/caches/transforms-3/f0f1adb04a33d004cb528af2313996da/transformed/jetified-armeabi_v7a_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/e23ce0413c7cc0bd66de138df26a3b67/transformed/jetified-arm64_v8a_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/784cea0f329118fd018911acfccfdcbc/transformed/jetified-x86_64_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/1cbd902874ea368e6f454f9df4a3bdd6/transformed/jetified-x86_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/transforms-3/0e2747bc4b3e68ee642784c46004ab6a/transformed/jetified-flutter_embedding_debug-1.0.0-e85ea0e79c6d894c120cda4ee8ee10fe6745e187.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-java8/2.2.0/cd3478503da69b1a7e0319bd2d1389943db9b364/lifecycle-common-java8-2.2.0.jar + $USER_HOME$/.gradle/caches/transforms-3/a6a190149aec405c3cfb182b382d2b23/transformed/fragment-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f92e2d873381f1d1707360aad1094c91/transformed/viewpager-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c4f8f542421ca869fe88630b711a9042/transformed/loader-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7e7d1f245490cb066db8d17399e38dff/transformed/jetified-activity-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/f591f286f2f99b78d4e08a61c6b49e36/transformed/customview-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/32bbc044f6ffa1e2db985c34112193a5/transformed/core-1.8.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/142061a8a65da7a6ebbeb271aca847d5/transformed/lifecycle-runtime-2.3.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/c614f25d6a6fb6f06ec4efe16626130e/transformed/jetified-savedstate-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/40f409d23b8ffcca63ceaed92ef8d3be/transformed/lifecycle-livedata-2.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/a97144b5f2481def8f191ab3ce896b08/transformed/lifecycle-livedata-core-2.0.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.3.1/fc466261d52f4433863642fb40d12441ae274a98/lifecycle-common-2.3.1.jar + $USER_HOME$/.gradle/caches/transforms-3/61f04b6d3d9300c9ac037c43c00da909/transformed/core-runtime-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/65dfeedcfcabd5ccf2fb4152fac7d39f/transformed/versionedparcelable-1.1.1-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar + $USER_HOME$/.gradle/caches/transforms-3/3bdc4799aa07daca58b6fe282e9fa11c/transformed/lifecycle-viewmodel-2.1.0-api.jar + $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.3.0/21f49f5f9b85fc49de712539f79123119740595/annotation-1.3.0.jar + $USER_HOME$/.gradle/caches/transforms-3/f6c9cce7947af7a04945f0e867293bcb/transformed/jetified-annotation-experimental-1.1.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/4e4a9a64633afa7567bf1538ebd15c07/transformed/jetified-tracing-1.0.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/e131f24a1c742084985a1e21d27160b6/transformed/jetified-window-java-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/7a3d2910e59501f79dad485f7528c831/transformed/jetified-window-1.0.0-beta04-api.jar + $USER_HOME$/.gradle/caches/transforms-3/5137eaf831106b2934f857d8670b1985/transformed/jetified-kotlinx-coroutines-android-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/8aca5707800dc7f8d66860ecd5aa4204/transformed/jetified-kotlinx-coroutines-core-jvm-1.5.2.jar + $USER_HOME$/.gradle/caches/transforms-3/5460b2ddd63f443a5091de4410ff33f0/transformed/jetified-kotlin-stdlib-jdk8-1.5.30.jar + $USER_HOME$/.gradle/caches/transforms-3/e4aeadcae3a018c74f492201906593cb/transformed/jetified-kotlin-stdlib-jdk7-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/e4a9755878718b95645fc38409db5a7d/transformed/jetified-kotlin-stdlib-1.6.10.jar + $USER_HOME$/.gradle/caches/transforms-3/64b43ea9936f21b9430addc94640a15a/transformed/jetified-annotations-13.0.jar + $USER_HOME$/.gradle/caches/transforms-3/c476e70c03f7771018e9ac07efab644c/transformed/jetified-kotlin-stdlib-common-1.6.10.jar + $USER_HOME$/Library/Android/sdk/platforms/android-31/android.jar + $USER_HOME$/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar + + + + + + + + + + + + $MODULE_DIR$/../../../../build/app/tmp/kotlin-classes/debug + $MODULE_DIR$/../../../../build/app/intermediates/javac/debug/classes + $MODULE_DIR$/../../../../build/app/intermediates/compile_app_classes_jar/debug/classes.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/android/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/workspace.xml b/android/.idea/workspace.xml new file mode 100644 index 0000000..671b6cc --- /dev/null +++ b/android/.idea/workspace.xml @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1659465460023 + + + + \ No newline at end of file diff --git a/android/app/.DS_Store b/android/app/.DS_Store new file mode 100644 index 0000000..54124d8 Binary files /dev/null and b/android/app/.DS_Store differ diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..6c0fd4f --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,81 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def minSdk = localProperties.getProperty('flutter.minSDKVersion') +if (minSdk == null) { + minSdk = '23' +} + +def targetSdk = localProperties.getProperty('flutter.targetSDKVersion') +if (targetSdk == null) { + targetSdk = '32' +} + +apply plugin: 'com.android.application' +// START: FlutterFire Configuration +apply plugin: 'com.google.gms.google-services' +// END: FlutterFire Configuration +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + 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 { + applicationId "com.redting.developers" + minSdkVersion minSdk.toInteger() + targetSdkVersion targetSdk.toInteger() + 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 { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..7812e60 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,54 @@ +{ + "project_info": { + "project_number": "309040849360", + "project_id": "redting-d5504", + "storage_bucket": "redting-d5504.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:309040849360:android:e67558df37e022f4bc99d5", + "android_client_info": { + "package_name": "com.redting.developers" + } + }, + "oauth_client": [ + { + "client_id": "309040849360-gdiqkfnqtqd1cc5r0jbjglmofhp1je3h.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.redting.developers", + "certificate_hash": "31c155c32b86c34876b774f2a591def878f05110" + } + }, + { + "client_id": "309040849360-lg9rimchd162dtq9h4v3tl7urjfjjv1p.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyC5Fs1bokNE96VvZoLSKIhfa88lcm9-w8A" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "309040849360-lg9rimchd162dtq9h4v3tl7urjfjjv1p.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "309040849360-ajflptqec7341i58rg3np5755gsi6ksc.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.redting.developers.redting" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/.DS_Store b/android/app/src/.DS_Store new file mode 100644 index 0000000..3300438 Binary files /dev/null and b/android/app/src/.DS_Store differ diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..65de103 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/app/src/main/.DS_Store b/android/app/src/main/.DS_Store new file mode 100644 index 0000000..9a63129 Binary files /dev/null and b/android/app/src/main/.DS_Store differ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e4b717f --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/redting/developers/MainActivity.kt b/android/app/src/main/kotlin/com/redting/developers/MainActivity.kt new file mode 100644 index 0000000..b35bc8a --- /dev/null +++ b/android/app/src/main/kotlin/com/redting/developers/MainActivity.kt @@ -0,0 +1,6 @@ +package com.redting.developers + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/playstore.png b/android/app/src/main/playstore.png new file mode 100644 index 0000000..125f6b0 Binary files /dev/null and b/android/app/src/main/playstore.png differ diff --git a/android/app/src/main/res/.DS_Store b/android/app/src/main/res/.DS_Store new file mode 100644 index 0000000..19af49d Binary files /dev/null and b/android/app/src/main/res/.DS_Store differ 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 0000000..71fb420 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ 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 0000000..37cdaa6 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..0c75d7a Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ 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 0000000..07d7d99 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ 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 0000000..90a6ae7 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ 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..2711556 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..1d04327 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,34 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.1' + // START: FlutterFire Configuration + classpath 'com.google.gms:google-services:4.3.10' + // END: FlutterFire Configuration + 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') +} + +task clean(type: 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..cc5527d --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/android/redting_android.iml b/android/redting_android.iml new file mode 100644 index 0000000..3e44773 --- /dev/null +++ b/android/redting_android.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..9f21b95 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/fonts/.DS_Store b/assets/fonts/.DS_Store new file mode 100644 index 0000000..03c34e6 Binary files /dev/null and b/assets/fonts/.DS_Store differ diff --git a/assets/fonts/Righteous-Regular.ttf b/assets/fonts/Righteous-Regular.ttf new file mode 100644 index 0000000..fc9c0a8 Binary files /dev/null and b/assets/fonts/Righteous-Regular.ttf differ diff --git a/assets/fonts/Rubik-Light.ttf b/assets/fonts/Rubik-Light.ttf new file mode 100644 index 0000000..b899363 Binary files /dev/null and b/assets/fonts/Rubik-Light.ttf differ diff --git a/assets/fonts/Rubik-Regular.ttf b/assets/fonts/Rubik-Regular.ttf new file mode 100644 index 0000000..0e2a6f4 Binary files /dev/null and b/assets/fonts/Rubik-Regular.ttf differ diff --git a/assets/images/.DS_Store b/assets/images/.DS_Store new file mode 100644 index 0000000..8269800 Binary files /dev/null and b/assets/images/.DS_Store differ diff --git a/assets/images/redting_black.png b/assets/images/redting_black.png new file mode 100644 index 0000000..c5aa39d Binary files /dev/null and b/assets/images/redting_black.png differ diff --git a/assets/images/redting_red.png b/assets/images/redting_red.png new file mode 100644 index 0000000..c09c07e Binary files /dev/null and b/assets/images/redting_red.png differ diff --git a/assets/images/redting_white.png b/assets/images/redting_white.png new file mode 100644 index 0000000..4cbffc0 Binary files /dev/null and b/assets/images/redting_white.png differ diff --git a/ios/.DS_Store b/ios/.DS_Store new file mode 100644 index 0000000..ecb130a Binary files /dev/null and b/ios/.DS_Store differ 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..8d4492f --- /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 + 9.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +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 new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +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..63b4469 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,45 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '10.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__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0' + end + end +end + diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..ac593a7 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,916 @@ +PODS: + - abseil/algorithm (1.20211102.0): + - abseil/algorithm/algorithm (= 1.20211102.0) + - abseil/algorithm/container (= 1.20211102.0) + - abseil/algorithm/algorithm (1.20211102.0): + - abseil/base/config + - abseil/algorithm/container (1.20211102.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/base (1.20211102.0): + - abseil/base/atomic_hook (= 1.20211102.0) + - abseil/base/base (= 1.20211102.0) + - abseil/base/base_internal (= 1.20211102.0) + - abseil/base/config (= 1.20211102.0) + - abseil/base/core_headers (= 1.20211102.0) + - abseil/base/dynamic_annotations (= 1.20211102.0) + - abseil/base/endian (= 1.20211102.0) + - abseil/base/errno_saver (= 1.20211102.0) + - abseil/base/fast_type_id (= 1.20211102.0) + - abseil/base/log_severity (= 1.20211102.0) + - abseil/base/malloc_internal (= 1.20211102.0) + - abseil/base/pretty_function (= 1.20211102.0) + - abseil/base/raw_logging_internal (= 1.20211102.0) + - abseil/base/spinlock_wait (= 1.20211102.0) + - abseil/base/strerror (= 1.20211102.0) + - abseil/base/throw_delegate (= 1.20211102.0) + - abseil/base/atomic_hook (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/base (1.20211102.0): + - abseil/base/atomic_hook + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/log_severity + - abseil/base/raw_logging_internal + - abseil/base/spinlock_wait + - abseil/meta/type_traits + - abseil/base/base_internal (1.20211102.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/base/config (1.20211102.0) + - abseil/base/core_headers (1.20211102.0): + - abseil/base/config + - abseil/base/dynamic_annotations (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver (1.20211102.0): + - abseil/base/config + - abseil/base/fast_type_id (1.20211102.0): + - abseil/base/config + - abseil/base/log_severity (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/malloc_internal (1.20211102.0): + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/base/pretty_function (1.20211102.0) + - abseil/base/raw_logging_internal (1.20211102.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/base/spinlock_wait (1.20211102.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/base/strerror (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/base/throw_delegate (1.20211102.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/container/common (1.20211102.0): + - abseil/meta/type_traits + - abseil/types/optional + - abseil/container/compressed_tuple (1.20211102.0): + - abseil/utility/utility + - abseil/container/container_memory (1.20211102.0): + - abseil/base/config + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/utility/utility + - abseil/container/fixed_array (1.20211102.0): + - abseil/algorithm/algorithm + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/throw_delegate + - abseil/container/compressed_tuple + - abseil/memory/memory + - abseil/container/flat_hash_map (1.20211102.0): + - abseil/algorithm/container + - abseil/container/container_memory + - abseil/container/hash_function_defaults + - abseil/container/raw_hash_map + - abseil/memory/memory + - abseil/container/hash_function_defaults (1.20211102.0): + - abseil/base/config + - abseil/hash/hash + - abseil/strings/cord + - abseil/strings/strings + - abseil/container/hash_policy_traits (1.20211102.0): + - abseil/meta/type_traits + - abseil/container/hashtable_debug_hooks (1.20211102.0): + - abseil/base/config + - abseil/container/hashtablez_sampler (1.20211102.0): + - abseil/base/base + - abseil/base/core_headers + - abseil/container/have_sse + - abseil/debugging/stacktrace + - abseil/memory/memory + - abseil/profiling/exponential_biased + - abseil/profiling/sample_recorder + - abseil/synchronization/synchronization + - abseil/utility/utility + - abseil/container/have_sse (1.20211102.0) + - abseil/container/inlined_vector (1.20211102.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/container/inlined_vector_internal + - abseil/memory/memory + - abseil/container/inlined_vector_internal (1.20211102.0): + - abseil/base/core_headers + - abseil/container/compressed_tuple + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/span + - abseil/container/layout (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/strings/strings + - abseil/types/span + - abseil/utility/utility + - abseil/container/raw_hash_map (1.20211102.0): + - abseil/base/throw_delegate + - abseil/container/container_memory + - abseil/container/raw_hash_set + - abseil/container/raw_hash_set (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/container/common + - abseil/container/compressed_tuple + - abseil/container/container_memory + - abseil/container/hash_policy_traits + - abseil/container/hashtable_debug_hooks + - abseil/container/hashtablez_sampler + - abseil/container/have_sse + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/utility/utility + - abseil/debugging/debugging_internal (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/errno_saver + - abseil/base/raw_logging_internal + - abseil/debugging/demangle_internal (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/debugging/stacktrace (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/debugging/debugging_internal + - abseil/debugging/symbolize (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/malloc_internal + - abseil/base/raw_logging_internal + - abseil/debugging/debugging_internal + - abseil/debugging/demangle_internal + - abseil/strings/strings + - abseil/functional/bind_front (1.20211102.0): + - abseil/base/base_internal + - abseil/container/compressed_tuple + - abseil/meta/type_traits + - abseil/utility/utility + - abseil/functional/function_ref (1.20211102.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/hash/city (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/hash/hash (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/container/fixed_array + - abseil/hash/city + - abseil/hash/low_level_hash + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/types/optional + - abseil/types/variant + - abseil/utility/utility + - abseil/hash/low_level_hash (1.20211102.0): + - abseil/base/config + - abseil/base/endian + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/memory (1.20211102.0): + - abseil/memory/memory (= 1.20211102.0) + - abseil/memory/memory (1.20211102.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/meta (1.20211102.0): + - abseil/meta/type_traits (= 1.20211102.0) + - abseil/meta/type_traits (1.20211102.0): + - abseil/base/config + - abseil/numeric/bits (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/numeric/int128 (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/numeric/bits + - abseil/numeric/representation (1.20211102.0): + - abseil/base/config + - abseil/profiling/exponential_biased (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/profiling/sample_recorder (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/synchronization/synchronization + - abseil/time/time + - abseil/random/distributions (1.20211102.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/random/internal/distribution_caller + - abseil/random/internal/fast_uniform_bits + - abseil/random/internal/fastmath + - abseil/random/internal/generate_real + - abseil/random/internal/iostream_state_saver + - abseil/random/internal/traits + - abseil/random/internal/uniform_helper + - abseil/random/internal/wide_multiply + - abseil/strings/strings + - abseil/random/internal/distribution_caller (1.20211102.0): + - abseil/base/config + - abseil/base/fast_type_id + - abseil/utility/utility + - abseil/random/internal/fast_uniform_bits (1.20211102.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/random/internal/fastmath (1.20211102.0): + - abseil/numeric/bits + - abseil/random/internal/generate_real (1.20211102.0): + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/random/internal/fastmath + - abseil/random/internal/traits + - abseil/random/internal/iostream_state_saver (1.20211102.0): + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/random/internal/nonsecure_base (1.20211102.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/random/internal/pool_urbg + - abseil/random/internal/salted_seed_seq + - abseil/random/internal/seed_material + - abseil/types/optional + - abseil/types/span + - abseil/random/internal/pcg_engine (1.20211102.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/random/internal/fastmath + - abseil/random/internal/iostream_state_saver + - abseil/random/internal/platform (1.20211102.0): + - abseil/base/config + - abseil/random/internal/pool_urbg (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/random/internal/randen + - abseil/random/internal/seed_material + - abseil/random/internal/traits + - abseil/random/seed_gen_exception + - abseil/types/span + - abseil/random/internal/randen (1.20211102.0): + - abseil/base/raw_logging_internal + - abseil/random/internal/platform + - abseil/random/internal/randen_hwaes + - abseil/random/internal/randen_slow + - abseil/random/internal/randen_engine (1.20211102.0): + - abseil/base/endian + - abseil/meta/type_traits + - abseil/random/internal/iostream_state_saver + - abseil/random/internal/randen + - abseil/random/internal/randen_hwaes (1.20211102.0): + - abseil/base/config + - abseil/random/internal/platform + - abseil/random/internal/randen_hwaes_impl + - abseil/random/internal/randen_hwaes_impl (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/numeric/int128 + - abseil/random/internal/platform + - abseil/random/internal/randen_slow (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/numeric/int128 + - abseil/random/internal/platform + - abseil/random/internal/salted_seed_seq (1.20211102.0): + - abseil/container/inlined_vector + - abseil/meta/type_traits + - abseil/random/internal/seed_material + - abseil/types/optional + - abseil/types/span + - abseil/random/internal/seed_material (1.20211102.0): + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/random/internal/fast_uniform_bits + - abseil/strings/strings + - abseil/types/optional + - abseil/types/span + - abseil/random/internal/traits (1.20211102.0): + - abseil/base/config + - abseil/random/internal/uniform_helper (1.20211102.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/random/internal/traits + - abseil/random/internal/wide_multiply (1.20211102.0): + - abseil/base/config + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/random/internal/traits + - abseil/random/random (1.20211102.0): + - abseil/random/distributions + - abseil/random/internal/nonsecure_base + - abseil/random/internal/pcg_engine + - abseil/random/internal/pool_urbg + - abseil/random/internal/randen_engine + - abseil/random/seed_sequences + - abseil/random/seed_gen_exception (1.20211102.0): + - abseil/base/config + - abseil/random/seed_sequences (1.20211102.0): + - abseil/container/inlined_vector + - abseil/random/internal/nonsecure_base + - abseil/random/internal/pool_urbg + - abseil/random/internal/salted_seed_seq + - abseil/random/internal/seed_material + - abseil/random/seed_gen_exception + - abseil/types/span + - abseil/status/status (1.20211102.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/container/inlined_vector + - abseil/debugging/stacktrace + - abseil/debugging/symbolize + - abseil/functional/function_ref + - abseil/strings/cord + - abseil/strings/str_format + - abseil/strings/strings + - abseil/types/optional + - abseil/status/statusor (1.20211102.0): + - abseil/base/base + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/meta/type_traits + - abseil/status/status + - abseil/strings/strings + - abseil/types/variant + - abseil/utility/utility + - abseil/strings/cord (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/container/fixed_array + - abseil/container/inlined_vector + - abseil/functional/function_ref + - abseil/meta/type_traits + - abseil/strings/cord_internal + - abseil/strings/cordz_functions + - abseil/strings/cordz_info + - abseil/strings/cordz_statistics + - abseil/strings/cordz_update_scope + - abseil/strings/cordz_update_tracker + - abseil/strings/internal + - abseil/strings/str_format + - abseil/strings/strings + - abseil/types/optional + - abseil/strings/cord_internal (1.20211102.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/base/throw_delegate + - abseil/container/compressed_tuple + - abseil/container/inlined_vector + - abseil/container/layout + - abseil/functional/function_ref + - abseil/meta/type_traits + - abseil/strings/strings + - abseil/types/span + - abseil/strings/cordz_functions (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/profiling/exponential_biased + - abseil/strings/cordz_handle (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/synchronization/synchronization + - abseil/strings/cordz_info (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/container/inlined_vector + - abseil/debugging/stacktrace + - abseil/strings/cord_internal + - abseil/strings/cordz_functions + - abseil/strings/cordz_handle + - abseil/strings/cordz_statistics + - abseil/strings/cordz_update_tracker + - abseil/synchronization/synchronization + - abseil/types/span + - abseil/strings/cordz_statistics (1.20211102.0): + - abseil/base/config + - abseil/strings/cordz_update_tracker + - abseil/strings/cordz_update_scope (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/strings/cord_internal + - abseil/strings/cordz_info + - abseil/strings/cordz_update_tracker + - abseil/strings/cordz_update_tracker (1.20211102.0): + - abseil/base/config + - abseil/strings/internal (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/meta/type_traits + - abseil/strings/str_format (1.20211102.0): + - abseil/strings/str_format_internal + - abseil/strings/str_format_internal (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/functional/function_ref + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/numeric/representation + - abseil/strings/strings + - abseil/types/optional + - abseil/types/span + - abseil/strings/strings (1.20211102.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/base/throw_delegate + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/strings/internal + - abseil/synchronization/graphcycles_internal (1.20211102.0): + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/malloc_internal + - abseil/base/raw_logging_internal + - abseil/synchronization/kernel_timeout_internal (1.20211102.0): + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/time/time + - abseil/synchronization/synchronization (1.20211102.0): + - abseil/base/atomic_hook + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/malloc_internal + - abseil/base/raw_logging_internal + - abseil/debugging/stacktrace + - abseil/debugging/symbolize + - abseil/synchronization/graphcycles_internal + - abseil/synchronization/kernel_timeout_internal + - abseil/time/time + - abseil/time (1.20211102.0): + - abseil/time/internal (= 1.20211102.0) + - abseil/time/time (= 1.20211102.0) + - abseil/time/internal (1.20211102.0): + - abseil/time/internal/cctz (= 1.20211102.0) + - abseil/time/internal/cctz (1.20211102.0): + - abseil/time/internal/cctz/civil_time (= 1.20211102.0) + - abseil/time/internal/cctz/time_zone (= 1.20211102.0) + - abseil/time/internal/cctz/civil_time (1.20211102.0): + - abseil/base/config + - abseil/time/internal/cctz/time_zone (1.20211102.0): + - abseil/base/config + - abseil/time/internal/cctz/civil_time + - abseil/time/time (1.20211102.0): + - abseil/base/base + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/time/internal/cctz/civil_time + - abseil/time/internal/cctz/time_zone + - abseil/types (1.20211102.0): + - abseil/types/any (= 1.20211102.0) + - abseil/types/bad_any_cast (= 1.20211102.0) + - abseil/types/bad_any_cast_impl (= 1.20211102.0) + - abseil/types/bad_optional_access (= 1.20211102.0) + - abseil/types/bad_variant_access (= 1.20211102.0) + - abseil/types/compare (= 1.20211102.0) + - abseil/types/optional (= 1.20211102.0) + - abseil/types/span (= 1.20211102.0) + - abseil/types/variant (= 1.20211102.0) + - abseil/types/any (1.20211102.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/fast_type_id + - abseil/meta/type_traits + - abseil/types/bad_any_cast + - abseil/utility/utility + - abseil/types/bad_any_cast (1.20211102.0): + - abseil/base/config + - abseil/types/bad_any_cast_impl + - abseil/types/bad_any_cast_impl (1.20211102.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/bad_optional_access (1.20211102.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/bad_variant_access (1.20211102.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/compare (1.20211102.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/optional (1.20211102.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/bad_optional_access + - abseil/utility/utility + - abseil/types/span (1.20211102.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/meta/type_traits + - abseil/types/variant (1.20211102.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/bad_variant_access + - abseil/utility/utility + - abseil/utility/utility (1.20211102.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/meta/type_traits + - BoringSSL-GRPC (0.0.24): + - BoringSSL-GRPC/Implementation (= 0.0.24) + - BoringSSL-GRPC/Interface (= 0.0.24) + - BoringSSL-GRPC/Implementation (0.0.24): + - BoringSSL-GRPC/Interface (= 0.0.24) + - BoringSSL-GRPC/Interface (0.0.24) + - cloud_firestore (3.4.3): + - Firebase/Firestore (= 9.3.0) + - firebase_core + - Flutter + - connectivity_plus (0.0.1): + - Flutter + - ReachabilitySwift + - Firebase/Auth (9.3.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 9.3.0) + - Firebase/CoreOnly (9.3.0): + - FirebaseCore (= 9.3.0) + - Firebase/Firestore (9.3.0): + - Firebase/CoreOnly + - FirebaseFirestore (~> 9.3.0) + - Firebase/Storage (9.3.0): + - Firebase/CoreOnly + - FirebaseStorage (~> 9.3.0) + - firebase_auth (3.6.2): + - Firebase/Auth (= 9.3.0) + - firebase_core + - Flutter + - firebase_core (1.20.0): + - Firebase/CoreOnly (= 9.3.0) + - Flutter + - firebase_storage (10.3.4): + - Firebase/Storage (= 9.3.0) + - firebase_core + - Flutter + - FirebaseAppCheckInterop (9.4.0) + - FirebaseAuth (9.3.0): + - FirebaseCore (~> 9.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/Environment (~> 7.7) + - GTMSessionFetcher/Core (< 3.0, >= 1.7) + - FirebaseAuthInterop (9.4.0) + - FirebaseCore (9.3.0): + - FirebaseCoreDiagnostics (~> 9.0) + - FirebaseCoreInternal (~> 9.0) + - GoogleUtilities/Environment (~> 7.7) + - GoogleUtilities/Logger (~> 7.7) + - FirebaseCoreDiagnostics (9.4.0): + - GoogleDataTransport (< 10.0.0, >= 9.1.4) + - GoogleUtilities/Environment (~> 7.7) + - GoogleUtilities/Logger (~> 7.7) + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseCoreExtension (9.4.0): + - FirebaseCore (~> 9.0) + - FirebaseCoreInternal (9.4.0): + - "GoogleUtilities/NSData+zlib (~> 7.7)" + - FirebaseFirestore (9.3.0): + - abseil/algorithm (~> 1.20211102.0) + - abseil/base (~> 1.20211102.0) + - abseil/container/flat_hash_map (~> 1.20211102.0) + - abseil/memory (~> 1.20211102.0) + - abseil/meta (~> 1.20211102.0) + - abseil/strings/strings (~> 1.20211102.0) + - abseil/time (~> 1.20211102.0) + - abseil/types (~> 1.20211102.0) + - FirebaseCore (~> 9.0) + - "gRPC-C++ (~> 1.44.0)" + - leveldb-library (~> 1.22) + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseStorage (9.3.0): + - FirebaseAppCheckInterop (~> 9.0) + - FirebaseAuthInterop (~> 9.0) + - FirebaseCore (~> 9.0) + - FirebaseCoreExtension (~> 9.0) + - FirebaseStorageInternal (~> 9.0) + - FirebaseStorageInternal (9.4.0): + - FirebaseCore (~> 9.0) + - GTMSessionFetcher/Core (< 3.0, >= 1.7) + - Flutter (1.0.0) + - flutter_image_compress (1.0.0): + - Flutter + - Mantle + - SDWebImage + - SDWebImageWebPCoder + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - GoogleDataTransport (9.2.0): + - GoogleUtilities/Environment (~> 7.7) + - nanopb (< 2.30910.0, >= 2.30908.0) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.7.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.7.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.7.0): + - GoogleUtilities/Environment + - GoogleUtilities/Network (7.7.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.7.0)" + - GoogleUtilities/Reachability (7.7.0): + - GoogleUtilities/Logger + - "gRPC-C++ (1.44.0)": + - "gRPC-C++/Implementation (= 1.44.0)" + - "gRPC-C++/Interface (= 1.44.0)" + - "gRPC-C++/Implementation (1.44.0)": + - abseil/base/base (= 1.20211102.0) + - abseil/base/core_headers (= 1.20211102.0) + - abseil/container/flat_hash_map (= 1.20211102.0) + - abseil/container/inlined_vector (= 1.20211102.0) + - abseil/functional/bind_front (= 1.20211102.0) + - abseil/hash/hash (= 1.20211102.0) + - abseil/memory/memory (= 1.20211102.0) + - abseil/random/random (= 1.20211102.0) + - abseil/status/status (= 1.20211102.0) + - abseil/status/statusor (= 1.20211102.0) + - abseil/strings/cord (= 1.20211102.0) + - abseil/strings/str_format (= 1.20211102.0) + - abseil/strings/strings (= 1.20211102.0) + - abseil/synchronization/synchronization (= 1.20211102.0) + - abseil/time/time (= 1.20211102.0) + - abseil/types/optional (= 1.20211102.0) + - abseil/types/variant (= 1.20211102.0) + - abseil/utility/utility (= 1.20211102.0) + - "gRPC-C++/Interface (= 1.44.0)" + - gRPC-Core (= 1.44.0) + - "gRPC-C++/Interface (1.44.0)" + - gRPC-Core (1.44.0): + - gRPC-Core/Implementation (= 1.44.0) + - gRPC-Core/Interface (= 1.44.0) + - gRPC-Core/Implementation (1.44.0): + - abseil/base/base (= 1.20211102.0) + - abseil/base/core_headers (= 1.20211102.0) + - abseil/container/flat_hash_map (= 1.20211102.0) + - abseil/container/inlined_vector (= 1.20211102.0) + - abseil/functional/bind_front (= 1.20211102.0) + - abseil/hash/hash (= 1.20211102.0) + - abseil/memory/memory (= 1.20211102.0) + - abseil/random/random (= 1.20211102.0) + - abseil/status/status (= 1.20211102.0) + - abseil/status/statusor (= 1.20211102.0) + - abseil/strings/cord (= 1.20211102.0) + - abseil/strings/str_format (= 1.20211102.0) + - abseil/strings/strings (= 1.20211102.0) + - abseil/synchronization/synchronization (= 1.20211102.0) + - abseil/time/time (= 1.20211102.0) + - abseil/types/optional (= 1.20211102.0) + - abseil/types/variant (= 1.20211102.0) + - abseil/utility/utility (= 1.20211102.0) + - BoringSSL-GRPC (= 0.0.24) + - gRPC-Core/Interface (= 1.44.0) + - Libuv-gRPC (= 0.0.10) + - gRPC-Core/Interface (1.44.0) + - GTMSessionFetcher/Core (2.0.0) + - image_picker_ios (0.0.1): + - Flutter + - leveldb-library (1.22.1) + - Libuv-gRPC (0.0.10): + - Libuv-gRPC/Implementation (= 0.0.10) + - Libuv-gRPC/Interface (= 0.0.10) + - Libuv-gRPC/Implementation (0.0.10): + - Libuv-gRPC/Interface (= 0.0.10) + - Libuv-gRPC/Interface (0.0.10) + - libwebp (1.2.3): + - libwebp/demux (= 1.2.3) + - libwebp/mux (= 1.2.3) + - libwebp/webp (= 1.2.3) + - libwebp/demux (1.2.3): + - libwebp/webp + - libwebp/mux (1.2.3): + - libwebp/demux + - libwebp/webp (1.2.3) + - Mantle (2.2.0): + - Mantle/extobjc (= 2.2.0) + - Mantle/extobjc (2.2.0) + - nanopb (2.30909.0): + - nanopb/decode (= 2.30909.0) + - nanopb/encode (= 2.30909.0) + - nanopb/decode (2.30909.0) + - nanopb/encode (2.30909.0) + - path_provider_ios (0.0.1): + - Flutter + - PromisesObjC (2.1.1) + - ReachabilitySwift (5.0.0) + - SDWebImage (5.13.2): + - SDWebImage/Core (= 5.13.2) + - SDWebImage/Core (5.13.2) + - SDWebImageWebPCoder (0.9.0): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.13) + - sqflite (0.0.2): + - Flutter + - FMDB (>= 2.7.5) + - video_compress (0.3.0): + - Flutter + - video_player_avfoundation (0.0.1): + - Flutter + +DEPENDENCIES: + - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) + - Flutter (from `Flutter`) + - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - video_compress (from `.symlinks/plugins/video_compress/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) + +SPEC REPOS: + trunk: + - abseil + - BoringSSL-GRPC + - Firebase + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseFirestore + - FirebaseStorage + - FirebaseStorageInternal + - FMDB + - GoogleDataTransport + - GoogleUtilities + - "gRPC-C++" + - gRPC-Core + - GTMSessionFetcher + - leveldb-library + - Libuv-gRPC + - libwebp + - Mantle + - nanopb + - PromisesObjC + - ReachabilitySwift + - SDWebImage + - SDWebImageWebPCoder + +EXTERNAL SOURCES: + cloud_firestore: + :path: ".symlinks/plugins/cloud_firestore/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_storage: + :path: ".symlinks/plugins/firebase_storage/ios" + Flutter: + :path: Flutter + flutter_image_compress: + :path: ".symlinks/plugins/flutter_image_compress/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" + video_compress: + :path: ".symlinks/plugins/video_compress/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/ios" + +SPEC CHECKSUMS: + abseil: ebe5b5529fb05d93a8bdb7951607be08b7fa71bc + BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33 + cloud_firestore: 08093e207dfb2e6c82a6b272def699e022548786 + connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e + Firebase: ef75abb1cdbc746d5a38f4e26c422c807b189b8c + firebase_auth: e4bd9a01f6c45764bf3bfb0cea4d5ce3673a92a2 + firebase_core: 96214f90497b808a2cf2a24517084c5f6de37b53 + firebase_storage: e95ec41580e311cb30b1ed7459b9e94fb4bbe672 + FirebaseAppCheckInterop: 63119cdfc94b16c3e9421513c17f597aee2ea225 + FirebaseAuth: 9ebc3577fe0acf9092df21ac314024b70aebf21e + FirebaseAuthInterop: 826d3d772b554e3675ceaab8c665008277ca9d1c + FirebaseCore: c088995ece701a021a48a1348ea0174877de2a6a + FirebaseCoreDiagnostics: aaa87098082c4d4bdd1a9557b1186d18ca85ce8c + FirebaseCoreExtension: 2cf8c542b54ad3c2d4b746c22e8828b670dcd9b0 + FirebaseCoreInternal: a13302b0088fbf5f38b79b6ece49c2af7d3e05d6 + FirebaseFirestore: 5a72aa925528f67469b16a54a6cfc369467197e4 + FirebaseStorage: 1414d27e15fa04f6350ef6602accef0e951c8bca + FirebaseStorageInternal: 425c7dc7de44d9b7e07a9f8d6515bab0f1266b87 + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433 + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f + GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 + "gRPC-C++": 9675f953ace2b3de7c506039d77be1f2e77a8db2 + gRPC-Core: 943e491cb0d45598b0b0eb9e910c88080369290b + GTMSessionFetcher: 681175626052e03fdde7952f7e9c7a9785719506 + image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb + leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729 + Libuv-gRPC: 55e51798e14ef436ad9bc45d12d43b77b49df378 + libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c + Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d + nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 + SDWebImageWebPCoder: 3dc350894112feab5375cfba9ce0986544a66a69 + sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe + video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff + +PODFILE CHECKSUM: cb520c24ab437919c1cbc923652dabeb8956904c + +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0a923d2 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,579 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + 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 */; }; + 5CBE7A0A463E3420D0786D8B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 036C8F55DC2835B8D1C5FE17 /* Pods_Runner.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 */; }; +/* End PBXBuildFile 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 */ + 036C8F55DC2835B8D1C5FE17 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 13E4582F129D3B979B0C2A45 /* 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 = ""; }; + 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 = ""; }; + 4DD51BD2DF377E37F7F0993A /* 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 = ""; }; + 550EFB2C581560DB88B1E592 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.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 = ""; }; + AF09C1F1BFF6DA8D85B40D78 /* 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 = ""; }; + F588AC7E28AA1D9B00427DAD /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CBE7A0A463E3420D0786D8B /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1F21B24CF1D330CB9AA13442 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 036C8F55DC2835B8D1C5FE17 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 550EFB2C581560DB88B1E592 /* GoogleService-Info.plist */, + DEC419CA172B284C75A4D773 /* Pods */, + 1F21B24CF1D330CB9AA13442 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + F588AC7E28AA1D9B00427DAD /* Runner.entitlements */, + 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 = ""; + }; + DEC419CA172B284C75A4D773 /* Pods */ = { + isa = PBXGroup; + children = ( + 4DD51BD2DF377E37F7F0993A /* Pods-Runner.debug.xcconfig */, + 13E4582F129D3B979B0C2A45 /* Pods-Runner.release.xcconfig */, + AF09C1F1BFF6DA8D85B40D78 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 4948FB541658273AEC0E66F7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ECBA23F950821228653D1DC1 /* [CP] Embed Pods Frameworks */, + ); + 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 = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 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 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 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; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 4948FB541658273AEC0E66F7 /* [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; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + 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"; + }; + ECBA23F950821228653D1DC1 /* [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 */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase 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; + baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 10.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 = AF09C1F1BFF6DA8D85B40D78 /* Pods-Runner.profile.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = Q56R6X96PZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.2; + PRODUCT_BUNDLE_IDENTIFIER = com.redting.developers.redting; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB31CF90195004384FC /* Generated.xcconfig */; + 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + 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 = 10.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 = 4DD51BD2DF377E37F7F0993A /* Pods-Runner.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = Q56R6X96PZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.2; + PRODUCT_BUNDLE_IDENTIFIER = com.redting.developers.redting; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13E4582F129D3B979B0C2A45 /* Pods-Runner.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = Q56R6X96PZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.2; + PRODUCT_BUNDLE_IDENTIFIER = com.redting.developers.redting; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 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..c87d15a --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + 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/.DS_Store b/ios/Runner/.DS_Store new file mode 100644 index 0000000..463b846 Binary files /dev/null and b/ios/Runner/.DS_Store differ diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..2fcdc7a --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,14 @@ +import UIKit +import Flutter +import Firebase + +@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/.DS_Store b/ios/Runner/Assets.xcassets/.DS_Store new file mode 100644 index 0000000..067f882 Binary files /dev/null and b/ios/Runner/Assets.xcassets/.DS_Store differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..8c9981a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..f3dbe41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..cf3e0cb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..a04fe36 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..3baa18f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..905a94d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..5be21da Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..be64af5 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..3e671b2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..62c7baa Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..75f88c4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..4de32d3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..95348b8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..cbaaf61 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..5ed0c39 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..67b5d17 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..b5e8b13 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..edda185 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..546d455 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..9a6e9eb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..25f5e67 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..0379221 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..aca45f7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..b9dc222 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..afb1496 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..260ee8d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..ba30145 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..a8cb134 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..95567ed Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..684cdb8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..a6a85bf Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png differ 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..04de9d4 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,330 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} 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/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..0c8dd6f --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 309040849360-ajflptqec7341i58rg3np5755gsi6ksc.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.309040849360-ajflptqec7341i58rg3np5755gsi6ksc + ANDROID_CLIENT_ID + 309040849360-gdiqkfnqtqd1cc5r0jbjglmofhp1je3h.apps.googleusercontent.com + API_KEY + AIzaSyAC5o9UXSTCyCMeMNYblbawbOTe_6MYmx8 + GCM_SENDER_ID + 309040849360 + PLIST_VERSION + 1 + BUNDLE_ID + com.redting.developers.redting + PROJECT_ID + redting-d5504 + STORAGE_BUCKET + redting-d5504.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:309040849360:ios:9ce023fc88ca3d7abc99d5 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..640b0b8 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,71 @@ + + + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + RedTing + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleGetInfoString + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + redting + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.309040849360-ajflptqec7341i58rg3np5755gsi6ksc + + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationCategoryType + + LSRequiresIPhoneOS + + NSCameraUsageDescription + REDTING needs access to your camera to take and upload photos and videos for your REDTING profile + NSMicrophoneUsageDescription + REDTING needs access to your microphone to take and upload videos for your profile + NSPhotoLibraryUsageDescription + REDTING needs access to your photos to select photos for your REDTING profile + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + 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/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/ios/Runner/appstore.png b/ios/Runner/appstore.png new file mode 100644 index 0000000..f3dbe41 Binary files /dev/null and b/ios/Runner/appstore.png differ diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..9ee5146 --- /dev/null +++ b/ios/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:309040849360:ios:9ce023fc88ca3d7abc99d5", + "FIREBASE_PROJECT_ID": "redting-d5504", + "GCM_SENDER_ID": "309040849360" +} \ No newline at end of file diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000..75ca218 Binary files /dev/null and b/lib/.DS_Store differ diff --git a/lib/core/components/buttons/main_elevated_btn.dart b/lib/core/components/buttons/main_elevated_btn.dart new file mode 100644 index 0000000..c89135f --- /dev/null +++ b/lib/core/components/buttons/main_elevated_btn.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class MainElevatedBtn extends StatelessWidget { + final VoidCallback onClick; + final bool showLoading; + final String lbl; + final String? loadingLbl; + final bool primaryBg; + final IconData? suffixIcon; + const MainElevatedBtn( + {Key? key, + required this.onClick, + required this.showLoading, + required this.lbl, + this.loadingLbl, + this.primaryBg = false, + this.suffixIcon}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: onClick, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(primaryBg + ? appTheme.colorScheme.primary + : appTheme.colorScheme.primaryContainer)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Visibility( + visible: showLoading, + child: const CircularProgress(), + ), + if (suffixIcon != null) + Icon( + suffixIcon, + size: 24, + color: primaryBg + ? appTheme.colorScheme.onPrimary + : appTheme.colorScheme.primary, + ), + Expanded(child: _getTxtLbl()), + ], + ), + ); + } + + Widget _getTxtLbl() { + return Text( + showLoading ? (loadingLbl ?? lbl.toUpperCase()) : lbl.toUpperCase(), + style: appTextTheme.button?.copyWith( + color: primaryBg + ? appTheme.colorScheme.onPrimary + : appTheme.colorScheme.primary, + fontWeight: FontWeight.w700), + textAlign: TextAlign.center, + ); + } +} diff --git a/lib/core/components/cards/glass_card.dart b/lib/core/components/cards/glass_card.dart new file mode 100644 index 0000000..093ee7b --- /dev/null +++ b/lib/core/components/cards/glass_card.dart @@ -0,0 +1,52 @@ +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/res/dimens.dart'; + +class GlassCard extends StatelessWidget { + final Widget child; + final EdgeInsets margins; + final BoxConstraints constraints; + final EdgeInsets contentPadding; + final double borderRadius; + final Gradient? gradient; + final bool wrapInChildScrollable; + const GlassCard( + {Key? key, + required this.child, + this.constraints = const BoxConstraints( + minHeight: 300, + maxHeight: 300, + ), + this.margins = const EdgeInsets.only(bottom: paddingMd), + this.contentPadding = const EdgeInsets.all(paddingMd), + this.borderRadius = 14.0, + this.gradient, + this.wrapInChildScrollable = true}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: margins, + alignment: Alignment.center, + child: ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40), + child: Container( + constraints: constraints, + decoration: BoxDecoration( + gradient: gradient ?? twoColorOpaquePrimaryGradient, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Padding( + padding: contentPadding, + child: wrapInChildScrollable + ? SingleChildScrollView(child: child) + : child, + )), + ))); + } +} diff --git a/lib/core/components/flutter_patches/custom_stateful_builder.dart b/lib/core/components/flutter_patches/custom_stateful_builder.dart new file mode 100644 index 0000000..780c65d --- /dev/null +++ b/lib/core/components/flutter_patches/custom_stateful_builder.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class CustomStatefulBuilder extends StatefulWidget { + final StatefulWidgetBuilder builder; + + const CustomStatefulBuilder({ + required Key key, + required this.builder, + }) : super(key: key); + + @override + State createState() => _CustomStatefulBuilderState(); +} + +class _CustomStatefulBuilderState extends State { + bool isMounted = false; + @override + Widget build(BuildContext context) => widget.builder(context, (callback) { + isMounted = mounted; + if (mounted) { + setState(callback); + } + }); +} diff --git a/lib/core/components/gradients/primary_gradients.dart b/lib/core/components/gradients/primary_gradients.dart new file mode 100644 index 0000000..a22a233 --- /dev/null +++ b/lib/core/components/gradients/primary_gradients.dart @@ -0,0 +1,19 @@ +import 'package:flutter/widgets.dart'; +import 'package:redting/res/theme.dart'; + +var twoColorOpaquePrimaryGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + appTheme.colorScheme.primary.withOpacity(0.1), + appTheme.colorScheme.primary.withOpacity(0.2) + ]); + +var threeColorOpaqueGradientTB = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + appTheme.colorScheme.inversePrimary.withOpacity(0.1), + appTheme.colorScheme.inversePrimary.withOpacity(0.4), + appTheme.colorScheme.inversePrimary.withOpacity(0.6), + ]); diff --git a/lib/core/components/misc/darkish_transparent_layer.dart b/lib/core/components/misc/darkish_transparent_layer.dart new file mode 100644 index 0000000..ab6cabb --- /dev/null +++ b/lib/core/components/misc/darkish_transparent_layer.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class TransparentLayer extends StatelessWidget { + const TransparentLayer({Key? key}) : super(key: key); + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient(colors: [ + Colors.transparent, + Colors.black, + ], stops: [ + 0.7, + 1 + ], begin: Alignment.topCenter, end: Alignment.bottomCenter))); + } +} diff --git a/lib/core/components/progress/circular_progress.dart b/lib/core/components/progress/circular_progress.dart new file mode 100644 index 0000000..7a463d5 --- /dev/null +++ b/lib/core/components/progress/circular_progress.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/theme.dart'; + +class CircularProgress extends StatelessWidget { + final bool makeSmaller; + const CircularProgress({Key? key, this.makeSmaller = false}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: makeSmaller ? 16 : 24, + height: makeSmaller ? 16 : 24, + child: CircularProgressIndicator( + strokeWidth: makeSmaller ? 3 : 4, + backgroundColor: appTheme.colorScheme.inversePrimary, + color: appTheme.colorScheme.primary, + ), + ); + } +} diff --git a/lib/core/components/screens/gradient_screen_container.dart b/lib/core/components/screens/gradient_screen_container.dart new file mode 100644 index 0000000..6bead1d --- /dev/null +++ b/lib/core/components/screens/gradient_screen_container.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/res/dimens.dart'; + +class GradientScreenContainer extends StatelessWidget { + final Widget screen; + final BoxDecoration? decor; + const GradientScreenContainer({Key? key, required this.screen, this.decor}) + : super(key: key); + + @override + Widget build(BuildContext context) { + var screenHeight = MediaQuery.of(context).size.height; + var screenWidth = MediaQuery.of(context).size.width; + return Container( + decoration: + decor ?? BoxDecoration(gradient: threeColorOpaqueGradientTB), + constraints: BoxConstraints( + minWidth: screenWidth, + minHeight: screenHeight, + maxHeight: screenHeight, + maxWidth: screenWidth), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingStd), + child: screen)); + } +} diff --git a/lib/core/components/screens/scaffold_wrapper.dart b/lib/core/components/screens/scaffold_wrapper.dart new file mode 100644 index 0000000..e2396e3 --- /dev/null +++ b/lib/core/components/screens/scaffold_wrapper.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ScaffoldWrapper extends StatelessWidget { + final Widget child; + final Color? statusBarColor; + const ScaffoldWrapper({ + Key? key, + required this.child, + this.statusBarColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Brightness brightness = MediaQuery.of(context).platformBrightness; + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: brightness == Brightness.dark + ? Colors.transparent + : Colors.black26, + ), + child: child); + } +} diff --git a/lib/core/components/selectors/country_code_selector.dart b/lib/core/components/selectors/country_code_selector.dart new file mode 100644 index 0000000..0cf8760 --- /dev/null +++ b/lib/core/components/selectors/country_code_selector.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/countries.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class CountryCodeSelector extends StatefulWidget { + final String selectedCountry; + final Function(String country) onCountrySelected; + const CountryCodeSelector({ + Key? key, + required this.selectedCountry, + required this.onCountrySelected, + }) : super(key: key); + + @override + State createState() => _CountryCodeSelectorState(); +} + +class _CountryCodeSelectorState extends State { + bool _isShowingPopUp = false; + void _showCountrySelector() async { + if (_isShowingPopUp) return; + setState(() { + _isShowingPopUp = true; + }); + String? tappedCountry = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: SingleChildScrollView( + child: ListBody( + children: countryToPhoneCodeMap.keys.map((String value) { + return InkWell( + onTap: () { + Navigator.pop(context, value); + }, + child: Container( + margin: const EdgeInsets.only(bottom: paddingSm), + child: Text( + value, + style: + appTextTheme.bodyText1?.copyWith(color: Colors.black), + ), + ), + ); + }).toList(), + ), + ), + ); + }, + ); + + if (mounted) { + setState(() { + _isShowingPopUp = false; + }); + if (tappedCountry != null) { + widget.onCountrySelected(tappedCountry); + } + } + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + _showCountrySelector(); + }, + splashColor: appTheme.colorScheme.inversePrimary, + radius: 14.0, + child: Container( + height: 58, + width: 70, + margin: const EdgeInsets.only(right: paddingStd), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: const Border.fromBorderSide( + BorderSide(color: Colors.grey, width: 1))), + child: Center( + child: Text( + countryToPhoneCodeMap[widget.selectedCountry] ?? '', + style: appTextTheme.headline5, + ), + ), + ), + ); + } +} diff --git a/lib/core/components/selectors/country_selector.dart b/lib/core/components/selectors/country_selector.dart new file mode 100644 index 0000000..3cf1057 --- /dev/null +++ b/lib/core/components/selectors/country_selector.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/countries.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class CountrySelector extends StatefulWidget { + final String selectedCountry; + final Function(String country) onCountrySelected; + const CountrySelector({ + Key? key, + required this.selectedCountry, + required this.onCountrySelected, + }) : super(key: key); + + @override + State createState() => _CountrySelectorState(); +} + +class _CountrySelectorState extends State { + bool _isShowingPopUp = false; + void _showCountrySelector() async { + if (_isShowingPopUp) return; + setState(() { + _isShowingPopUp = true; + }); + String? tappedCountry = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: SingleChildScrollView( + child: ListBody( + children: countryToPhoneCodeMap.keys.map((String value) { + return InkWell( + onTap: () { + Navigator.pop(context, value); + }, + child: Container( + margin: const EdgeInsets.only(bottom: paddingSm), + child: Text( + value, + style: + appTextTheme.bodyText1?.copyWith(color: Colors.black), + ), + ), + ); + }).toList(), + ), + ), + ); + }, + ); + + if (mounted) { + setState(() { + _isShowingPopUp = false; + }); + if (tappedCountry != null) { + widget.onCountrySelected(tappedCountry); + } + } + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + _showCountrySelector(); + }, + splashColor: appTheme.colorScheme.inversePrimary, + radius: 14.0, + child: Container( + height: 58, + width: 70, + margin: const EdgeInsets.only(right: paddingStd), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: const Border.fromBorderSide( + BorderSide(color: Colors.grey, width: 1))), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingStd), + child: Text( + widget.selectedCountry.isNotEmpty + ? widget.selectedCountry + : selectFromCountryLbl, + style: appTextTheme.subtitle1, + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ); + } +} diff --git a/lib/core/components/snack/snack.dart b/lib/core/components/snack/snack.dart new file mode 100644 index 0000000..11cf763 --- /dev/null +++ b/lib/core/components/snack/snack.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class Snack { + final String content; + final Function? action; + final String actionLbl; + final bool isError; + + const Snack( + {Key? key, + required this.content, + this.action, + this.actionLbl = '', + this.isError = false}); + + SnackBar create(context) { + return SnackBar( + elevation: 12.0, + duration: const Duration(seconds: 5), + content: Text( + content, + style: appTextTheme.bodyText2?.copyWith( + fontWeight: FontWeight.bold, + color: isError + ? appTheme.colorScheme.error + : appTheme.colorScheme.primary), + ), + action: action != null + ? SnackBarAction( + label: actionLbl.toUpperCase(), + onPressed: () => action!(), + textColor: appTheme.colorScheme.primary, + ) + : null, + backgroundColor: + isError ? appTheme.colorScheme.inversePrimary : Colors.white); + } +} diff --git a/lib/core/components/text/app_name_std_style.dart b/lib/core/components/text/app_name_std_style.dart new file mode 100644 index 0000000..51e9a9a --- /dev/null +++ b/lib/core/components/text/app_name_std_style.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class StdAppName extends StatelessWidget { + final bool large; + final Color? firstPartTxtColor, secondPartTxtColor; + const StdAppName( + {Key? key, + this.large = false, + this.firstPartTxtColor, + this.secondPartTxtColor}) + : super(key: key); + + @override + Widget build(BuildContext context) { + TextStyle? txtTheme = + large ? appTextTheme.headline3 : appTextTheme.headline4; + return RichText( + text: TextSpan( + children: [ + TextSpan( + text: appNameFirstWord, + style: txtTheme?.copyWith( + color: firstPartTxtColor ?? + appTheme.colorScheme.primary.withOpacity(0.5), + )), + TextSpan( + text: appNameLastWord, + style: txtTheme?.copyWith( + color: secondPartTxtColor ?? appTheme.colorScheme.primary)), + ], + )); + } +} diff --git a/lib/core/components/text_input/outlined_txtfield.dart b/lib/core/components/text_input/outlined_txtfield.dart new file mode 100644 index 0000000..7f3bc0d --- /dev/null +++ b/lib/core/components/text_input/outlined_txtfield.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class OutlinedTxtField extends StatelessWidget { + final TextEditingController controller; + final TextInputType keyboardType; + final TextInputAction? txtInputAction; + final String? prefixText, hintTxt; + const OutlinedTxtField( + {Key? key, + required this.controller, + required this.keyboardType, + this.txtInputAction, + this.prefixText, + this.hintTxt}) + : super(key: key); + + @override + Widget build(BuildContext context) { + TextStyle? txtStyle = keyboardType == TextInputType.phone + ? appTextTheme.headline6?.copyWith(letterSpacing: 2) + : appTextTheme.bodyText1; + return TextField( + textInputAction: txtInputAction ?? TextInputAction.next, + controller: controller, + keyboardType: keyboardType, + enabled: keyboardType != TextInputType.none, + showCursor: keyboardType != TextInputType.none, + style: txtStyle, + decoration: InputDecoration( + isDense: true, + contentPadding: + const EdgeInsets.symmetric(vertical: 14, horizontal: 12), + prefixStyle: txtStyle, + prefixText: prefixText, + hintText: hintTxt, + hintStyle: appTextTheme.caption?.copyWith(color: Colors.black), + focusedBorder: _getBorder(isFocused: true), + disabledBorder: _getBorder(isDisabled: true), + errorBorder: _getBorder(isError: true), + border: _getBorder()), + ); + } + + _getBorder({ + bool isFocused = false, + bool isError = false, + bool isDisabled = false, + }) { + ColorScheme colorScheme = appTheme.colorScheme; + Color borderColor = isFocused + ? colorScheme.inversePrimary + : isDisabled + ? Colors.grey + : isError + ? colorScheme.error + : Colors.black45; + return OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: + BorderSide(color: borderColor, width: isFocused ? 2.0 : 1.0)); + } +} diff --git a/lib/core/components/text_input/six_code_input.dart b/lib/core/components/text_input/six_code_input.dart new file mode 100644 index 0000000..dee1f9c --- /dev/null +++ b/lib/core/components/text_input/six_code_input.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:redting/res/fonts.dart'; + +class SixDigitCodeInput extends StatefulWidget { + final Function(String code) onUpdateCode; + const SixDigitCodeInput({Key? key, required this.onUpdateCode}) + : super(key: key); + + @override + State createState() => _SixDigitCodeInputState(); +} + +class _SixDigitCodeInputState extends State { + List code = ["", "", "", "", "", ""]; + final List _controllers = []; + + @override + void initState() { + _initControllers(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _getInputs(), + ); + } + + List _getInputs() { + List widgets = []; + for (int i = 0; i < 6; i++) { + widgets.add(ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 48), + child: TextField( + style: appTextTheme.headline4, + textAlign: TextAlign.center, + controller: _controllers[i], + inputFormatters: [ + LengthLimitingTextInputFormatter(1), + FilteringTextInputFormatter.digitsOnly + ], + onChanged: (digit) { + if (digit.length == 1 && i != 5) { + FocusScope.of(context).nextFocus(); + } + + if (digit.isEmpty && i != 0) { + FocusScope.of(context).previousFocus(); + } + + _updateCode(digit: digit, pos: i); + }, + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + isDense: true, + border: + OutlineInputBorder(borderRadius: BorderRadius.circular(8))), + ))); + } + return widgets; + } + + void _initControllers() { + int i = 0; + while (i < 6) { + _controllers.add(TextEditingController()..text = code[i]); + i++; + } + } + + @override + void dispose() { + for (var element in _controllers) { + element.dispose(); + } + super.dispose(); + } + + void _updateCode({required int pos, required String digit}) { + setState(() { + code[pos] = digit.isNotEmpty ? digit[0] : ""; + widget.onUpdateCode(code.join()); + }); + } +} diff --git a/lib/core/components/text_input/unstyled_input_txt.dart b/lib/core/components/text_input/unstyled_input_txt.dart new file mode 100644 index 0000000..9b61ade --- /dev/null +++ b/lib/core/components/text_input/unstyled_input_txt.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/fonts.dart'; + +class UnStyledTxtInput extends StatelessWidget { + final TextEditingController controller; + final String hint; + final BoxConstraints? constraints; + final TextInputType? keyboardType; + final TextAlign? textAlign; + final String? label; + final int? maxCharacters; + final Function(String)? onTxtChanged; + final Function()? onTap; + final Color? txtColor; + const UnStyledTxtInput( + {Key? key, + required this.controller, + required this.hint, + this.constraints, + this.keyboardType, + this.textAlign, + this.label, + this.maxCharacters, + this.onTxtChanged, + this.onTap, + this.txtColor}) + : super(key: key); + + @override + Widget build(BuildContext context) { + bool isLongTxt = keyboardType == TextInputType.multiline; + return GestureDetector( + onTap: onTap, + child: TextField( + controller: controller, + keyboardType: keyboardType ?? TextInputType.text, + minLines: isLongTxt ? 3 : 1, + maxLines: isLongTxt ? 5 : 1, + maxLength: maxCharacters, + showCursor: keyboardType != TextInputType.none, + enabled: keyboardType != TextInputType.none, + decoration: InputDecoration( + labelStyle: appTextTheme.headline6 + ?.copyWith(color: txtColor ?? Colors.black), + helperStyle: appTextTheme.caption + ?.copyWith(fontSize: 12, fontWeight: FontWeight.w200), + labelText: label, + floatingLabelBehavior: label != null + ? FloatingLabelBehavior.always + : FloatingLabelBehavior.auto, + isDense: true, + hintText: hint, + constraints: constraints, + hintStyle: appTextTheme.bodyText2 + ?.copyWith(color: txtColor ?? Colors.black), + border: + isLongTxt ? const UnderlineInputBorder() : InputBorder.none), + textAlign: textAlign ?? TextAlign.start, + onChanged: onTxtChanged, + style: keyboardType == TextInputType.number + ? appTextTheme.headline6?.copyWith(color: txtColor ?? Colors.black) + : appTextTheme.bodyText1?.copyWith(color: txtColor ?? Colors.black), + ), + ); + } +} diff --git a/lib/core/data/hive_names.dart b/lib/core/data/hive_names.dart new file mode 100644 index 0000000..0803122 --- /dev/null +++ b/lib/core/data/hive_names.dart @@ -0,0 +1,13 @@ +/// boxes +const String authUserBox = "auth_user_box"; +const String userProfileBox = "user_profile_box"; +const String iceBreakersBox = "ice_breakers_box"; +const String likedUsersBox = "liked_users_box"; +const String canMakeBlindDatesBox = "can_make_blind_dates_box"; + +//keys +const String authUserKey = "auth_user_key"; +const String userProfileKey = "user_profile_key"; +const String iceBreakersKey = "ice_breakers_key"; +const String likedUsersBoxKey = "liked_users_key"; +const String canMakeBlindDatesBoxKey = "can_make_blind_dates_box_key"; diff --git a/lib/core/data/hive_type_ids.dart b/lib/core/data/hive_type_ids.dart new file mode 100644 index 0000000..7b19521 --- /dev/null +++ b/lib/core/data/hive_type_ids.dart @@ -0,0 +1,7 @@ +const authUserTypeId = 1; +const userGenderTypeId = 2; +const userProfileTypeId = 3; +const userVerificationVideoTypeId = 4; +const sexualOrientationTypeId = 5; +const iceBreakerMessagesTypeId = 6; +const messageTypeId = 7; diff --git a/lib/core/data/local_storage.dart b/lib/core/data/local_storage.dart new file mode 100644 index 0000000..e713a00 --- /dev/null +++ b/lib/core/data/local_storage.dart @@ -0,0 +1,74 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_names.dart'; +import 'package:redting/features/auth/data/entities/auth_user_entity.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/matching/data/entities/ice_breaker_messages_entity.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/features/profile/data/entities/sexual_orientation_entity.dart'; +import 'package:redting/features/profile/data/entities/user_gender_entity.dart'; +import 'package:redting/features/profile/data/entities/user_profile_entity.dart'; +import 'package:redting/features/profile/data/entities/user_verification_video_entity.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +class LocalStorage { + static Future init() async { + await Hive.initFlutter(); + _registerAllAdapters(); + await _openAllBoxes(); + } + + static _registerAllAdapters() async { + _registerAuthUserAdapter(); + _registerUserProfileAdapters(); + _registerMatchingDataAdapters(); + //todo other adapters + } + + static Future _openAllBoxes() async { + await _openBoxAuthUser(); + await _openBoxUserProfile(); + await _openMatchingDataBox(); + await _openCanMakeBlindDatesBox(); + //todo other boxes + } + + static Future dispose() async { + await Hive.close(); //release all open boxes + } + + /// auth user + static Future _openBoxAuthUser() { + return Hive.openBox(authUserBox); + } + + static void _registerAuthUserAdapter() { + return Hive.registerAdapter(AuthUserEntityAdapter(), override: true); + } + + /// user profile + static Future _openBoxUserProfile() { + return Hive.openBox(userProfileBox); + } + + static void _registerUserProfileAdapters() { + Hive.registerAdapter(SexualOrientationEntityAdapter(), override: true); + Hive.registerAdapter(UserGenderEntityAdapter(), override: true); + Hive.registerAdapter(UserVerificationVideoEntityAdapter(), override: true); + Hive.registerAdapter(UserProfileEntityAdapter(), override: true); + } + + /// Matching Feature Data + static void _registerMatchingDataAdapters() { + Hive.registerAdapter(IceBreakerMessagesEntityAdapter(), override: true); + } + + static Future _openMatchingDataBox() async { + await Hive.openBox?>(likedUsersBox); + await Hive.openBox(iceBreakersBox); + } + + /// blind data setup status + static Future _openCanMakeBlindDatesBox() async { + await Hive.openBox(canMakeBlindDatesBox); + } +} diff --git a/lib/core/di/core_dependencies.dart b/lib/core/di/core_dependencies.dart new file mode 100644 index 0000000..e5e1d42 --- /dev/null +++ b/lib/core/di/core_dependencies.dart @@ -0,0 +1,14 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; +import 'package:redting/features/profile/data/utils/compressors/video_compressor.dart'; + +GetIt init() { + final GetIt diInstance = GetIt.instance; +//compressor + diInstance + .registerLazySingleton(() => ImageCompressorImpl()); + diInstance + .registerLazySingleton(() => VideoCompressorImpl()); + + return diInstance; +} diff --git a/lib/core/platform/network_info.dart b/lib/core/platform/network_info.dart new file mode 100644 index 0000000..68a2ba8 --- /dev/null +++ b/lib/core/platform/network_info.dart @@ -0,0 +1,3 @@ +abstract class NetworkInfo { + Future get isConnected; +} diff --git a/lib/core/utils/consts.dart b/lib/core/utils/consts.dart new file mode 100644 index 0000000..63307c2 --- /dev/null +++ b/lib/core/utils/consts.dart @@ -0,0 +1,7 @@ +//dating profile +const int datingProfilePhotoSizeWidth = 300; +const int datingProfilePhotoSizeHeight = 848; +const int maxDatingProfilePhotos = 4; +const int minDatingProfilePhotosRequired = 3; +const int queryPageResultsSize = 100; +const int maxStarsForRating = 5; diff --git a/lib/core/utils/flutter_fire_date_time_utils.dart b/lib/core/utils/flutter_fire_date_time_utils.dart new file mode 100644 index 0000000..79c3ebb --- /dev/null +++ b/lib/core/utils/flutter_fire_date_time_utils.dart @@ -0,0 +1,14 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:json_annotation/json_annotation.dart'; + +class TimestampConverter implements JsonConverter { + const TimestampConverter(); + + @override + DateTime fromJson(Timestamp timestamp) { + return timestamp.toDate(); + } + + @override + Timestamp toJson(DateTime date) => Timestamp.fromDate(date); +} diff --git a/lib/core/utils/service_result.dart b/lib/core/utils/service_result.dart new file mode 100644 index 0000000..b025342 --- /dev/null +++ b/lib/core/utils/service_result.dart @@ -0,0 +1,16 @@ +class ServiceResult { + bool errorOccurred; + String? errorMessage; + dynamic data; + ServiceResult({this.errorOccurred = false, this.data, this.errorMessage}); +} + +class OperationRealTimeResult { + dynamic data; + RealTimeEventType realTimeEventType; + + OperationRealTimeResult( + {required this.data, required this.realTimeEventType}); +} + +enum RealTimeEventType { added, modified, deleted } diff --git a/lib/core/utils/txt_helpers.dart b/lib/core/utils/txt_helpers.dart new file mode 100644 index 0000000..a34645c --- /dev/null +++ b/lib/core/utils/txt_helpers.dart @@ -0,0 +1,35 @@ +import 'dart:math'; + +String clipText({required String txt, int maxChars = 48}) { + if (txt.length <= maxChars) return txt; + return "${txt.substring(0, maxChars)}..."; +} + +String clipWords({required String txt, int maxChars = 48}) { + if (txt.length <= maxChars) return txt; + var allWords = txt.split(" "); + + String clipped = allWords[0]; + + if (clipped.length > maxChars) { + return clipText(txt: txt, maxChars: maxChars); // a single long word + } + + int i = 1; + while (i < allWords.length) { + String candidate = "$clipped ${allWords[i]}"; + if (candidate.length > maxChars) { + break; + } + clipped = candidate; + i++; + } + return "$clipped..."; +} + +String randomWordInList(List aList) { + final random = Random(); + String iceBreaker = + aList.isNotEmpty ? aList[random.nextInt(aList.length)] : ''; + return iceBreaker; +} diff --git a/lib/features/.DS_Store b/lib/features/.DS_Store new file mode 100644 index 0000000..cd54794 Binary files /dev/null and b/lib/features/.DS_Store differ diff --git a/lib/features/auth/data/.DS_Store b/lib/features/auth/data/.DS_Store new file mode 100644 index 0000000..071ee93 Binary files /dev/null and b/lib/features/auth/data/.DS_Store differ diff --git a/lib/features/auth/data/data_sources/.DS_Store b/lib/features/auth/data/data_sources/.DS_Store new file mode 100644 index 0000000..d2b7dbb Binary files /dev/null and b/lib/features/auth/data/data_sources/.DS_Store differ diff --git a/lib/features/auth/data/data_sources/local/auth_user_hive.dart b/lib/features/auth/data/data_sources/local/auth_user_hive.dart new file mode 100644 index 0000000..ee707f9 --- /dev/null +++ b/lib/features/auth/data/data_sources/local/auth_user_hive.dart @@ -0,0 +1,23 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_names.dart'; +import 'package:redting/features/auth/data/data_sources/local/local_auth.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; + +class AuthUserHive implements LocalAuthSource { + final Box _usersHiveBox = Hive.box(authUserBox); + + @override + Future cacheAuthUser({required AuthUser authUser}) async { + await _usersHiveBox.put(authUserKey, authUser); + } + + @override + AuthUser? getAuthUser() { + return _usersHiveBox.get(authUserKey); + } + + @override + Future signUserOut() async { + await _usersHiveBox.delete(authUserKey); + } +} diff --git a/lib/features/auth/data/data_sources/local/local_auth.dart b/lib/features/auth/data/data_sources/local/local_auth.dart new file mode 100644 index 0000000..b0e3fc7 --- /dev/null +++ b/lib/features/auth/data/data_sources/local/local_auth.dart @@ -0,0 +1,7 @@ +import 'package:redting/features/auth/domain/models/auth_user.dart'; + +abstract class LocalAuthSource { + AuthUser? getAuthUser(); + Future signUserOut(); + Future cacheAuthUser({required AuthUser authUser}); +} diff --git a/lib/features/auth/data/data_sources/remote/fire_auth.dart b/lib/features/auth/data/data_sources/remote/fire_auth.dart new file mode 100644 index 0000000..fa53ee8 --- /dev/null +++ b/lib/features/auth/data/data_sources/remote/fire_auth.dart @@ -0,0 +1,98 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/data/data_sources/remote/remote_auth.dart'; +import 'package:redting/features/auth/data/entities/auth_user_entity.dart'; +import 'package:redting/features/auth/data/utils/fire_verification_result.dart'; +import 'package:redting/res/strings.dart'; + +class FireAuth implements RemoteAuthSource { + final FirebaseAuth _auth = FirebaseAuth.instance; + final FirebaseFirestore _db = FirebaseFirestore.instance; + + @override + ServiceResult getAuthUser() { + try { + User? user = _auth.currentUser; + if (user == null) return ServiceResult(); + + return ServiceResult( + data: AuthUserEntity( + userId: user.uid, + phoneNumber: user.phoneNumber!, + )); + } catch (e) { + /// probably phone number is unset + return ServiceResult(errorOccurred: true, errorMessage: "$e"); + } + } + + @override + void sendVerificationCodeToPhone(String phone, String countryCode, + int? resendToken, Function(FireAuthSendCodeResult result) callback) { + String formattedPhone = (countryCode + phone).replaceAll(" ", ""); + + FirebaseAuth.instance.verifyPhoneNumber( + phoneNumber: formattedPhone, + timeout: const Duration(seconds: 60), + forceResendingToken: resendToken, + verificationCompleted: (PhoneAuthCredential credential) { + //android only + callback(FireAuthSendCodeResult( + isAutoVerified: true, credential: credential)); + }, + verificationFailed: (FirebaseAuthException e) { + if (e.code == 'invalid-phone-number') { + callback(FireAuthSendCodeResult( + errorOccurred: true, errMsg: phoneNumberErr)); + } else { + callback(FireAuthSendCodeResult( + errMsg: unknownCodeSendingErr, errorOccurred: true)); + } + }, + codeSent: (String verificationId, int? resendToken) { + callback(FireAuthSendCodeResult( + isCodeSent: true, + verificationId: verificationId, + resendToken: resendToken)); + }, + codeAutoRetrievalTimeout: (String verificationId) {}, + ); + } + + @override + Future signInWithCredentials(dynamic credential) async { + try { + UserCredential userCredentials = + await _auth.signInWithCredential(credential as PhoneAuthCredential); + + return ServiceResult(data: userCredentials); + } on FirebaseAuthException catch (e) { + if (e.code == "invalid-verification-code") { + return ServiceResult( + errorOccurred: true, errorMessage: invalidVerificationCode); + } + + return ServiceResult( + errorOccurred: true, errorMessage: failedToVerifyUnknown); + } + } + + @override + Future signInWithVerificationCode( + String verificationId, String smsCode) async { + // Create a PhoneAuthCredential with the code + if (smsCode.trim().isEmpty) { + return ServiceResult( + errorOccurred: true, errorMessage: invalidVerificationCode); + } + PhoneAuthCredential credential = PhoneAuthProvider.credential( + verificationId: verificationId, smsCode: smsCode); + return await signInWithCredentials(credential); + } + + @override + Future signUserOut() async { + await _auth.signOut(); + } +} diff --git a/lib/features/auth/data/data_sources/remote/remote_auth.dart b/lib/features/auth/data/data_sources/remote/remote_auth.dart new file mode 100644 index 0000000..6f690ce --- /dev/null +++ b/lib/features/auth/data/data_sources/remote/remote_auth.dart @@ -0,0 +1,12 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; + +abstract class RemoteAuthSource { + ServiceResult getAuthUser(); + Future signUserOut(); + void sendVerificationCodeToPhone(String phone, String countryCode, + int? resendToken, Function(PhoneVerificationResult result) callback); + Future signInWithCredentials(dynamic credential); + Future signInWithVerificationCode( + String verificationId, String smsCode); +} diff --git a/lib/features/auth/data/entities/auth_user_entity.dart b/lib/features/auth/data/entities/auth_user_entity.dart new file mode 100644 index 0000000..1f2c4e5 --- /dev/null +++ b/lib/features/auth/data/entities/auth_user_entity.dart @@ -0,0 +1,32 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; + +part 'auth_user_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +@HiveType(typeId: authUserTypeId) +class AuthUserEntity implements AuthUser { + @HiveField(0) + @override + String userId; + + @HiveField(1) + @override + String phoneNumber; + + AuthUserEntity({ + required this.userId, + required this.phoneNumber, + }); + + factory AuthUserEntity.fromJson(Map json) => + _$AuthUserEntityFromJson(json); + Map toJson() => _$AuthUserEntityToJson(this); + + @override + bool isSameAs(AuthUser user) { + return user.userId == userId; + } +} diff --git a/lib/features/auth/data/entities/auth_user_entity.g.dart b/lib/features/auth/data/entities/auth_user_entity.g.dart new file mode 100644 index 0000000..f2ac94c --- /dev/null +++ b/lib/features/auth/data/entities/auth_user_entity.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_user_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class AuthUserEntityAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + AuthUserEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return AuthUserEntity( + userId: fields[0] as String, + phoneNumber: fields[1] as String, + ); + } + + @override + void write(BinaryWriter writer, AuthUserEntity obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj.userId) + ..writeByte(1) + ..write(obj.phoneNumber); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AuthUserEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AuthUserEntity _$AuthUserEntityFromJson(Map json) => AuthUserEntity( + userId: json['userId'] as String, + phoneNumber: json['phoneNumber'] as String, + ); + +Map _$AuthUserEntityToJson(AuthUserEntity instance) => + { + 'userId': instance.userId, + 'phoneNumber': instance.phoneNumber, + }; diff --git a/lib/features/auth/data/repositories/auth_repository_impl.dart b/lib/features/auth/data/repositories/auth_repository_impl.dart new file mode 100644 index 0000000..760f5f6 --- /dev/null +++ b/lib/features/auth/data/repositories/auth_repository_impl.dart @@ -0,0 +1,81 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/data/data_sources/local/local_auth.dart'; +import 'package:redting/features/auth/data/data_sources/remote/remote_auth.dart'; +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; +import 'package:redting/res/strings.dart'; + +class AuthRepositoryImpl implements AuthRepository { + final RemoteAuthSource remoteAuth; + final LocalAuthSource localAuth; + + AuthRepositoryImpl({ + required this.remoteAuth, + required this.localAuth, + }); + + @override + Future getCachedAuthUser() async { + try { + AuthUser? authUser = localAuth.getAuthUser(); + return ServiceResult(data: authUser); + } catch (e) { + return ServiceResult(errorOccurred: true, errorMessage: "$e"); + } + } + + @override + void sendVerificationCode(String phone, String countryCode, int? resendToken, + Function(PhoneVerificationResult result) callback) { + remoteAuth.sendVerificationCodeToPhone( + phone, countryCode, resendToken, callback); + } + + @override + Future signInUser( + String? verificationId, String? smsCode, dynamic credential) async { + ServiceResult result; + + //2 ways to sign + if (verificationId != null && smsCode != null) { + result = + await remoteAuth.signInWithVerificationCode(verificationId, smsCode); + } else { + result = await remoteAuth.signInWithCredentials(credential); + } + + if (result.errorOccurred) { + return result; + } + + //try and cache the user + AuthUser? authUser = await cacheAuthUser(); + if (authUser == null) { + await signOut(); //just to be certain + return ServiceResult( + errorOccurred: true, errorMessage: failedToCacheAuthUser); + } + return ServiceResult(data: authUser); + } + + @override + Future signOut() async { + await localAuth.signUserOut(); + await remoteAuth.signUserOut(); + } + + Future cacheAuthUser() async { + try { + ServiceResult result = remoteAuth.getAuthUser(); + if (result.data is AuthUser) { + AuthUser user = result.data as AuthUser; + await localAuth.cacheAuthUser(authUser: user); + return user; + } + return null; + } catch (e) { + return null; + } + } +} diff --git a/lib/features/auth/data/utils/fire_verification_result.dart b/lib/features/auth/data/utils/fire_verification_result.dart new file mode 100644 index 0000000..2eaf94f --- /dev/null +++ b/lib/features/auth/data/utils/fire_verification_result.dart @@ -0,0 +1,33 @@ +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; + +class FireAuthSendCodeResult implements PhoneVerificationResult { + @override + dynamic credential; + + @override + String? errMsg; + + @override + bool errorOccurred; + + @override + bool isAutoVerified; + + @override + bool isCodeSent; + + @override + int? resendToken; + + @override + String? verificationId; + + FireAuthSendCodeResult( + {this.credential, + this.errMsg, + this.errorOccurred = false, + this.isAutoVerified = false, + this.isCodeSent = false, + this.resendToken, + this.verificationId}); +} diff --git a/lib/features/auth/data/utils/phone_verification_result.dart b/lib/features/auth/data/utils/phone_verification_result.dart new file mode 100644 index 0000000..68ad0be --- /dev/null +++ b/lib/features/auth/data/utils/phone_verification_result.dart @@ -0,0 +1,18 @@ +abstract class PhoneVerificationResult { + dynamic credential; + String? verificationId; + int? resendToken; + bool isCodeSent; + bool isAutoVerified; + bool errorOccurred; + String? errMsg; + + PhoneVerificationResult( + {this.errorOccurred = false, + this.errMsg, + this.resendToken, + this.isCodeSent = false, + this.verificationId, + this.isAutoVerified = false, + this.credential}); +} diff --git a/lib/features/auth/di/auth_di.dart b/lib/features/auth/di/auth_di.dart new file mode 100644 index 0000000..a6369a5 --- /dev/null +++ b/lib/features/auth/di/auth_di.dart @@ -0,0 +1,51 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/auth/data/data_sources/local/auth_user_hive.dart'; +import 'package:redting/features/auth/data/data_sources/local/local_auth.dart'; +import 'package:redting/features/auth/data/data_sources/remote/fire_auth.dart'; +import 'package:redting/features/auth/data/data_sources/remote/remote_auth.dart'; +import 'package:redting/features/auth/data/repositories/auth_repository_impl.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; +import 'package:redting/features/auth/domain/use_cases/auth_use_cases.dart'; +import 'package:redting/features/auth/domain/use_cases/get_auth_user.dart'; +import 'package:redting/features/auth/domain/use_cases/send_verification_code.dart'; +import 'package:redting/features/auth/domain/use_cases/sign_user_in.dart'; +import 'package:redting/features/auth/presentation/state/auth_user_bloc.dart'; + +/// FACTORY - instantiated every time we request +/// SINGLETON - only a single instance is created + +GetIt init() { + final GetIt diInstance = GetIt.instance; + //auth bloc + diInstance.registerFactory(() => AuthUserBloc(authUseCases: diInstance())); + + //auth useCases + diInstance.registerLazySingleton(() => AuthUseCases( + getAuthenticatedUser: diInstance(), + sendVerificationCodeUseCase: diInstance(), + signUserInUseCase: diInstance())); + + //get auth use case + diInstance.registerLazySingleton( + () => GetAuthenticatedUserCase(repository: diInstance())); + + //get sendVerificationUseCase + diInstance.registerLazySingleton( + () => SendVerificationCodeUseCase(repository: diInstance())); + + //get SignUserInUseCase + diInstance + .registerLazySingleton(() => SignUserInUseCase(repository: diInstance())); + + //auth repository + diInstance.registerLazySingleton(() => + AuthRepositoryImpl(remoteAuth: diInstance(), localAuth: diInstance())); + + //remote data source + diInstance.registerLazySingleton(() => FireAuth()); + + //local data source + diInstance.registerLazySingleton(() => AuthUserHive()); + + return diInstance; +} diff --git a/lib/features/auth/domain/models/auth_user.dart b/lib/features/auth/domain/models/auth_user.dart new file mode 100644 index 0000000..86c409f --- /dev/null +++ b/lib/features/auth/domain/models/auth_user.dart @@ -0,0 +1,10 @@ +abstract class AuthUser { + String userId; + String phoneNumber; + AuthUser({ + required this.userId, + required this.phoneNumber, + }); + + bool isSameAs(AuthUser user); +} diff --git a/lib/features/auth/domain/repositories/auth_repository.dart b/lib/features/auth/domain/repositories/auth_repository.dart new file mode 100644 index 0000000..200fe15 --- /dev/null +++ b/lib/features/auth/domain/repositories/auth_repository.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; + +abstract class AuthRepository { + Future getCachedAuthUser(); + Future signOut(); + void sendVerificationCode(String phone, String countryCode, int? resendToken, + Function(PhoneVerificationResult result) callback); + Future signInUser( + String? verificationId, String? smsCode, dynamic credential); +} diff --git a/lib/features/auth/domain/use_cases/auth_use_cases.dart b/lib/features/auth/domain/use_cases/auth_use_cases.dart new file mode 100644 index 0000000..4d21d7c --- /dev/null +++ b/lib/features/auth/domain/use_cases/auth_use_cases.dart @@ -0,0 +1,15 @@ +import 'package:redting/features/auth/domain/use_cases/get_auth_user.dart'; +import 'package:redting/features/auth/domain/use_cases/send_verification_code.dart'; +import 'package:redting/features/auth/domain/use_cases/sign_user_in.dart'; + +class AuthUseCases { + final GetAuthenticatedUserCase getAuthenticatedUser; + final SendVerificationCodeUseCase sendVerificationCodeUseCase; + final SignUserInUseCase signUserInUseCase; + AuthUseCases( + {required this.sendVerificationCodeUseCase, + required this.getAuthenticatedUser, + required this.signUserInUseCase}); + + /// TODO USE CASES [UPDATE - phone number & registerCountry ] +} diff --git a/lib/features/auth/domain/use_cases/get_auth_user.dart b/lib/features/auth/domain/use_cases/get_auth_user.dart new file mode 100644 index 0000000..08252a1 --- /dev/null +++ b/lib/features/auth/domain/use_cases/get_auth_user.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; + +class GetAuthenticatedUserCase { + final AuthRepository repository; + GetAuthenticatedUserCase({required this.repository}); + + Future execute() async { + return await repository.getCachedAuthUser(); + } +} diff --git a/lib/features/auth/domain/use_cases/send_verification_code.dart b/lib/features/auth/domain/use_cases/send_verification_code.dart new file mode 100644 index 0000000..312844c --- /dev/null +++ b/lib/features/auth/domain/use_cases/send_verification_code.dart @@ -0,0 +1,15 @@ +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; + +class SendVerificationCodeUseCase { + final AuthRepository repository; + SendVerificationCodeUseCase({required this.repository}); + + execute( + {required phone, + required code, + int? resendToken, + required Function(PhoneVerificationResult result) callback}) { + return repository.sendVerificationCode(phone, code, resendToken, callback); + } +} diff --git a/lib/features/auth/domain/use_cases/sign_user_in.dart b/lib/features/auth/domain/use_cases/sign_user_in.dart new file mode 100644 index 0000000..fbd77ec --- /dev/null +++ b/lib/features/auth/domain/use_cases/sign_user_in.dart @@ -0,0 +1,12 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; + +class SignUserInUseCase { + final AuthRepository repository; + SignUserInUseCase({required this.repository}); + + Future execute( + {String? verificationId, String? smsCode, dynamic credential}) async { + return await repository.signInUser(verificationId, smsCode, credential); + } +} diff --git a/lib/features/auth/presentation/pages/login_screen.dart b/lib/features/auth/presentation/pages/login_screen.dart new file mode 100644 index 0000000..a097cdb --- /dev/null +++ b/lib/features/auth/presentation/pages/login_screen.dart @@ -0,0 +1,380 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/core/components/selectors/country_code_selector.dart'; +import 'package:redting/core/components/text_input/outlined_txtfield.dart'; +import 'package:redting/core/components/text_input/six_code_input.dart'; +import 'package:redting/features/auth/presentation/state/auth_user_bloc.dart'; +import 'package:redting/res/countries.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({Key? key}) : super(key: key); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + late TextEditingController _phoneController; + String _selectedCountry = countryToPhoneCodeMap.keys.first; + String _smsCode = ""; + LoginSteps _step = LoginSteps.getPhone; + int? _resendToken; + String _verificationId = ""; + String? _verificationErr; + String? _signInErr; + bool _isLoading = false; + AuthUserBloc? _eventDispatcher; + + @override + void initState() { + _phoneController = TextEditingController(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var screenHeight = MediaQuery.of(context).size.height; + var screenWidth = MediaQuery.of(context).size.width; + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + return ScaffoldWrapper( + child: WillPopScope( + onWillPop: () async { + return _onBackPressed(blocContext); + }, + child: Scaffold( + extendBodyBehindAppBar: true, + body: SingleChildScrollView( + child: Container( + constraints: BoxConstraints( + minWidth: screenWidth, minHeight: screenHeight), + decoration: + BoxDecoration(gradient: threeColorOpaqueGradientTB), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingLg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + loginTitle, + style: appTextTheme.headline2, + ), + const SizedBox( + height: paddingStd, + ), + if (_step == LoginSteps.getPhone) + ..._getPhoneStepWidgets(), + if (_step == LoginSteps.verifyPhone) + ..._getVerificationStepWidgets(blocContext), + const SizedBox( + height: paddingSm, + ), + _getContinueBtn(blocContext) + ]), + ), + ), + )), + )); + }), + )); + } + + /// STEP 1 GET PHONE + List _getPhoneStepWidgets() { + return [ + Text( + yourPhoneNumberLbl, + style: appTextTheme.subtitle2, + ), + const SizedBox( + height: paddingSm, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CountryCodeSelector( + selectedCountry: _selectedCountry, + onCountrySelected: (String country) { + setState(() { + _selectedCountry = country; + }); + }), + Expanded( + child: OutlinedTxtField( + txtInputAction: TextInputAction.done, + controller: _phoneController, + keyboardType: TextInputType.phone, + ), + ), + ], + ), + Visibility( + visible: _verificationErr != null, + child: Container( + margin: const EdgeInsets.only(top: paddingSm), + child: Text( + _verificationErr ?? '', + textAlign: TextAlign.justify, + style: appTextTheme.caption + ?.copyWith(color: appTheme.colorScheme.error), + ), + ), + ), + const SizedBox( + height: paddingSm, + ), + Text( + phoneVerificationHint, + textAlign: TextAlign.justify, + style: appTextTheme.caption?.copyWith(color: Colors.black54), + ), + ]; + } + + /// STEP 2 VERIFY PHONE + List _getVerificationStepWidgets(BuildContext blocContext) { + return [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + "$enterCodeSentTo ${countryToPhoneCodeMap[_selectedCountry]}${_phoneController.text}", + style: appTextTheme.button, + ), + ), + SizedBox( + width: 100, + child: InkWell( + splashColor: appTheme.colorScheme.primary, + radius: 14, + onTap: () { + _onSendOrResendCodeClicked(blocContext); + }, + child: Text( + resendTxt, + style: appTextTheme.button?.copyWith( + color: appTheme.colorScheme.secondary, + fontWeight: FontWeight.w800, + ), + textAlign: TextAlign.end, + ), + ), + ) + ], + ), + const SizedBox( + height: paddingSm, + ), + SixDigitCodeInput( + onUpdateCode: (String code) { + setState(() { + _smsCode = code; + }); + }, + ), + Visibility( + visible: _signInErr != null, + child: Container( + margin: const EdgeInsets.only(top: paddingSm), + child: Text( + _signInErr ?? '', + style: appTextTheme.bodyText2 + ?.copyWith(color: appTheme.colorScheme.error), + ), + ), + ), + const SizedBox( + height: paddingSm, + ), + SizedBox( + height: 40, + child: Align( + alignment: Alignment.centerLeft, + child: InkWell( + splashColor: appTheme.colorScheme.primary, + radius: 14, + onTap: () { + _goToGetPhone(blocContext); + }, + child: Text( + changePhoneTxt, + style: appTextTheme.button?.copyWith( + color: appTheme.colorScheme.secondary, + fontWeight: FontWeight.w800, + ), + textAlign: TextAlign.end, + ), + ), + ), + ), + ConstrainedBox( + constraints: const BoxConstraints(minHeight: 40), + child: Align( + alignment: Alignment.centerLeft, + child: InkWell( + splashColor: appTheme.colorScheme.primary, + radius: 14, + child: Text( + signInTerms, + style: appTextTheme.caption?.copyWith(color: Colors.black54), + textAlign: TextAlign.justify, + ), + ), + ), + ) + ]; + } + + ///SIGN IN BTN + Widget _getContinueBtn(BuildContext blocContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 200, maxWidth: 200), + child: MainElevatedBtn( + onClick: () { + _onClickedContinue(blocContext); + }, + lbl: loginBtn, + showLoading: _isLoading, + ), + ), + ], + ); + } + + /// STATE + void _listenToStateChange(BuildContext context, AuthUserState state) { + if (state is GetPhoneState) { + setState(() { + _step = LoginSteps.getPhone; + }); + } + + if (state is GetCodeState) { + setState(() { + _step = LoginSteps.verifyPhone; + }); + } + + if (state is CodeSentToPhoneState) { + setState(() { + _verificationId = state.verificationId; + _resendToken = state.resendToken; + _step = LoginSteps.verifyPhone; + _isLoading = false; + }); + } + + if (state is VerificationFailedState) { + setState(() { + _verificationErr = state.errMsg; + _isLoading = false; + }); + } + + if (state is SigningUserInFailedState) { + setState(() { + _signInErr = state.errMsg; + _isLoading = false; + }); + } + + if (state is UserSignedInState) { + setState(() { + _isLoading = false; + _step = LoginSteps.signedIn; + //GO BACK TO SPLASH + Navigator.pushReplacementNamed( + context, + splashRoute, + ); + }); + } + + if (state is LoadingAuthState) { + setState(() { + _isLoading = true; + }); + } + } + + /// ACTIONS + void _onClickedContinue(BuildContext blocContext) { + if (_isLoading) return; + + setState(() { + _verificationErr = null; + _signInErr = null; + }); + + if (_step == LoginSteps.getPhone) { + //we have the phone number + _onSendOrResendCodeClicked(blocContext); + } else { + //we have an sms code + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(SignInAuthUserEvent( + verificationId: _verificationId, smsCode: _smsCode)); + } + } + + bool _onBackPressed(BuildContext blocContext) { + if (_step == LoginSteps.verifyPhone) { + //go back to get phone + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher + ?.add(SwitchLoginModeEvent(isInGetPhoneNotGetCodeMode: false)); + return false; //don't go back + } else { + return true; + } + } + + void _goToGetPhone(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher + ?.add(SwitchLoginModeEvent(isInGetPhoneNotGetCodeMode: false)); + } + + void _onSendOrResendCodeClicked(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + String phoneNumber = _phoneController.text; + String countryCode = countryToPhoneCodeMap[_selectedCountry] ?? ''; + _eventDispatcher?.add(VerifyAuthUserEvent( + phoneNumber: phoneNumber, + countryCode: countryCode, + resendToken: _resendToken)); + } + + /// life cycle + @override + void dispose() { + _phoneController.dispose(); + _eventDispatcher?.close(); + super.dispose(); + } +} + +enum LoginSteps { getPhone, verifyPhone, signedIn } diff --git a/lib/features/auth/presentation/state/auth_user_bloc.dart b/lib/features/auth/presentation/state/auth_user_bloc.dart new file mode 100644 index 0000000..a9d3ee8 --- /dev/null +++ b/lib/features/auth/presentation/state/auth_user_bloc.dart @@ -0,0 +1,109 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/auth/domain/use_cases/auth_use_cases.dart'; +import 'package:redting/res/strings.dart'; + +part 'auth_user_event.dart'; +part 'auth_user_state.dart'; + +class AuthUserBloc extends Bloc { + final AuthUseCases authUseCases; + + AuthUserBloc({required this.authUseCases}) : super(InitialAuthUserState()) { + on(_onLoadAuthUserEvent); + on(_verifyAuthUserEvent); + on(_signInAuthUserEvent); + on(_switchLoginModeEvent); + on(_onSendVerificationAttemptEvent); + } + + FutureOr _onLoadAuthUserEvent( + LoadAuthUserEvent event, Emitter emit) async { + //loading + emit(LoadingAuthState()); + + ServiceResult result = await authUseCases.getAuthenticatedUser.execute(); + if (result.errorOccurred) { + emit(ErrorLoadingAuthUserState()); + } else if (result.data is AuthUser) { + emit(UserSignedInState(result.data as AuthUser)); + } else if (result.data == null) { + emit(NoAuthUserFoundState()); + } + } + + /// WHEN SENDING CODE WE PASS A CALLBACK + /// AN EVENT [SendVerificationCodeAttemptedEvent] IS TRIGGERED IN THAT CALLBACK + FutureOr _verifyAuthUserEvent( + VerifyAuthUserEvent event, Emitter emit) async { + //loading + emit(LoadingAuthState()); + authUseCases.sendVerificationCodeUseCase.execute( + phone: event.phoneNumber, + code: event.countryCode, + resendToken: event.resendToken, + callback: (PhoneVerificationResult result) async { + add(SendVerificationCodeAttemptedEvent(result)); + }, + ); + } + + /// WHEN SENDING CODE WE PASS A CALLBACK + /// THIS EVENT IS TRIGGERED IN THAT CALLBACK + FutureOr _onSendVerificationAttemptEvent( + SendVerificationCodeAttemptedEvent event, + Emitter emit) async { + PhoneVerificationResult result = event.result; + if (result.errorOccurred) { + emit(VerificationFailedState(result.errMsg ?? unknownCodeSendingErr)); + } + + if (result.isAutoVerified) { + ServiceResult signInResult = await authUseCases.signUserInUseCase + .execute(credential: result.credential); + + if (signInResult.errorOccurred) { + emit(SigningUserInFailedState( + signInResult.errorMessage ?? unknownCodeSendingErr)); + } else { + emit(UserSignedInState(signInResult.data as AuthUser)); + } + } + + if (result.isCodeSent && result.verificationId != null) { + emit(CodeSentToPhoneState( + resendToken: result.resendToken, + verificationId: result.verificationId!)); + } + } + + FutureOr _signInAuthUserEvent( + SignInAuthUserEvent event, Emitter emit) async { + //loading + emit(LoadingAuthState()); + ServiceResult signInResult = await authUseCases.signUserInUseCase + .execute(smsCode: event.smsCode, verificationId: event.verificationId); + + if (signInResult.errorOccurred) { + emit(SigningUserInFailedState( + signInResult.errorMessage ?? unknownCodeSendingErr)); + } else { + emit(UserSignedInState(signInResult.data as AuthUser)); + } + } + + _switchLoginModeEvent( + SwitchLoginModeEvent event, Emitter emit) { + //switch + if (event.isInGetPhoneNotGetCodeMode) { + emit(GetCodeState()); + } else { + emit(GetPhoneState()); + } + } +} diff --git a/lib/features/auth/presentation/state/auth_user_event.dart b/lib/features/auth/presentation/state/auth_user_event.dart new file mode 100644 index 0000000..314bae3 --- /dev/null +++ b/lib/features/auth/presentation/state/auth_user_event.dart @@ -0,0 +1,31 @@ +part of 'auth_user_bloc.dart'; + +@immutable +abstract class AuthUserEvent {} + +class LoadAuthUserEvent extends AuthUserEvent {} + +class VerifyAuthUserEvent extends AuthUserEvent { + final String phoneNumber; + final String countryCode; + final int? resendToken; + VerifyAuthUserEvent( + {required this.phoneNumber, required this.countryCode, this.resendToken}); +} + +class SendVerificationCodeAttemptedEvent extends AuthUserEvent { + final PhoneVerificationResult result; + SendVerificationCodeAttemptedEvent(this.result); +} + +class SignInAuthUserEvent extends AuthUserEvent { + final String verificationId; + final String smsCode; + SignInAuthUserEvent({required this.verificationId, required this.smsCode}); +} + +/// login steps / mode +class SwitchLoginModeEvent extends AuthUserEvent { + final bool isInGetPhoneNotGetCodeMode; + SwitchLoginModeEvent({required this.isInGetPhoneNotGetCodeMode}); +} diff --git a/lib/features/auth/presentation/state/auth_user_state.dart b/lib/features/auth/presentation/state/auth_user_state.dart new file mode 100644 index 0000000..205f3f1 --- /dev/null +++ b/lib/features/auth/presentation/state/auth_user_state.dart @@ -0,0 +1,40 @@ +part of 'auth_user_bloc.dart'; + +@immutable +abstract class AuthUserState {} + +class InitialAuthUserState extends AuthUserState {} + +class LoadingAuthState extends AuthUserState {} + +class ErrorLoadingAuthUserState extends AuthUserState {} + +class NoAuthUserFoundState extends AuthUserState {} + +/// verification +class CodeSentToPhoneState extends AuthUserState { + final int? resendToken; + final String verificationId; + CodeSentToPhoneState({this.resendToken, required this.verificationId}); +} + +class VerificationFailedState extends AuthUserState { + final String errMsg; + VerificationFailedState(this.errMsg); +} + +/// sign in +class SigningUserInFailedState extends AuthUserState { + final String errMsg; + SigningUserInFailedState(this.errMsg); +} + +class UserSignedInState extends AuthUserState { + final AuthUser authUser; + UserSignedInState(this.authUser); +} + +/// login steps / mode +class GetPhoneState extends AuthUserState {} + +class GetCodeState extends AuthUserState {} diff --git a/lib/features/blind_date_setup/data/data_sources/local/hive_source.dart b/lib/features/blind_date_setup/data/data_sources/local/hive_source.dart new file mode 100644 index 0000000..a6c7f67 --- /dev/null +++ b/lib/features/blind_date_setup/data/data_sources/local/hive_source.dart @@ -0,0 +1,18 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_names.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/local/local_source.dart'; + +class HiveBlindDateSource implements LocalBlindDateSource { + final Box _blindDatesSetupStatBox = Hive.box(canMakeBlindDatesBox); + + @override + Future hasReachedMaxSetups() async { + return _blindDatesSetupStatBox.get(canMakeBlindDatesBoxKey, + defaultValue: false); + } + + @override + Future setHasReachedMaxSetups() async { + await _blindDatesSetupStatBox.put(canMakeBlindDatesBoxKey, true); + } +} diff --git a/lib/features/blind_date_setup/data/data_sources/local/local_source.dart b/lib/features/blind_date_setup/data/data_sources/local/local_source.dart new file mode 100644 index 0000000..1a69886 --- /dev/null +++ b/lib/features/blind_date_setup/data/data_sources/local/local_source.dart @@ -0,0 +1,4 @@ +abstract class LocalBlindDateSource { + Future hasReachedMaxSetups(); + Future setHasReachedMaxSetups(); +} diff --git a/lib/features/blind_date_setup/data/data_sources/remote/fire_blind_date.dart b/lib/features/blind_date_setup/data/data_sources/remote/fire_blind_date.dart new file mode 100644 index 0000000..a0a6391 --- /dev/null +++ b/lib/features/blind_date_setup/data/data_sources/remote/fire_blind_date.dart @@ -0,0 +1,184 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/foundation.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/remote/remote_source.dart'; +import 'package:redting/features/blind_date_setup/data/entities/blind_date_entity.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/profile/data/data_sources/remote/fire_profile.dart'; +import 'package:redting/features/profile/data/entities/user_phones_entity.dart'; + +const blindDatesCollection = "blind_dates"; + +class FireBlindDate implements RemoteBlindDateSetupSource { + final FirebaseFirestore _db = FirebaseFirestore.instance; + DocumentSnapshot? _lastFetchedBlindDate; + + @override + Future getBlindSetupsDoneByUser(AuthUser user) async { + try { + QuerySnapshot> snapshot = await _db + .collection(blindDatesCollection) + .where(BlindDateEntity.setupByUserIdFieldName, isEqualTo: user.userId) + .get(); + List setups = []; + if (snapshot.docs.isNotEmpty) { + setups = snapshot.docs + .map((e) => BlindDateEntity.fromJson(e.data())) + .toList(growable: false); + } + return ServiceResult(errorOccurred: false, data: setups); + } catch (e) { + if (kDebugMode) { + print("=============== getBlindSetupsDoneByUser $e ============="); + } + return ServiceResult(errorOccurred: true); + } + } + + @override + Future setupBlindDate(BlindDate blindDate) async { + try { + var snapshot = await _db + .collection(blindDatesCollection) + .where(BlindDateEntity.idFieldName, isEqualTo: blindDate.id) + .limit(1) + .get(); + + if (snapshot.size > 0) { + return ServiceResult(errorOccurred: false); + } + await _db.collection(blindDatesCollection).doc().set(blindDate.toJson()); + return ServiceResult(errorOccurred: false); + } catch (e) { + if (kDebugMode) { + print("=============== setupBlindDate $e ============="); + } + return ServiceResult(errorOccurred: true); + } + } + + @override + Future?> getIdsOfBlindDateParties( + String user1PhoneNumber, String user2PhoneNumber) async { + try { + Map phoneToIdsMap = { + user1PhoneNumber: null, + user2PhoneNumber: null + }; + var snapshot1 = await _db + .collection(userPhonesCollection) + .doc(user1PhoneNumber) + .get(); + + if (snapshot1.exists && snapshot1.data() != null) { + String uid = UserPhoneEntity.fromJson(snapshot1.data()!).userId; + phoneToIdsMap[user1PhoneNumber] = uid; + } + + var snapshot2 = await _db + .collection(userPhonesCollection) + .doc(user2PhoneNumber) + .get(); + if (snapshot2.exists && snapshot2.data() != null) { + String uid = UserPhoneEntity.fromJson(snapshot2.data()!).userId; + phoneToIdsMap[user2PhoneNumber] = uid; + } + return phoneToIdsMap; + } catch (e) { + if (kDebugMode) { + print("=============== getIdsOfBlindDateParties $e ============="); + } + return null; + } + } + + @override + Future> loadOldBlindDates(String userId) async { + try { + if (_lastFetchedBlindDate == null) { + return []; + } + + QuerySnapshot> results = await _db + .collection(blindDatesCollection) + .where(BlindDateEntity.membersFieldName, arrayContains: userId) + .orderBy(BlindDateEntity.orderByFieldName, descending: true) + .startAfterDocument(_lastFetchedBlindDate!) + .limit(queryPageResultsSize) + .get(); + + if (results.docs.isNotEmpty) { + _lastFetchedBlindDate = results.docs.last; + return results.docs + .map((e) => BlindDateEntity.fromJson(e.data())) + .toList(growable: false); + } + return []; + } catch (e) { + if (kDebugMode) { + print("============= load older messages exc $e ========"); + } + return []; + } + } + + @override + Stream> listenToRecentBlindDates( + String userId, + ) { + try { + Query> query = _db + .collection(blindDatesCollection) + .where(BlindDateEntity.membersFieldName, arrayContains: userId) + .orderBy(BlindDateEntity.orderByFieldName, descending: true) + .limit(queryPageResultsSize); + return query.snapshots().map((event) { + return _mapSnapshotsToBlindDates(event); + }); + } catch (e) { + if (kDebugMode) { + print("=============== listen to messages $e ========="); + } + return const Stream.empty(); + } + } + + List _mapSnapshotsToBlindDates( + QuerySnapshot> event) { + List results = []; + for (var change in event.docChanges) { + BlindDate blindDate = BlindDateEntity.fromJson(change.doc.data()!); + OperationRealTimeResult result; + switch (change.type) { + case DocumentChangeType.added: + //cache the last added for pagination + _lastFetchedBlindDate = change.doc; + result = OperationRealTimeResult( + data: blindDate, + realTimeEventType: RealTimeEventType.added, + ); + break; + case DocumentChangeType.modified: + result = OperationRealTimeResult( + data: blindDate, + realTimeEventType: RealTimeEventType.modified, + ); + break; + case DocumentChangeType.removed: + //reset pagination cache + if (_lastFetchedBlindDate == change.doc) { + _lastFetchedBlindDate = null; + } + result = OperationRealTimeResult( + data: blindDate, + realTimeEventType: RealTimeEventType.deleted, + ); + break; + } + results.add(result); + } + return results; + } +} diff --git a/lib/features/blind_date_setup/data/data_sources/remote/remote_source.dart b/lib/features/blind_date_setup/data/data_sources/remote/remote_source.dart new file mode 100644 index 0000000..5ccfdbd --- /dev/null +++ b/lib/features/blind_date_setup/data/data_sources/remote/remote_source.dart @@ -0,0 +1,14 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; + +abstract class RemoteBlindDateSetupSource { + Future getBlindSetupsDoneByUser(AuthUser user); + Future setupBlindDate(BlindDate blindDate); + Future?> getIdsOfBlindDateParties( + String user1PhoneNumber, String user2PhoneNumber); + Stream> listenToRecentBlindDates( + String userId, + ); + Future> loadOldBlindDates(String userId); +} diff --git a/lib/features/blind_date_setup/data/entities/blind_date_entity.dart b/lib/features/blind_date_setup/data/entities/blind_date_entity.dart new file mode 100644 index 0000000..67cfb4a --- /dev/null +++ b/lib/features/blind_date_setup/data/entities/blind_date_entity.dart @@ -0,0 +1,61 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/utils/flutter_fire_date_time_utils.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/matching/data/entities/matching_profiles_entity.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/res/strings.dart'; + +part 'blind_date_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +class BlindDateEntity implements BlindDate { + static String setupByUserIdFieldName = "setupByUserId"; + static String idFieldName = "id"; + static String membersFieldName = "members"; + static String orderByFieldName = "setupOn"; + + @override + String id; + + @override + String iceBreaker; + + @override + String setupByUserId; + + @override + @TimestampConverter() + DateTime setupOn; + + @override + List members; + + BlindDateEntity( + {required this.id, + required this.iceBreaker, + required this.setupByUserId, + required this.setupOn, + List? members}) + : members = members ?? []; + + @override + factory BlindDateEntity.fromJson(Map json) => + _$BlindDateEntityFromJson(json); + + @override + Map toJson() => _$BlindDateEntityToJson(this); + + @override + BlindDate fromJson(Map json) { + return BlindDateEntity.fromJson(json); + } + + @override + MatchingMembers toMatchingMembersType({required String thisUserId}) { + return MatchingMembersEntity( + thisUserId, + "$blindDateOtherUserName ${thisUserId[0]}${thisUserId[thisUserId.length - 1]}", + ""); + } +} diff --git a/lib/features/blind_date_setup/data/entities/blind_date_entity.g.dart b/lib/features/blind_date_setup/data/entities/blind_date_entity.g.dart new file mode 100644 index 0000000..176d21f --- /dev/null +++ b/lib/features/blind_date_setup/data/entities/blind_date_entity.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'blind_date_entity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +BlindDateEntity _$BlindDateEntityFromJson(Map json) => BlindDateEntity( + id: json['id'] as String, + iceBreaker: json['iceBreaker'] as String, + setupByUserId: json['setupByUserId'] as String, + setupOn: + const TimestampConverter().fromJson(json['setupOn'] as Timestamp), + members: + (json['members'] as List?)?.map((e) => e as String).toList(), + ); + +Map _$BlindDateEntityToJson(BlindDateEntity instance) => + { + 'id': instance.id, + 'iceBreaker': instance.iceBreaker, + 'setupByUserId': instance.setupByUserId, + 'setupOn': const TimestampConverter().toJson(instance.setupOn), + 'members': instance.members, + }; diff --git a/lib/features/blind_date_setup/data/repository/blind_date_repo_impl.dart b/lib/features/blind_date_setup/data/repository/blind_date_repo_impl.dart new file mode 100644 index 0000000..27d92ff --- /dev/null +++ b/lib/features/blind_date_setup/data/repository/blind_date_repo_impl.dart @@ -0,0 +1,128 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/local/local_source.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/remote/remote_source.dart'; +import 'package:redting/features/blind_date_setup/data/entities/blind_date_entity.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; +import 'package:redting/res/strings.dart'; + +const maxBlindDatesSetupsAllowed = 2; + +class BlindDateRepoImpl implements BlindDateRepo { + final AuthRepository _authRepository; + final MatchingRepository _matchingRepository; + final RemoteBlindDateSetupSource _remoteBlindDateSource; + final LocalBlindDateSource _localBlindDateSource; + BlindDateRepoImpl(this._authRepository, this._remoteBlindDateSource, + this._localBlindDateSource, this._matchingRepository); + + @override + Future canSetupBlindDate(AuthUser user) async { + //check locally first + bool hasReachedMax = await _localBlindDateSource.hasReachedMaxSetups(); + if (hasReachedMax) { + return ServiceResult(data: !hasReachedMax); + } + + //then check remote + ServiceResult result = + await _remoteBlindDateSource.getBlindSetupsDoneByUser(user); + hasReachedMax = result.data is List && + (result.data as List).length >= maxBlindDatesSetupsAllowed; + if (hasReachedMax) { + await _localBlindDateSource.setHasReachedMaxSetups(); + } + + return ServiceResult( + data: !hasReachedMax, + errorOccurred: result.errorOccurred, + errorMessage: result.errorMessage); + } + + @override + Future getAuthUserUseCase() { + return _authRepository.getCachedAuthUser(); + } + + @override + Future setupBlindDate(AuthUser authUser, String phoneNumber1, + String phoneNumber2, String iceBreaker) async { + String formattedNum1 = "+${phoneNumber1.trim()}".replaceAll("++", "+"); + String formattedNum2 = "+${phoneNumber2.trim()}".replaceAll("++", "+"); + + String? errMsg; + if (formattedNum1.length == 1 || + formattedNum2.length == 1 || + formattedNum1 == formattedNum2) { + errMsg = phoneNumbersInvalid; + } + + if (errMsg == null && + (formattedNum1[0] != "+" || formattedNum2[0] != "+")) { + errMsg = phoneNumbersMissingCountryCodeErr; + } + + if (errMsg == null && + (authUser.phoneNumber.trim() == formattedNum1 || + authUser.phoneNumber.trim() == formattedNum2)) { + errMsg = cannotSetupBlindDateWithSelfErr; + } + + if (errMsg == null && iceBreaker.trim().isEmpty) { + errMsg = funIceBreakerMissingErr; + } + + if (errMsg != null) { + return ServiceResult(errorMessage: errMsg, errorOccurred: true); + } + + final phoneToIds = await _remoteBlindDateSource.getIdsOfBlindDateParties( + formattedNum1, formattedNum2); + if (phoneToIds == null) { + return ServiceResult( + errorOccurred: true, errorMessage: blindDateSetupUnknownErr); + } + if (phoneToIds[formattedNum1] == null) { + return ServiceResult( + errorOccurred: true, + errorMessage: "$formattedNum1 : $blindDateSetupNotSignedIn"); + } + + if (phoneToIds[formattedNum2] == null) { + return ServiceResult( + errorOccurred: true, + errorMessage: "$formattedNum2 : $blindDateSetupNotSignedIn"); + } + + List members = [ + phoneToIds[formattedNum1]!, + phoneToIds[formattedNum2]! + ]; + return _remoteBlindDateSource.setupBlindDate(BlindDateEntity( + id: BlindDate.concatUser1User2IdsSortAndGetAsId(members), + iceBreaker: iceBreaker, + setupByUserId: authUser.userId, + setupOn: DateTime.now(), + members: members)); + } + + @override + Future fetchIceBreakerMessages() async { + return await _matchingRepository.getCachedIceBreakers(); + } + + @override + Stream> listenToNewBlindDateSetups( + String userId) { + return _remoteBlindDateSource.listenToRecentBlindDates(userId); + } + + @override + Future> loadOldBlindDateSetups(String userId) async { + return await _remoteBlindDateSource.loadOldBlindDates(userId); + } +} diff --git a/lib/features/blind_date_setup/di/blind_date_di.dart b/lib/features/blind_date_setup/di/blind_date_di.dart new file mode 100644 index 0000000..d44a1d7 --- /dev/null +++ b/lib/features/blind_date_setup/di/blind_date_di.dart @@ -0,0 +1,54 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/local/hive_source.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/local/local_source.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/remote/fire_blind_date.dart'; +import 'package:redting/features/blind_date_setup/data/data_sources/remote/remote_source.dart'; +import 'package:redting/features/blind_date_setup/data/repository/blind_date_repo_impl.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/fetch/fetch_blind_dates_usecases.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/fetch/listen_to_blind_dates_stream.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/fetch/load_old_blind_dates.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/get_user.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/blind_date_usecases.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/check_if_can_setup_blind_date.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/get_icebreakers.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/setup_blind_date.dart'; +import 'package:redting/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_bloc.dart'; +import 'package:redting/features/blind_date_setup/presentation/pages/state/setup/blind_date_bloc.dart'; + +init() { + GetIt diInstance = GetIt.instance; + diInstance.registerFactory(() => BlindDateBloc(diInstance())); + diInstance.registerFactory( + () => LoadBlindDatesBloc(diInstance())); + + /// use cases + diInstance.registerLazySingleton(() => BlindDateUseCases( + diInstance(), diInstance(), diInstance(), diInstance())); + diInstance.registerLazySingleton( + () => FetchBlindDatesUseCases(diInstance(), diInstance(), diInstance())); + + diInstance.registerLazySingleton( + () => CheckIfCanSetupBlindDateUseCase(diInstance())); + diInstance.registerLazySingleton( + () => GetAuthUserUseCase(diInstance())); + diInstance.registerLazySingleton( + () => SetupBlindDateUseCase(diInstance())); + diInstance.registerLazySingleton( + () => GetIceBreakersUseCase(diInstance())); + diInstance.registerLazySingleton( + () => GetBlindDatesStreamUseCase(diInstance())); + diInstance.registerLazySingleton( + () => LoadOlderBlindDatesUseCase(diInstance())); + + /// repo + diInstance.registerLazySingleton(() => BlindDateRepoImpl( + diInstance(), diInstance(), diInstance(), diInstance())); + + // data sources + diInstance + .registerLazySingleton(() => HiveBlindDateSource()); + + diInstance + .registerLazySingleton(() => FireBlindDate()); +} diff --git a/lib/features/blind_date_setup/domain/model/blind_date.dart b/lib/features/blind_date_setup/domain/model/blind_date.dart new file mode 100644 index 0000000..90c3ac4 --- /dev/null +++ b/lib/features/blind_date_setup/domain/model/blind_date.dart @@ -0,0 +1,24 @@ +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +abstract class BlindDate { + String id; + String setupByUserId; + List members; + String iceBreaker; + DateTime setupOn; + BlindDate( + this.id, this.setupByUserId, this.members, this.iceBreaker, this.setupOn); + + Map toJson(); + BlindDate fromJson(Map json); + static String concatUser1User2IdsSortAndGetAsId(List members) { + String user1 = members[0]; + String user2 = members[1]; + String concatUser1User2 = "$user1$user2"; + final concatUser1User2List = concatUser1User2.split(""); + concatUser1User2List.sort((a, b) => a.compareTo(b)); + return concatUser1User2List.join(""); + } + + MatchingMembers toMatchingMembersType({required String thisUserId}); +} diff --git a/lib/features/blind_date_setup/domain/repository/blind_date_repo.dart b/lib/features/blind_date_setup/domain/repository/blind_date_repo.dart new file mode 100644 index 0000000..0687ca3 --- /dev/null +++ b/lib/features/blind_date_setup/domain/repository/blind_date_repo.dart @@ -0,0 +1,15 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; + +abstract class BlindDateRepo { + Future getAuthUserUseCase(); + Future canSetupBlindDate(AuthUser user); + Future setupBlindDate(AuthUser authUser, String phoneNumber1, + String phoneNumber2, String iceBreaker); + Future fetchIceBreakerMessages(); + Stream> listenToNewBlindDateSetups( + String userId); + Future> loadOldBlindDateSetups(String userId); +} diff --git a/lib/features/blind_date_setup/domain/usecases/fetch/fetch_blind_dates_usecases.dart b/lib/features/blind_date_setup/domain/usecases/fetch/fetch_blind_dates_usecases.dart new file mode 100644 index 0000000..9f6c299 --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/fetch/fetch_blind_dates_usecases.dart @@ -0,0 +1,11 @@ +import 'package:redting/features/blind_date_setup/domain/usecases/fetch/listen_to_blind_dates_stream.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/fetch/load_old_blind_dates.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/get_user.dart'; + +class FetchBlindDatesUseCases { + final GetBlindDatesStreamUseCase getBlindDatesStreamUseCase; + final LoadOlderBlindDatesUseCase loadOlderBlindDatesUseCase; + final GetAuthUserUseCase getAuthUserUseCase; + FetchBlindDatesUseCases(this.getBlindDatesStreamUseCase, + this.loadOlderBlindDatesUseCase, this.getAuthUserUseCase); +} diff --git a/lib/features/blind_date_setup/domain/usecases/fetch/listen_to_blind_dates_stream.dart b/lib/features/blind_date_setup/domain/usecases/fetch/listen_to_blind_dates_stream.dart new file mode 100644 index 0000000..e19ff81 --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/fetch/listen_to_blind_dates_stream.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; + +class GetBlindDatesStreamUseCase { + final BlindDateRepo _blindDateRepo; + GetBlindDatesStreamUseCase(this._blindDateRepo); + + Stream> execute(String userId) { + return _blindDateRepo.listenToNewBlindDateSetups(userId); + } +} diff --git a/lib/features/blind_date_setup/domain/usecases/fetch/load_old_blind_dates.dart b/lib/features/blind_date_setup/domain/usecases/fetch/load_old_blind_dates.dart new file mode 100644 index 0000000..9fe1b76 --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/fetch/load_old_blind_dates.dart @@ -0,0 +1,11 @@ +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; + +class LoadOlderBlindDatesUseCase { + final BlindDateRepo _blindDateRepo; + LoadOlderBlindDatesUseCase(this._blindDateRepo); + + Future> execute(String userId) { + return _blindDateRepo.loadOldBlindDateSetups(userId); + } +} diff --git a/lib/features/blind_date_setup/domain/usecases/get_user.dart b/lib/features/blind_date_setup/domain/usecases/get_user.dart new file mode 100644 index 0000000..f81e148 --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/get_user.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; + +class GetAuthUserUseCase { + final BlindDateRepo _blindDateRepo; + GetAuthUserUseCase(this._blindDateRepo); + + Future execute() async { + return await _blindDateRepo.getAuthUserUseCase(); + } +} diff --git a/lib/features/blind_date_setup/domain/usecases/setup/blind_date_usecases.dart b/lib/features/blind_date_setup/domain/usecases/setup/blind_date_usecases.dart new file mode 100644 index 0000000..fcfe8f9 --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/setup/blind_date_usecases.dart @@ -0,0 +1,13 @@ +import 'package:redting/features/blind_date_setup/domain/usecases/get_user.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/check_if_can_setup_blind_date.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/get_icebreakers.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/setup_blind_date.dart'; + +class BlindDateUseCases { + final CheckIfCanSetupBlindDateUseCase checkIfCanSetup; + final GetAuthUserUseCase getAuthUser; + final SetupBlindDateUseCase setupBlindDate; + final GetIceBreakersUseCase getIceBreakersUseCase; + BlindDateUseCases(this.checkIfCanSetup, this.getAuthUser, this.setupBlindDate, + this.getIceBreakersUseCase); +} diff --git a/lib/features/blind_date_setup/domain/usecases/setup/check_if_can_setup_blind_date.dart b/lib/features/blind_date_setup/domain/usecases/setup/check_if_can_setup_blind_date.dart new file mode 100644 index 0000000..b992312 --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/setup/check_if_can_setup_blind_date.dart @@ -0,0 +1,12 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; + +class CheckIfCanSetupBlindDateUseCase { + final BlindDateRepo _blindDateRepo; + CheckIfCanSetupBlindDateUseCase(this._blindDateRepo); + + Future execute(AuthUser user) async { + return await _blindDateRepo.canSetupBlindDate(user); + } +} diff --git a/lib/features/blind_date_setup/domain/usecases/setup/get_icebreakers.dart b/lib/features/blind_date_setup/domain/usecases/setup/get_icebreakers.dart new file mode 100644 index 0000000..f404e0c --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/setup/get_icebreakers.dart @@ -0,0 +1,11 @@ +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; + +class GetIceBreakersUseCase { + final BlindDateRepo _blindDateRepo; + GetIceBreakersUseCase(this._blindDateRepo); + + Future execute() async { + return await _blindDateRepo.fetchIceBreakerMessages(); + } +} diff --git a/lib/features/blind_date_setup/domain/usecases/setup/setup_blind_date.dart b/lib/features/blind_date_setup/domain/usecases/setup/setup_blind_date.dart new file mode 100644 index 0000000..3c7256c --- /dev/null +++ b/lib/features/blind_date_setup/domain/usecases/setup/setup_blind_date.dart @@ -0,0 +1,14 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/repository/blind_date_repo.dart'; + +class SetupBlindDateUseCase { + final BlindDateRepo _blindDateRepo; + SetupBlindDateUseCase(this._blindDateRepo); + + Future execute(AuthUser authUser, String phoneNumber1, + String phoneNumber2, String iceBreaker) async { + return await _blindDateRepo.setupBlindDate( + authUser, phoneNumber1, phoneNumber2, iceBreaker); + } +} diff --git a/lib/features/blind_date_setup/presentation/components/app_bar.dart b/lib/features/blind_date_setup/presentation/components/app_bar.dart new file mode 100644 index 0000000..8dffa8a --- /dev/null +++ b/lib/features/blind_date_setup/presentation/components/app_bar.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/assets_paths.dart'; +import 'package:redting/res/dimens.dart'; + +PreferredSizeWidget buildBlindDateScreenAppBar() { + return AppBar( + toolbarHeight: appBarHeight, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + redLogoPath, + height: appBarHeight - (paddingStd * 2), + fit: BoxFit.fitHeight, + ), + ]), + ); +} diff --git a/lib/features/blind_date_setup/presentation/pages/blind_date_setup_screen.dart b/lib/features/blind_date_setup/presentation/pages/blind_date_setup_screen.dart new file mode 100644 index 0000000..c918bd9 --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/blind_date_setup_screen.dart @@ -0,0 +1,339 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/core/components/screens/gradient_screen_container.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/components/text_input/outlined_txtfield.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/presentation/components/app_bar.dart'; +import 'package:redting/features/blind_date_setup/presentation/pages/state/setup/blind_date_bloc.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class BlindDateSetupScreen extends StatefulWidget { + final UserProfile userProfile; + const BlindDateSetupScreen({Key? key, required this.userProfile}) + : super(key: key); + + @override + State createState() => _BlindDateSetupScreenState(); +} + +class _BlindDateSetupScreenState extends State { + late TextEditingController _phone1Controller, _phone2Controller; + + bool _isSending = false, _initializing = false; + bool? _canSetupBlindDate; + BlindDateBloc? _eventDispatcher; + late AuthUser _authUser; + final List _iceBreakers = [defaultIceBreaker]; + String _chosenIceBreaker = defaultIceBreaker; + + @override + void initState() { + _phone1Controller = TextEditingController()..text = "8618616753659"; + _phone2Controller = TextEditingController()..text = "8610000000000"; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ScaffoldWrapper( + child: Scaffold( + extendBodyBehindAppBar: false, + appBar: buildBlindDateScreenAppBar(), + body: GradientScreenContainer( + screen: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToState, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is BlindDateInitial) { + _initialize(blocContext); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (_initializing) + const Center(child: CircularProgress()), + Text( + blindDateSetupTitle, + style: appTextTheme.headline6?.copyWith( + color: appTheme.colorScheme.primary), + ), + RichText( + text: TextSpan( + text: blindDateSetupSubtitle, + style: appTextTheme.subtitle2?.copyWith( + color: appTheme.colorScheme.secondary), + children: [ + TextSpan( + text: "\n • $blindDateInstructionsStep1", + style: appTextTheme.button?.copyWith( + color: Colors.black, height: 1.5)), + TextSpan( + text: '\n • $blindDateInstructionsStep2', + style: appTextTheme.button?.copyWith( + color: Colors.black, height: 1.5)), + TextSpan( + text: '\n • $blindDateInstructionsStep3', + style: appTextTheme.button?.copyWith( + color: Colors.black, height: 1.5)), + ], + ), + ), + const SizedBox( + height: paddingStd, + ), + if (_canSetupBlindDate == false) + ..._buildCannotDateSetupViews(), + if (_canSetupBlindDate == true) + ..._buildBlindDateForm() + ], + ); + }, + ), + ))), + ), + ), + ); + } + + /// state listener + void _initialize(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(GetAuthUserEvent()); + } + + void _listenToState(BuildContext context, BlindDateState state) { + /// AUTH USER + if (state is GettingAuthUserState) { + setState(() { + _initializing = true; + }); + } + if (state is GettingAuthUserFailedState) { + setState(() { + _initializing = false; + }); + _showSnack(state.errMsg); + } + if (state is GettingAuthUserSuccessfulState) { + _authUser = state.user; + _eventDispatcher?.add(CheckIfCanSetupBlindDateEvent(_authUser)); + } + + /// CAN SETUP + if (state is CheckingIfCanSetupDateFailedState) { + setState(() { + _initializing = false; + }); + _showSnack(state.errMsg); + } + if (state is CheckingIfCanSetupDateSuccessfulState) { + setState(() { + _canSetupBlindDate = state.canSetup; + }); + _eventDispatcher?.add(GettingIceBreakersEvent()); + } + + /// ICE BREAKERS + if (state is GettingIceBreakersSuccessState) { + setState(() { + _initializing = false; + }); + if (state.iceBreakerMessages.messages.isNotEmpty) { + _iceBreakers.clear(); + _iceBreakers.addAll(state.iceBreakerMessages.messages); + _iceBreakers.shuffle(); + _chosenIceBreaker = _iceBreakers[0]; + } + } + + if (state is GettingIceBreakersFailedState) { + setState(() { + _initializing = false; + }); + } + + /// MATCH MAKING + if (state is SettingUpBlindDateSuccessfulState) { + setState(() { + _isSending = false; + }); + _showSnack(blindDateSetupSuccess, isError: false); + _navigateAwayAfterSec(); + } + if (state is SettingUpBlindDateState) { + setState(() { + _isSending = true; + }); + } + if (state is SettingUpBlindDateFailedState) { + setState(() { + _isSending = false; + }); + _showSnack(state.errMsg); + } + } + + void _onSetupDateClicked() { + _eventDispatcher?.add(SetupBlindDateEvent(_authUser, _phone1Controller.text, + _phone2Controller.text, _chosenIceBreaker)); + } + + /// UI + List _buildBlindDateForm() { + return [ + Text( + friend1PhoneNumberLbl, + style: appTextTheme.subtitle2, + ), + const SizedBox( + height: paddingSm, + ), + Container( + constraints: const BoxConstraints(maxWidth: 300), + child: OutlinedTxtField( + prefixText: "+", + txtInputAction: TextInputAction.next, + controller: _phone1Controller, + keyboardType: TextInputType.phone, + ), + ), + const SizedBox( + height: paddingStd, + ), + Text( + friend2PhoneNumberLbl, + style: appTextTheme.subtitle2, + ), + const SizedBox( + height: paddingSm, + ), + Container( + constraints: const BoxConstraints(maxWidth: 300), + child: OutlinedTxtField( + prefixText: "+", + txtInputAction: TextInputAction.next, + controller: _phone2Controller, + keyboardType: TextInputType.phone, + ), + ), + const SizedBox( + height: paddingStd, + ), + Text( + blindDateIceBreakerLbl, + style: appTextTheme.subtitle2?.copyWith(color: Colors.black), + softWrap: true, + ), + const SizedBox( + height: paddingSm, + ), + Container( + margin: const EdgeInsets.only(right: paddingStd), + child: ButtonTheme( + alignedDropdown: true, + child: DropdownButton( + value: _chosenIceBreaker, + isExpanded: true, + itemHeight: 52, + menuMaxHeight: 300, + borderRadius: BorderRadius.circular(12), + underline: Container( + height: 1, + color: appTheme.colorScheme.secondary, + ), + icon: const Icon(Icons.keyboard_arrow_down_rounded), + elevation: 16, + dropdownColor: appTheme.colorScheme.inversePrimary, + style: appTextTheme.bodyText2?.copyWith(color: Colors.black), + onChanged: (String? newValue) { + if (mounted && newValue != null) { + setState(() { + _chosenIceBreaker = newValue; + }); + } + }, + items: _iceBreakers.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: appTextTheme.bodyText2?.copyWith(color: Colors.black), + softWrap: true, + ), + ); + }).toList(), + ), + ), + ), + const SizedBox( + height: paddingStd, + ), + Text( + blindDateHint, + style: appTextTheme.caption, + ), + Center( + child: Container( + margin: const EdgeInsets.symmetric(vertical: paddingMd), + constraints: const BoxConstraints(maxWidth: 240), + child: MainElevatedBtn( + primaryBg: true, + showLoading: _isSending, + onClick: () { + _onSetupDateClicked(); + }, + lbl: blindDateSetupBtn, + ), + ), + ), + ]; + } + + List _buildCannotDateSetupViews() { + return [ + Text( + cannotSetupBlindDateMaxReached, + style: + appTextTheme.subtitle2?.copyWith(color: appTheme.colorScheme.error), + ) + ]; + } + + /// SNACK + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + @override + void dispose() { + _phone1Controller.dispose(); + _phone2Controller.dispose(); + super.dispose(); + } + + void _navigateAwayAfterSec({Duration seconds = const Duration(seconds: 1)}) { + Future.delayed(seconds, () { + Navigator.pop(context); + }); + } +} diff --git a/lib/features/blind_date_setup/presentation/pages/blind_dates_screen.dart b/lib/features/blind_date_setup/presentation/pages/blind_dates_screen.dart new file mode 100644 index 0000000..b2f6f66 --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/blind_dates_screen.dart @@ -0,0 +1,261 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_bloc.dart'; +import 'package:redting/features/chat/presentation/pages/chat_screen.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class BlindDatesScreen extends StatefulWidget { + const BlindDatesScreen({ + Key? key, + }) : super(key: key); + + @override + State createState() => _BlindDatesScreenState(); +} + +class _BlindDatesScreenState extends State { + LoadBlindDatesBloc? _eventDispatcher; + bool _hasFetchedAllBlindDates = false; + bool _isLoadingBlindDates = false; + + Stream>? _stream; + late ScrollController _blindDatesScrollController; + Map _blindDates = {}; + + //init with an old year + DateTime _mostRecentDate = DateTime(2021); + late AuthUser _authUser; + + @override + void initState() { + _blindDatesScrollController = ScrollController(); + _addScrollListener(); + super.initState(); + } + + @override + Widget build(BuildContext cxt) { + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is LoadBlindDatesInitial) { + _onInitState(blocContext); + } + return StreamBuilder>( + stream: _stream, + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data != null) { + _respondToDataChanges(snapshot.data!); + } + return ListView.builder( + physics: const BouncingScrollPhysics(), + controller: _blindDatesScrollController, + itemCount: _blindDates.values.length, + itemBuilder: (BuildContext context, int index) { + BlindDate date = _blindDates.values.toList()[index]; + return _buildBlindDateWidget(date); + }, + ); + }); + }))); + } + + /// INIT + void _onInitState(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(GetAuthUserEvent()); + } + + /// LISTEN TO STATE + void _listenToStateChange(BuildContext context, LoadBlindDatesState state) { + /// AUTH USER + if (state is GettingAuthUserState) { + setState(() { + _isLoadingBlindDates = true; + }); + } + if (state is GettingAuthUserFailedState) { + setState(() { + _isLoadingBlindDates = false; + }); + _showSnack(state.errMsg); + } + if (state is GettingAuthUserSuccessfulState) { + _authUser = state.user; + _eventDispatcher?.add(ListenToBlindDatesEvent( + userId: _authUser.userId, + )); + } + + if (state is ListeningToBlindDatesState) { + if (!mounted) return; + setState(() { + _stream = state.stream; + _isLoadingBlindDates = false; + }); + } + + /// loading old blindDates + if (state is LoadingState) { + if (!mounted) return; + setState(() { + _isLoadingBlindDates = true; + }); + } + + if (state is LoadedOlderBlindDatesState) { + for (BlindDate blindDate in state.dates) { + _blindDates[blindDate.id] = blindDate; + } + if (!mounted) return; + setState(() { + _isLoadingBlindDates = false; + _hasFetchedAllBlindDates = state.dates.isEmpty; + _blindDates = _blindDates; + }); + } + } + + /// INCOMING MESSAGES UPDATES + void _respondToDataChanges(List resultsList) { + for (var realTimeResult in resultsList) { + BlindDate date = realTimeResult.data as BlindDate; + switch (realTimeResult.realTimeEventType) { + case RealTimeEventType.added: + if (date.setupOn.isAfter(_mostRecentDate)) { + //new blindDate - must add to back (since we are reversing the listview) + Map newBlindDates = {}; + newBlindDates[date.id] = date; + newBlindDates.addAll(_blindDates); + _blindDates = newBlindDates; + + //update most recent + _mostRecentDate = date.setupOn; + + //scroll to bottom + _scrollToBottomOfBlindDates(); + } else { + _blindDates[date.id] = date; + } + break; + + case RealTimeEventType.modified: + if (_blindDates.containsKey(date.id)) { + _blindDates[date.id] = date; + } + break; + + case RealTimeEventType.deleted: + if (_blindDates.containsKey(date.id)) { + _blindDates.remove(date.id); + } + break; + } + } + } + + void _scrollToBottomOfBlindDates() { + _blindDatesScrollController.animateTo( + _blindDatesScrollController.position.minScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut); + } + + void _addScrollListener() { + _blindDatesScrollController.addListener(() { + if (_blindDatesScrollController.offset < + _blindDatesScrollController.position.maxScrollExtent && + !_blindDatesScrollController.position.outOfRange) { + if (!_hasFetchedAllBlindDates && !_isLoadingBlindDates) { + _eventDispatcher + ?.add(LoadMoreBlindDatesEvent(userId: _authUser.userId)); + } + } + }); + } + + /// DISPOSE + @override + void dispose() { + _blindDatesScrollController.dispose(); + super.dispose(); + } + + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + Widget _buildBlindDateWidget(BlindDate date) { + try { + MatchingMembers thisUser = + date.toMatchingMembersType(thisUserId: _authUser.userId); + MatchingMembers otherUser = date.toMatchingMembersType( + thisUserId: date.members.firstWhere((id) => id != _authUser.userId)); + return Container( + key: ValueKey(date.id), + margin: const EdgeInsets.only(bottom: paddingStd), + child: InkWell( + splashColor: appTheme.colorScheme.primary, + radius: 8, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatScreen( + thisUser: thisUser, + thatUser: otherUser, + iceBreaker: date.iceBreaker)), + ); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: Icon( + Icons.person, + size: 32, + color: appTheme.colorScheme.secondary, + )), + const SizedBox( + width: paddingStd, + ), + Expanded( + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 32), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + otherUser.userName, + style: appTextTheme.subtitle1 + ?.copyWith(color: Colors.black87), + ), + ), + ), + ) + ], + ), + ), + ); + } catch (e) { + return const SizedBox.shrink(); //should never happen + } + } +} diff --git a/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_bloc.dart b/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_bloc.dart new file mode 100644 index 0000000..72a31f4 --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_bloc.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/model/blind_date.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/fetch/fetch_blind_dates_usecases.dart'; +import 'package:redting/res/strings.dart'; + +part 'load_blind_dates_event.dart'; +part 'load_blind_dates_state.dart'; + +class LoadBlindDatesBloc + extends Bloc { + final FetchBlindDatesUseCases _blindDatesUseCases; + LoadBlindDatesBloc(this._blindDatesUseCases) + : super(LoadBlindDatesInitial()) { + on(_onGetAuthUserEvent); + on(_onListenToBlindDatesEvent); + on(_onLoadMoreBlindDatesEvent); + } + + FutureOr _onGetAuthUserEvent( + GetAuthUserEvent event, Emitter emit) async { + emit(GettingAuthUserState()); + ServiceResult result = + await _blindDatesUseCases.getAuthUserUseCase.execute(); + if (result.errorOccurred || result.data is! AuthUser) { + emit(GettingAuthUserFailedState( + result.errorMessage ?? blindDateSetupUnknownErr)); + } else { + emit(GettingAuthUserSuccessfulState(result.data as AuthUser)); + } + } + + FutureOr _onListenToBlindDatesEvent( + ListenToBlindDatesEvent event, Emitter emit) { + final stream = + _blindDatesUseCases.getBlindDatesStreamUseCase.execute(event.userId); + emit(ListeningToBlindDatesState(stream)); + } + + FutureOr _onLoadMoreBlindDatesEvent( + LoadMoreBlindDatesEvent event, Emitter emit) async { + emit((LoadingState())); + List blindDates = await _blindDatesUseCases + .loadOlderBlindDatesUseCase + .execute(event.userId); + emit(LoadedOlderBlindDatesState(blindDates)); + } +} diff --git a/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_event.dart b/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_event.dart new file mode 100644 index 0000000..650c31a --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_event.dart @@ -0,0 +1,20 @@ +part of 'load_blind_dates_bloc.dart'; + +@immutable +abstract class LoadBlindDatesEvent {} + +class GetAuthUserEvent extends LoadBlindDatesEvent {} + +class ListenToBlindDatesEvent extends LoadBlindDatesEvent { + final String userId; + ListenToBlindDatesEvent({ + required this.userId, + }); +} + +class LoadMoreBlindDatesEvent extends LoadBlindDatesEvent { + final String userId; + LoadMoreBlindDatesEvent({ + required this.userId, + }); +} diff --git a/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_state.dart b/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_state.dart new file mode 100644 index 0000000..9fb334b --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/state/fetch/load_blind_dates_state.dart @@ -0,0 +1,32 @@ +part of 'load_blind_dates_bloc.dart'; + +@immutable +abstract class LoadBlindDatesState {} + +class LoadBlindDatesInitial extends LoadBlindDatesState {} + +class LoadingState extends LoadBlindDatesState {} + +class GettingAuthUserState extends LoadBlindDatesState {} + +class GettingAuthUserFailedState extends LoadBlindDatesState { + final String errMsg; + GettingAuthUserFailedState(this.errMsg); +} + +class GettingAuthUserSuccessfulState extends LoadBlindDatesState { + final AuthUser user; + GettingAuthUserSuccessfulState(this.user); +} + +class ChatLoadingState extends LoadBlindDatesState {} + +class ListeningToBlindDatesState extends LoadBlindDatesState { + final Stream> stream; + ListeningToBlindDatesState(this.stream); +} + +class LoadedOlderBlindDatesState extends LoadBlindDatesState { + final List dates; + LoadedOlderBlindDatesState(this.dates); +} diff --git a/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_bloc.dart b/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_bloc.dart new file mode 100644 index 0000000..789f77e --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_bloc.dart @@ -0,0 +1,77 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/blind_date_setup/domain/usecases/setup/blind_date_usecases.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/res/strings.dart'; + +part 'blind_date_event.dart'; +part 'blind_date_state.dart'; + +class BlindDateBloc extends Bloc { + final BlindDateUseCases _blindDateUseCases; + BlindDateBloc(this._blindDateUseCases) : super(BlindDateInitial()) { + on(_onGetAuthUserEvent); + on(_onCheckIfCanSetupBlindDateEvent); + on(_onSetupBlindDateEvent); + on(_onGettingIceBreakersEvent); + } + + FutureOr _onGetAuthUserEvent( + GetAuthUserEvent event, Emitter emit) async { + emit(GettingAuthUserState()); + ServiceResult result = await _blindDateUseCases.getAuthUser.execute(); + if (result.errorOccurred || result.data is! AuthUser) { + emit(GettingAuthUserFailedState( + result.errorMessage ?? blindDateSetupUnknownErr)); + } else { + emit(GettingAuthUserSuccessfulState(result.data as AuthUser)); + } + } + + FutureOr _onCheckIfCanSetupBlindDateEvent( + CheckIfCanSetupBlindDateEvent event, Emitter emit) async { + emit(CheckingIfCanSetupBlindDateState()); + + ServiceResult result = + await _blindDateUseCases.checkIfCanSetup.execute(event.authUser); + if (result.errorOccurred || result.data is! bool) { + emit(CheckingIfCanSetupDateFailedState( + result.errorMessage ?? blindDateSetupUnknownErr)); + } else { + emit(CheckingIfCanSetupDateSuccessfulState(result.data as bool)); + } + } + + FutureOr _onSetupBlindDateEvent( + SetupBlindDateEvent event, Emitter emit) async { + emit(SettingUpBlindDateState()); + ServiceResult result = await _blindDateUseCases.setupBlindDate.execute( + event.authUser, + event.phoneNumber1, + event.phoneNumber2, + event.iceBreaker); + if (result.errorOccurred) { + emit(SettingUpBlindDateFailedState( + result.errorMessage ?? blindDateSetupUnknownErr)); + } else { + emit(SettingUpBlindDateSuccessfulState()); + } + } + + FutureOr _onGettingIceBreakersEvent( + GettingIceBreakersEvent event, Emitter emit) async { + emit(GettingIceBreakersState()); + IceBreakerMessages? iceBreakerMessages = + await _blindDateUseCases.getIceBreakersUseCase.execute(); + + if (iceBreakerMessages != null) { + emit(GettingIceBreakersSuccessState(iceBreakerMessages)); + } else { + emit(GettingIceBreakersFailedState()); + } + } +} diff --git a/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_event.dart b/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_event.dart new file mode 100644 index 0000000..51c786b --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_event.dart @@ -0,0 +1,22 @@ +part of 'blind_date_bloc.dart'; + +@immutable +abstract class BlindDateEvent {} + +class GetAuthUserEvent extends BlindDateEvent {} + +class CheckIfCanSetupBlindDateEvent extends BlindDateEvent { + final AuthUser authUser; + CheckIfCanSetupBlindDateEvent(this.authUser); +} + +class SetupBlindDateEvent extends BlindDateEvent { + final AuthUser authUser; + final String phoneNumber1; + final String phoneNumber2; + final String iceBreaker; + SetupBlindDateEvent( + this.authUser, this.phoneNumber1, this.phoneNumber2, this.iceBreaker); +} + +class GettingIceBreakersEvent extends BlindDateEvent {} diff --git a/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_state.dart b/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_state.dart new file mode 100644 index 0000000..4bccab4 --- /dev/null +++ b/lib/features/blind_date_setup/presentation/pages/state/setup/blind_date_state.dart @@ -0,0 +1,49 @@ +part of 'blind_date_bloc.dart'; + +@immutable +abstract class BlindDateState {} + +class BlindDateInitial extends BlindDateState {} + +/// AUTH USER +class GettingAuthUserState extends BlindDateState {} + +class GettingAuthUserFailedState extends BlindDateState { + final String errMsg; + GettingAuthUserFailedState(this.errMsg); +} + +class GettingAuthUserSuccessfulState extends BlindDateState { + final AuthUser user; + GettingAuthUserSuccessfulState(this.user); +} + +class CheckingIfCanSetupBlindDateState extends BlindDateState {} + +class SettingUpBlindDateState extends BlindDateState {} + +class CheckingIfCanSetupDateFailedState extends BlindDateState { + final String errMsg; + CheckingIfCanSetupDateFailedState(this.errMsg); +} + +class SettingUpBlindDateFailedState extends BlindDateState { + final String errMsg; + SettingUpBlindDateFailedState(this.errMsg); +} + +class CheckingIfCanSetupDateSuccessfulState extends BlindDateState { + final bool canSetup; + CheckingIfCanSetupDateSuccessfulState(this.canSetup); +} + +class SettingUpBlindDateSuccessfulState extends BlindDateState {} + +class GettingIceBreakersState extends BlindDateState {} + +class GettingIceBreakersFailedState extends BlindDateState {} + +class GettingIceBreakersSuccessState extends BlindDateState { + final IceBreakerMessages iceBreakerMessages; + GettingIceBreakersSuccessState(this.iceBreakerMessages); +} diff --git a/lib/features/chat/data/data_sources/remote/fire_chat.dart b/lib/features/chat/data/data_sources/remote/fire_chat.dart new file mode 100644 index 0000000..bd08b80 --- /dev/null +++ b/lib/features/chat/data/data_sources/remote/fire_chat.dart @@ -0,0 +1,159 @@ +import 'dart:io'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/data/data_sources/remote/remote_chat_source.dart'; +import 'package:redting/features/chat/data/entities/message_entity.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; + +const String chatPhotosBucket = "chats_bucket"; +const String chatRoomPhotosBucket = "chat_room_bucket"; +const String chatsCollection = "chats"; +const String messagesSubCollection = "messages"; + +class FireChat implements RemoteChatSource { + final FirebaseStorage _storage = FirebaseStorage.instance; + final FirebaseFirestore _fireStore = FirebaseFirestore.instance; + //for pagination + final Map> _chatRoomLastDocFetched = {}; + + @override + Future setIdAndSendMessage(Message message) async { + try { + var doc = _fireStore + .collection(chatsCollection) + .doc(message.chatRoomId) + .collection(messagesSubCollection) + .doc(); + message.uid = doc.id; + await doc.set(message.toJson()); + return ServiceResult(); + } catch (e) { + if (kDebugMode) { + print("================ setIdAndSendMessage exc $e ==========="); + } + return ServiceResult(errorOccurred: true); + } + } + + @override + Future uploadPhoto(String userId, File imageFile, + String imageFileName, ImageCompressor imageCompressor) async { + try { + final fileToUpload = + await imageCompressor.compressImageAndGetCompressedOrOg(imageFile); + final storageRef = _storage.ref(); + final photoRef = storageRef.child( + "$chatPhotosBucket/$userId/$chatRoomPhotosBucket/${DateTime.now().millisecondsSinceEpoch.toString()}_$imageFileName"); + await photoRef.putFile(fileToUpload); + String downloadUrl = await photoRef.getDownloadURL(); + return downloadUrl; + } catch (e) { + return null; + } + } + + /// ** listen to messages ***/ + @override + Future> loadOldMessages(String chatRoomId) async { + try { + if (!_chatRoomLastDocFetched.containsKey(chatRoomId)) { + return []; + } + + QuerySnapshot> results = await _fireStore + .collection(chatsCollection) + .doc(chatRoomId) + .collection(messagesSubCollection) + .orderBy(MessageEntity.orderByFieldName, descending: true) + .startAfterDocument(_chatRoomLastDocFetched[chatRoomId]!) + .limit(queryPageResultsSize) + .get(); + + if (results.docs.isNotEmpty) { + _chatRoomLastDocFetched[chatRoomId] = results.docs.last; + return results.docs + .map((e) => MessageEntity.fromJson(e.data())) + .toList(growable: false); + } + return []; + } catch (e) { + if (kDebugMode) { + print("============= load older messages exc $e ========"); + } + return []; + } + } + + @override + resetChatRoomPageTracker(String chatRoomId) { + if (_chatRoomLastDocFetched.containsKey(chatRoomId)) { + _chatRoomLastDocFetched.remove(chatRoomId); + } + } + + @override + Stream> listenToChatStreamBetweenUsers( + String chatRoomId, + ) { + try { + Query> query = _fireStore + .collection(chatsCollection) + .doc(chatRoomId) + .collection(messagesSubCollection) + .orderBy(MessageEntity.orderByFieldName, descending: true) + .limit(queryPageResultsSize); + return query.snapshots().map((event) { + return _mapSnapshotsToRealTimeResultList(chatRoomId, event); + }); + } catch (e) { + if (kDebugMode) { + print("=============== listen to messages $e ========="); + } + return const Stream.empty(); + } + } + + List _mapSnapshotsToRealTimeResultList( + String chatRoomId, QuerySnapshot> event) { + List results = []; + for (var change in event.docChanges) { + MessageEntity msgReceived = MessageEntity.fromJson(change.doc.data()!); + OperationRealTimeResult result; + switch (change.type) { + case DocumentChangeType.added: + //cache the last added for pagination + _chatRoomLastDocFetched[chatRoomId] = change.doc; + result = OperationRealTimeResult( + data: msgReceived, + realTimeEventType: RealTimeEventType.added, + ); + break; + case DocumentChangeType.modified: + result = OperationRealTimeResult( + data: msgReceived, + realTimeEventType: RealTimeEventType.modified, + ); + break; + case DocumentChangeType.removed: + //reset pagination cache + if (_chatRoomLastDocFetched.containsKey(chatRoomId)) { + if (_chatRoomLastDocFetched[chatRoomId] == change.doc) { + _chatRoomLastDocFetched.remove(chatRoomId); + } + } + result = OperationRealTimeResult( + data: msgReceived, + realTimeEventType: RealTimeEventType.deleted, + ); + break; + } + results.add(result); + } + return results; + } +} diff --git a/lib/features/chat/data/data_sources/remote/remote_chat_source.dart b/lib/features/chat/data/data_sources/remote/remote_chat_source.dart new file mode 100644 index 0000000..0ba5e00 --- /dev/null +++ b/lib/features/chat/data/data_sources/remote/remote_chat_source.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; + +abstract class RemoteChatSource { + Future uploadPhoto(String userId, File imageFile, + String imageFileName, ImageCompressor imageCompressor); + Future setIdAndSendMessage(Message message); + Stream> listenToChatStreamBetweenUsers( + String chatRoomId); + Future> loadOldMessages(String chatRoomId); + resetChatRoomPageTracker(String chatRoomId); +} diff --git a/lib/features/chat/data/entities/message_entity.dart b/lib/features/chat/data/entities/message_entity.dart new file mode 100644 index 0000000..3e9ed79 --- /dev/null +++ b/lib/features/chat/data/entities/message_entity.dart @@ -0,0 +1,74 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/core/utils/flutter_fire_date_time_utils.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; + +part 'message_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +@HiveType(typeId: messageTypeId) +class MessageEntity implements Message { + @HiveField(0) + @override + String? imageUrl; + + @HiveField(1) + @override + bool isImage; + + @HiveField(2) + @override + bool isTxt; + + @HiveField(3) + @override + String? message; + + @HiveField(4) + @TimestampConverter() + @override + DateTime sentAt; + + @HiveField(5) + @override + String sentBy; + + @HiveField(6) + @override + String sentTo; + + @HiveField(7) + @override + String uid; + + @HiveField(8) + @override + String chatRoomId; + + static String orderByFieldName = "sentAt"; + + MessageEntity( + {required this.uid, + required this.isImage, + required this.isTxt, + required this.sentBy, + required this.sentTo, + required this.sentAt, + this.message, + this.imageUrl, + required this.chatRoomId}); + + @override + factory MessageEntity.fromJson(Map json) => + _$MessageEntityFromJson(json); + + @override + Map toJson() => _$MessageEntityToJson(this); + + @override + MessageEntity fromJson(Map json) { + return MessageEntity.fromJson(json); + } +} diff --git a/lib/features/chat/data/entities/message_entity.g.dart b/lib/features/chat/data/entities/message_entity.g.dart new file mode 100644 index 0000000..031e4a4 --- /dev/null +++ b/lib/features/chat/data/entities/message_entity.g.dart @@ -0,0 +1,94 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'message_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MessageEntityAdapter extends TypeAdapter { + @override + final int typeId = 7; + + @override + MessageEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MessageEntity( + uid: fields[7] as String, + isImage: fields[1] as bool, + isTxt: fields[2] as bool, + sentBy: fields[5] as String, + sentTo: fields[6] as String, + sentAt: fields[4] as DateTime, + message: fields[3] as String?, + imageUrl: fields[0] as String?, + chatRoomId: fields[8] as String, + ); + } + + @override + void write(BinaryWriter writer, MessageEntity obj) { + writer + ..writeByte(9) + ..writeByte(0) + ..write(obj.imageUrl) + ..writeByte(1) + ..write(obj.isImage) + ..writeByte(2) + ..write(obj.isTxt) + ..writeByte(3) + ..write(obj.message) + ..writeByte(4) + ..write(obj.sentAt) + ..writeByte(5) + ..write(obj.sentBy) + ..writeByte(6) + ..write(obj.sentTo) + ..writeByte(7) + ..write(obj.uid) + ..writeByte(8) + ..write(obj.chatRoomId); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MessageEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MessageEntity _$MessageEntityFromJson(Map json) => MessageEntity( + uid: json['uid'] as String, + isImage: json['isImage'] as bool, + isTxt: json['isTxt'] as bool, + sentBy: json['sentBy'] as String, + sentTo: json['sentTo'] as String, + sentAt: const TimestampConverter().fromJson(json['sentAt'] as Timestamp), + message: Message.decryptMsg(json['message'] as String?), + imageUrl: json['imageUrl'] as String?, + chatRoomId: json['chatRoomId'] as String, + ); + +Map _$MessageEntityToJson(MessageEntity instance) => + { + 'imageUrl': instance.imageUrl, + 'isImage': instance.isImage, + 'isTxt': instance.isTxt, + 'message': instance.message, + 'sentAt': const TimestampConverter().toJson(instance.sentAt), + 'sentBy': instance.sentBy, + 'sentTo': instance.sentTo, + 'uid': instance.uid, + 'chatRoomId': instance.chatRoomId, + }; diff --git a/lib/features/chat/data/repository/chat_repo_impl.dart b/lib/features/chat/data/repository/chat_repo_impl.dart new file mode 100644 index 0000000..33172ec --- /dev/null +++ b/lib/features/chat/data/repository/chat_repo_impl.dart @@ -0,0 +1,100 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/data/data_sources/remote/remote_chat_source.dart'; +import 'package:redting/features/chat/data/entities/message_entity.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/chat/domain/repository/chat_repository.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; +import 'package:redting/res/strings.dart'; + +class ChatRepositoryImpl implements ChatRepository { + final RemoteChatSource remoteSource; + final ImageCompressor imageCompressor; + + ChatRepositoryImpl( + {required this.remoteSource, required this.imageCompressor}); + + @override + Stream> listenToLatestMessagesBetweenUsers( + MatchingMembers thisUser, MatchingMembers thatUser) { + String chatRoomId = Message.getChatRoomId(thisUser.userId, thatUser.userId); + //since we are getting the very first batch + remoteSource.resetChatRoomPageTracker(chatRoomId); + return remoteSource.listenToChatStreamBetweenUsers(chatRoomId); + } + + @override + Future> loadOlderMessages( + MatchingMembers thisUser, MatchingMembers thatUser) { + String chatRoomId = Message.getChatRoomId(thisUser.userId, thatUser.userId); + return remoteSource.loadOldMessages(chatRoomId); + } + + @override + Future sendImageMessage( + {required MatchingMembers thisUser, + required MatchingMembers thatUser, + required File imageFile, + required String imageFileName}) async { + try { + String? downloadUrl = await remoteSource.uploadPhoto( + thisUser.userId, imageFile, imageFileName, imageCompressor); + if (downloadUrl == null) { + //failed + return ServiceResult( + errorOccurred: true, errorMessage: errorSendingImageMessage); + } + + String chatRoomId = + Message.getChatRoomId(thisUser.userId, thatUser.userId); + Message message = MessageEntity( + uid: "", + chatRoomId: chatRoomId, + isTxt: false, + isImage: true, + sentAt: DateTime.now(), + sentBy: thisUser.userId, + sentTo: thatUser.userId, + imageUrl: downloadUrl); + return await remoteSource.setIdAndSendMessage(message); + } catch (e) { + if (kDebugMode) { + print("============ sendImageMessage exc $e ========="); + } + return ServiceResult( + errorOccurred: true, errorMessage: errorSendingImageMessage); + } + } + + @override + Future encryptAndSendTextMessage( + {required MatchingMembers thisUser, + required MatchingMembers thatUser, + required String message}) async { + try { + String chatRoomId = + Message.getChatRoomId(thisUser.userId, thatUser.userId); + + String? encryptedMsg = Message.encryptMsg(message); + Message messageObj = MessageEntity( + uid: "", + chatRoomId: chatRoomId, + isTxt: true, + isImage: false, + sentAt: DateTime.now(), + sentBy: thisUser.userId, + sentTo: thatUser.userId, + message: encryptedMsg!); + return await remoteSource.setIdAndSendMessage(messageObj); + } catch (e) { + if (kDebugMode) { + print("============ sendTextMessage exc $e ========="); + } + return ServiceResult( + errorOccurred: true, errorMessage: errorSendingTxtMessage); + } + } +} diff --git a/lib/features/chat/di/chat_di.dart b/lib/features/chat/di/chat_di.dart new file mode 100644 index 0000000..e779ca3 --- /dev/null +++ b/lib/features/chat/di/chat_di.dart @@ -0,0 +1,42 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/chat/data/data_sources/remote/fire_chat.dart'; +import 'package:redting/features/chat/data/data_sources/remote/remote_chat_source.dart'; +import 'package:redting/features/chat/data/repository/chat_repo_impl.dart'; +import 'package:redting/features/chat/domain/repository/chat_repository.dart'; +import 'package:redting/features/chat/domain/use_cases/chat_use_cases.dart'; +import 'package:redting/features/chat/domain/use_cases/listen_latest_msgs_usecase.dart'; +import 'package:redting/features/chat/domain/use_cases/load_older_msgs_usecase.dart'; +import 'package:redting/features/chat/domain/use_cases/send_photo_usecase.dart'; +import 'package:redting/features/chat/domain/use_cases/send_text_usecase.dart'; +import 'package:redting/features/chat/presentation/pages/state/chat_bloc.dart'; + +GetIt init() { + final GetIt diInstance = GetIt.instance; + + diInstance.registerFactory(() => ChatBloc(diInstance())); + + diInstance.registerLazySingleton(() => ChatUseCases( + listenToChatUseCase: diInstance(), + sendPhotoUseCase: diInstance(), + sendTxtMessageUseCase: diInstance(), + loadOlderMessagesUseCase: diInstance())); + + diInstance.registerLazySingleton( + () => ListenToLatestMessagesUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => SendPhotoUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => SendTxtMessageUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => LoadOlderMessagesUseCase(diInstance())); + + diInstance.registerLazySingleton(() => ChatRepositoryImpl( + remoteSource: diInstance(), imageCompressor: diInstance())); + + diInstance.registerLazySingleton(() => FireChat()); + + return diInstance; +} diff --git a/lib/features/chat/domain/models/message.dart b/lib/features/chat/domain/models/message.dart new file mode 100644 index 0000000..b2de167 --- /dev/null +++ b/lib/features/chat/domain/models/message.dart @@ -0,0 +1,41 @@ +import 'package:redting/features/chat/utils/encrypt_txt_msg.dart'; + +abstract class Message { + bool isImage; + bool isTxt; + String sentBy; + String sentTo; + DateTime sentAt; + String? message; + String? imageUrl; + String uid; + String chatRoomId; + Message( + {required this.uid, + required this.isImage, + required this.isTxt, + required this.sentBy, + required this.sentTo, + required this.sentAt, + this.message, + this.imageUrl, + required this.chatRoomId}); + + Map toJson(); + Message fromJson(Map json); + static String getChatRoomId(String user1, user2) { + String concatUser1User2 = "$user1$user2"; + final concatUser1User2List = concatUser1User2.split(""); + concatUser1User2List.sort((a, b) => a.compareTo(b)); + return concatUser1User2List.join(""); + } + + static String? decryptMsg(String? msg) { + if (msg == null) return null; + return EncryptTxtMessage.decryptTxtMessage(msg); + } + + static String? encryptMsg(String message) { + return EncryptTxtMessage.encryptTxtMessage(message); + } +} diff --git a/lib/features/chat/domain/repository/chat_repository.dart b/lib/features/chat/domain/repository/chat_repository.dart new file mode 100644 index 0000000..cffb85f --- /dev/null +++ b/lib/features/chat/domain/repository/chat_repository.dart @@ -0,0 +1,26 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +abstract class ChatRepository { + Future encryptAndSendTextMessage({ + required MatchingMembers thisUser, + required MatchingMembers thatUser, + required String message, + }); + + Future sendImageMessage({ + required MatchingMembers thisUser, + required MatchingMembers thatUser, + required File imageFile, + required String imageFileName, + }); + + Stream> listenToLatestMessagesBetweenUsers( + MatchingMembers thisUser, MatchingMembers thatUser); + + Future> loadOlderMessages( + MatchingMembers thisUser, MatchingMembers thatUser); +} diff --git a/lib/features/chat/domain/use_cases/chat_use_cases.dart b/lib/features/chat/domain/use_cases/chat_use_cases.dart new file mode 100644 index 0000000..44c1faa --- /dev/null +++ b/lib/features/chat/domain/use_cases/chat_use_cases.dart @@ -0,0 +1,16 @@ +import 'package:redting/features/chat/domain/use_cases/listen_latest_msgs_usecase.dart'; +import 'package:redting/features/chat/domain/use_cases/load_older_msgs_usecase.dart'; +import 'package:redting/features/chat/domain/use_cases/send_photo_usecase.dart'; +import 'package:redting/features/chat/domain/use_cases/send_text_usecase.dart'; + +class ChatUseCases { + final ListenToLatestMessagesUseCase listenToChatUseCase; + final SendPhotoUseCase sendPhotoUseCase; + final SendTxtMessageUseCase sendTxtMessageUseCase; + final LoadOlderMessagesUseCase loadOlderMessagesUseCase; + ChatUseCases( + {required this.listenToChatUseCase, + required this.sendPhotoUseCase, + required this.sendTxtMessageUseCase, + required this.loadOlderMessagesUseCase}); +} diff --git a/lib/features/chat/domain/use_cases/listen_latest_msgs_usecase.dart b/lib/features/chat/domain/use_cases/listen_latest_msgs_usecase.dart new file mode 100644 index 0000000..9181cbc --- /dev/null +++ b/lib/features/chat/domain/use_cases/listen_latest_msgs_usecase.dart @@ -0,0 +1,16 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/repository/chat_repository.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +class ListenToLatestMessagesUseCase { + ChatRepository chatRepository; + ListenToLatestMessagesUseCase(this.chatRepository); + + Stream> execute({ + required MatchingMembers thisUser, + required MatchingMembers thatUser, + }) { + return chatRepository.listenToLatestMessagesBetweenUsers( + thisUser, thatUser); + } +} diff --git a/lib/features/chat/domain/use_cases/load_older_msgs_usecase.dart b/lib/features/chat/domain/use_cases/load_older_msgs_usecase.dart new file mode 100644 index 0000000..9309c78 --- /dev/null +++ b/lib/features/chat/domain/use_cases/load_older_msgs_usecase.dart @@ -0,0 +1,15 @@ +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/chat/domain/repository/chat_repository.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +class LoadOlderMessagesUseCase { + ChatRepository chatRepository; + LoadOlderMessagesUseCase(this.chatRepository); + + Future> execute({ + required MatchingMembers thisUser, + required MatchingMembers thatUser, + }) { + return chatRepository.loadOlderMessages(thisUser, thatUser); + } +} diff --git a/lib/features/chat/domain/use_cases/send_photo_usecase.dart b/lib/features/chat/domain/use_cases/send_photo_usecase.dart new file mode 100644 index 0000000..153e496 --- /dev/null +++ b/lib/features/chat/domain/use_cases/send_photo_usecase.dart @@ -0,0 +1,23 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/repository/chat_repository.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +class SendPhotoUseCase { + final ChatRepository chatRepository; + SendPhotoUseCase(this.chatRepository); + + Future execute({ + required MatchingMembers thisUser, + required MatchingMembers thatUser, + required File imageFile, + required String imageFileName, + }) async { + return await chatRepository.sendImageMessage( + thisUser: thisUser, + thatUser: thatUser, + imageFile: imageFile, + imageFileName: imageFileName); + } +} diff --git a/lib/features/chat/domain/use_cases/send_text_usecase.dart b/lib/features/chat/domain/use_cases/send_text_usecase.dart new file mode 100644 index 0000000..1df0e65 --- /dev/null +++ b/lib/features/chat/domain/use_cases/send_text_usecase.dart @@ -0,0 +1,17 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/repository/chat_repository.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +class SendTxtMessageUseCase { + final ChatRepository chatRepository; + SendTxtMessageUseCase(this.chatRepository); + + Future execute({ + required MatchingMembers thisUser, + required MatchingMembers thatUser, + required String message, + }) async { + return await chatRepository.encryptAndSendTextMessage( + thisUser: thisUser, thatUser: thatUser, message: message); + } +} diff --git a/lib/features/chat/presentation/components/build_msg_widget_fun.dart b/lib/features/chat/presentation/components/build_msg_widget_fun.dart new file mode 100644 index 0000000..9182562 --- /dev/null +++ b/lib/features/chat/presentation/components/build_msg_widget_fun.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/chat/presentation/components/received_image.dart'; +import 'package:redting/features/chat/presentation/components/received_message.dart'; +import 'package:redting/features/chat/presentation/components/sent_image.dart'; +import 'package:redting/features/chat/presentation/components/sent_message.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_uneditable.dart'; + +Widget buildMessageWidget( + Message msg, MatchingMembers thisUser, MatchingMembers thatUser) { + bool isSent = msg.sentBy == thisUser.userId; + String profilePhoto = + isSent ? thisUser.userProfilePhoto : thatUser.userProfilePhoto; + + Widget photoWidget = + UneditableProfilePhoto(useRadius: 12, profilePhoto: profilePhoto); + + if (msg.isTxt) { + if (isSent) { + return SentMessage( + key: ValueKey(msg.uid), + msg.message ?? '', + profileWidget: photoWidget); + } + return ReceivedMessage( + key: ValueKey(msg.uid), msg.message ?? '', profileWidget: photoWidget); + } + + if (msg.isImage) { + if (isSent) { + return SentImage( + key: ValueKey(msg.uid), + photoUrl: msg.imageUrl ?? '', + profileWidget: photoWidget); + } + return ReceivedImage( + key: ValueKey(msg.uid), + photoUrl: msg.imageUrl ?? '', + profileWidget: photoWidget); + } + + return const SizedBox.shrink(); //shouldn't get here +} diff --git a/lib/features/chat/presentation/components/chat_app_bar.dart b/lib/features/chat/presentation/components/chat_app_bar.dart new file mode 100644 index 0000000..faeb568 --- /dev/null +++ b/lib/features/chat/presentation/components/chat_app_bar.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_uneditable.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; + +chatAppBar({required MatchingMembers chattingWithUser}) { + return AppBar( + toolbarHeight: 100, + centerTitle: true, + elevation: paddingSm, + shadowColor: Colors.black38, + title: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + height: paddingStd, + ), + Container( + constraints: const BoxConstraints( + maxHeight: avatarRadiusSmallest * 2.5, + maxWidth: avatarRadiusSmallest * 2.5), + child: Center( + child: UneditableProfilePhoto( + isSmallest: true, + profilePhoto: chattingWithUser.userProfilePhoto))), + const SizedBox( + height: paddingSm, + ), + Text( + chattingWithUser.userName, + style: appTextTheme.bodyText2?.copyWith(color: Colors.black87), + ), + const SizedBox( + height: paddingStd, + ), + ], + ), + ); +} diff --git a/lib/features/chat/presentation/components/ice_breaker_msg.dart b/lib/features/chat/presentation/components/ice_breaker_msg.dart new file mode 100644 index 0000000..c02e151 --- /dev/null +++ b/lib/features/chat/presentation/components/ice_breaker_msg.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class IceBreakerMsg extends StatelessWidget { + final String iceBreaker; + const IceBreakerMsg({Key? key, required this.iceBreaker}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), + margin: const EdgeInsets.symmetric( + horizontal: paddingStd, vertical: paddingMd), + child: Stack( + children: [ + Center( + child: Container( + margin: const EdgeInsets.only(top: 16), + child: Material( + shadowColor: appTheme.colorScheme.primary, + borderRadius: BorderRadius.circular(12), + color: Colors.white, + elevation: paddingStd, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingStd), + child: Text( + iceBreaker, + style: + appTextTheme.bodyText2?.copyWith(color: Colors.black), + ), + ), + ), + ), + ), + Align( + alignment: Alignment.center, + child: Icon( + Icons.light_mode, + color: appTheme.colorScheme.primary, + size: 32, + ), + ) + ], + ), + ); + } +} diff --git a/lib/features/chat/presentation/components/received_image.dart b/lib/features/chat/presentation/components/received_image.dart new file mode 100644 index 0000000..f21cc9a --- /dev/null +++ b/lib/features/chat/presentation/components/received_image.dart @@ -0,0 +1,56 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/theme.dart'; + +class ReceivedImage extends StatelessWidget { + final String photoUrl; + final Widget profileWidget; + const ReceivedImage({ + Key? key, + required this.photoUrl, + required this.profileWidget, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox( + width: paddingSm, + ), + profileWidget, + Container( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 200), + margin: const EdgeInsets.only(top: paddingMd), + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + bottomRight: Radius.circular(12), + topRight: Radius.circular(12))), + child: CachedNetworkImage( + imageUrl: photoUrl, + placeholder: (_, __) { + return const Center(child: CircularProgress()); + }, + errorWidget: (___, __, _) => SizedBox( + width: 40, + height: 40, + child: Center( + child: Icon(Icons.error_outline, + color: appTheme.colorScheme.primary), + ), + ), + fit: BoxFit.cover, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/chat/presentation/components/received_message.dart b/lib/features/chat/presentation/components/received_message.dart new file mode 100644 index 0000000..2834e58 --- /dev/null +++ b/lib/features/chat/presentation/components/received_message.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class ReceivedMessage extends StatelessWidget { + final String message; + final Widget profileWidget; + const ReceivedMessage(this.message, {Key? key, required this.profileWidget}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + profileWidget, + const SizedBox( + width: paddingSm, + ), + Container( + constraints: + BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 2), + margin: const EdgeInsets.only(top: paddingMd), + decoration: BoxDecoration( + color: appTheme.colorScheme.primary, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(12), + bottomRight: Radius.circular(12), + topLeft: Radius.circular(12))), + child: Padding( + padding: const EdgeInsets.all(paddingStd), + child: Text( + message, + style: appTextTheme.bodyText1 + ?.copyWith(color: appTheme.colorScheme.onPrimary), + textAlign: TextAlign.left, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/chat/presentation/components/send_message.dart b/lib/features/chat/presentation/components/send_message.dart new file mode 100644 index 0000000..35a67ca --- /dev/null +++ b/lib/features/chat/presentation/components/send_message.dart @@ -0,0 +1,101 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class SendMessage extends StatelessWidget { + final bool isSendingMessage; + final VoidCallback onSendTxtMessage; + final TextEditingController txtMsgController; + final Function({required String fileName, required File imageFile}) + onSendImageMessage; + const SendMessage( + {Key? key, + required this.isSendingMessage, + required this.onSendTxtMessage, + required this.txtMsgController, + required this.onSendImageMessage}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingStd), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: TextField( + controller: txtMsgController, + maxLines: 6, + minLines: 1, + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.done, + style: appTextTheme.bodyText1?.copyWith(color: Colors.black), + decoration: const InputDecoration( + isDense: true, border: InputBorder.none), + )), + Visibility( + visible: !isSendingMessage, + child: Container( + margin: const EdgeInsets.only(left: paddingSm), + child: IconButton( + onPressed: _sendMessage, + icon: Icon( + Icons.send, + size: 32, + color: appTheme.colorScheme.primary, + )), + )), + Visibility( + visible: !isSendingMessage, + child: Container( + margin: const EdgeInsets.only(left: paddingSm), + child: IconButton( + onPressed: () { + _sendImageMessage(); + }, + icon: const Icon( + Icons.photo_camera, + size: 32, + color: Colors.black54, + )), + ), + ), + Visibility( + visible: isSendingMessage, + child: Container( + margin: const EdgeInsets.only(bottom: paddingSm), + child: const CircularProgress( + makeSmaller: true, + )), + ) + ], + ), + ), + ); + } + + void _sendMessage() { + if (isSendingMessage) return; + FocusManager.instance.primaryFocus?.unfocus(); + onSendTxtMessage(); + } + + void _sendImageMessage() async { + if (isSendingMessage) return; + ImagePicker _picker = ImagePicker(); + XFile? file = await _picker.pickImage(source: ImageSource.gallery); + if (file != null) { + onSendImageMessage(fileName: file.name, imageFile: File(file.path)); + } + } +} diff --git a/lib/features/chat/presentation/components/sent_image.dart b/lib/features/chat/presentation/components/sent_image.dart new file mode 100644 index 0000000..482a1b4 --- /dev/null +++ b/lib/features/chat/presentation/components/sent_image.dart @@ -0,0 +1,56 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/theme.dart'; + +class SentImage extends StatelessWidget { + final String photoUrl; + final Widget profileWidget; + const SentImage({ + Key? key, + required this.photoUrl, + required this.profileWidget, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 200), + margin: const EdgeInsets.only(top: paddingMd), + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + topRight: Radius.circular(12))), + child: CachedNetworkImage( + imageUrl: photoUrl, + placeholder: (_, __) { + return const Center(child: CircularProgress()); + }, + errorWidget: (___, __, _) => SizedBox( + width: 40, + height: 40, + child: Center( + child: Icon(Icons.error_outline, + color: appTheme.colorScheme.primary), + ), + ), + fit: BoxFit.cover, + ), + ), + const SizedBox( + width: paddingSm, + ), + profileWidget + ], + ), + ); + } +} diff --git a/lib/features/chat/presentation/components/sent_message.dart b/lib/features/chat/presentation/components/sent_message.dart new file mode 100644 index 0000000..f0efe14 --- /dev/null +++ b/lib/features/chat/presentation/components/sent_message.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class SentMessage extends StatelessWidget { + final String message; + final Widget profileWidget; + const SentMessage(this.message, {Key? key, required this.profileWidget}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + constraints: + BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 2), + margin: const EdgeInsets.only(top: paddingMd), + decoration: BoxDecoration( + color: appTheme.colorScheme.inversePrimary, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + topRight: Radius.circular(12))), + child: Padding( + padding: const EdgeInsets.all(paddingStd), + child: Text( + message, + style: appTextTheme.bodyText1?.copyWith(color: Colors.black), + textAlign: TextAlign.right, + ), + ), + ), + const SizedBox( + width: paddingSm, + ), + profileWidget + ], + ), + ); + } +} diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart new file mode 100644 index 0000000..2fe8454 --- /dev/null +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -0,0 +1,286 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/chat/presentation/components/build_msg_widget_fun.dart'; +import 'package:redting/features/chat/presentation/components/chat_app_bar.dart'; +import 'package:redting/features/chat/presentation/components/ice_breaker_msg.dart'; +import 'package:redting/features/chat/presentation/components/send_message.dart'; +import 'package:redting/features/chat/presentation/pages/state/chat_bloc.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/res/dimens.dart'; + +class ChatScreen extends StatefulWidget { + final MatchingMembers thisUser; + final MatchingMembers thatUser; + final String iceBreaker; + const ChatScreen( + {Key? key, + required this.thisUser, + required this.thatUser, + required this.iceBreaker}) + : super(key: key); + + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + bool _isSendingMessage = false; + ChatBloc? _eventDispatcher; + bool _hasFetchedAllMessages = false; + bool _isLoadingMessages = false; + + late TextEditingController _txtMsgController; + Stream>? _stream; + late ScrollController _messagesScrollController; + Map _messages = {}; + DateTime _mostRecentMsgDate = DateTime(2021); //init with an old year + + @override + void initState() { + _txtMsgController = TextEditingController(); + _messagesScrollController = ScrollController(); + _addMsgScrollListener(); + super.initState(); + } + + @override + Widget build(BuildContext cxt) { + final Size size = MediaQuery.of(cxt).size; + double screenWidth = size.width; + double screenHeight = size.height; + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: + BlocBuilder(builder: (blocContext, state) { + if (state is ChatInitialState) { + _onInitState(blocContext); + } + return ScaffoldWrapper( + child: Scaffold( + appBar: chatAppBar(chattingWithUser: widget.thatUser), + body: StreamBuilder>( + stream: _stream, + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data != null) { + _respondToDataChanges(snapshot.data!); + } + return Container( + color: Colors.grey.withOpacity(0.1), + constraints: BoxConstraints( + minHeight: screenHeight, minWidth: screenWidth), + child: Padding( + padding: const EdgeInsets.all(paddingStd), + child: Stack( + children: [ + _buildMessagesContainer(), + Align( + alignment: Alignment.bottomCenter, + child: SendMessage( + txtMsgController: _txtMsgController, + isSendingMessage: _isSendingMessage, + onSendTxtMessage: _onSendTxtMessage, + onSendImageMessage: _onSendImgMessage, + ), + ) + ], + ), + ), + ); + }), + )); + }))); + } + + /// INIT + void _onInitState(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(ListenToChatEvent( + thisUser: widget.thisUser, thatUser: widget.thatUser)); + _isLoadingMessages = true; + } + + /// EVENTS + _onSendTxtMessage() { + if (_isSendingMessage) return; + String text = _txtMsgController.text; + if (text.isEmpty) return; + _eventDispatcher?.add(SendMessageEvent( + message: text, thisUser: widget.thisUser, thatUser: widget.thatUser)); + } + + _onSendImgMessage({required String fileName, required File imageFile}) { + if (_isSendingMessage) return; + _eventDispatcher?.add(SendMessageEvent( + thisUser: widget.thisUser, + thatUser: widget.thatUser, + imageFileName: fileName, + imageFile: imageFile)); + } + + /// LISTEN TO STATE + void _listenToStateChange(BuildContext context, ChatState state) { + if (state is SendingMessageFailedState) { + if (!mounted) return; + setState(() { + _isSendingMessage = false; + }); + _showSnack(state.errMsg); + } + + if (state is SendingMessageState) { + if (!mounted) return; + setState(() { + _isSendingMessage = true; + }); + } + + if (state is SendingMessageSuccessState) { + if (!mounted) return; + setState(() { + _isSendingMessage = false; + }); + _txtMsgController.clear(); + } + + if (state is ListeningToChatState) { + if (!mounted) return; + setState(() { + _stream = state.stream; + _isLoadingMessages = false; + }); + } + + /// loading old messages + if (state is ChatLoadingState) { + if (!mounted) return; + setState(() { + _isLoadingMessages = true; + }); + } + + if (state is LoadedOlderMessagesState) { + for (Message message in state.messages) { + _messages[message.uid] = message; + } + if (!mounted) return; + setState(() { + _isLoadingMessages = false; + _hasFetchedAllMessages = state.messages.isEmpty; + _messages = _messages; + }); + } + } + + /// INCOMING MESSAGES UPDATES + void _respondToDataChanges(List resultsList) { + for (var realTimeResult in resultsList) { + Message msg = realTimeResult.data as Message; + switch (realTimeResult.realTimeEventType) { + case RealTimeEventType.added: + if (msg.sentAt.isAfter(_mostRecentMsgDate)) { + //new message - must add to back (since we are reversing the listview) + Map newMessages = {}; + newMessages[msg.uid] = msg; + newMessages.addAll(_messages); + _messages = newMessages; + + //update most recent + _mostRecentMsgDate = msg.sentAt; + + //scroll to bottom + _scrollToBottomOfMessages(); + } else { + _messages[msg.uid] = msg; + } + break; + + case RealTimeEventType.modified: + if (_messages.containsKey(msg.uid)) { + _messages[msg.uid] = msg; + } + break; + + case RealTimeEventType.deleted: + if (_messages.containsKey(msg.uid)) { + _messages.remove(msg.uid); + } + break; + } + } + } + + void _scrollToBottomOfMessages() { + _messagesScrollController.animateTo( + _messagesScrollController.position.minScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut); + } + + void _addMsgScrollListener() { + _messagesScrollController.addListener(() { + if (_messagesScrollController.offset >= + _messagesScrollController.position.maxScrollExtent && + !_messagesScrollController.position.outOfRange) { + if (!_hasFetchedAllMessages && !_isLoadingMessages) { + _eventDispatcher?.add(LoadMoreMessagesEvent( + thisUser: widget.thisUser, thatUser: widget.thatUser)); + } + } + }); + } + + /// DISPOSE + @override + void dispose() { + _txtMsgController.dispose(); + _messagesScrollController.dispose(); + super.dispose(); + } + + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + _buildMessagesContainer() { + return Column( + children: [ + IceBreakerMsg(iceBreaker: widget.iceBreaker), + if (_isLoadingMessages) + Container( + margin: const EdgeInsets.symmetric(vertical: paddingStd), + child: const Center(child: CircularProgress())), + Expanded( + child: ListView.builder( + physics: const BouncingScrollPhysics(), + controller: _messagesScrollController, + reverse: true, + itemCount: _messages.values.length, + itemBuilder: (BuildContext context, int index) { + Message msg = _messages.values.toList()[index]; + return buildMessageWidget(msg, widget.thisUser, widget.thatUser); + }, + )), + const SizedBox( + height: 64, + ) + ], + ); + } +} diff --git a/lib/features/chat/presentation/pages/state/chat_bloc.dart b/lib/features/chat/presentation/pages/state/chat_bloc.dart new file mode 100644 index 0000000..8009045 --- /dev/null +++ b/lib/features/chat/presentation/pages/state/chat_bloc.dart @@ -0,0 +1,72 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/chat/domain/models/message.dart'; +import 'package:redting/features/chat/domain/use_cases/chat_use_cases.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/res/strings.dart'; + +part 'chat_event.dart'; +part 'chat_state.dart'; + +class ChatBloc extends Bloc { + final ChatUseCases chatUseCases; + ChatBloc(this.chatUseCases) : super(ChatInitialState()) { + on(_onSendMessageEvent); + on(_onListenToChatEvent); + on(_onLoadMoreMessagesEvent); + } + + FutureOr _onSendMessageEvent( + SendMessageEvent event, Emitter emit) async { + ServiceResult? result; + if (event.message != null) { + emit(SendingMessageState()); + result = await chatUseCases.sendTxtMessageUseCase.execute( + thisUser: event.thisUser, + thatUser: event.thatUser, + message: event.message!); + } + + if (event.imageFile != null && event.imageFileName != null) { + emit(SendingMessageState()); + result = await chatUseCases.sendPhotoUseCase.execute( + thisUser: event.thisUser, + thatUser: event.thatUser, + imageFile: event.imageFile!, + imageFileName: event.imageFileName!); + } + + if (result == null) { + //should not happen + return; + } + + if (result.errorOccurred) { + String defaultErr = event.imageFile != null + ? errorSendingImageMessage + : errorSendingTxtMessage; + emit(SendingMessageFailedState(result.errorMessage ?? defaultErr)); + } else { + emit(SendingMessageSuccessState()); + } + } + + FutureOr _onListenToChatEvent( + ListenToChatEvent event, Emitter emit) { + final stream = chatUseCases.listenToChatUseCase + .execute(thisUser: event.thisUser, thatUser: event.thatUser); + emit(ListeningToChatState(stream)); + } + + FutureOr _onLoadMoreMessagesEvent( + LoadMoreMessagesEvent event, Emitter emit) async { + emit(ChatLoadingState()); + List messages = await chatUseCases.loadOlderMessagesUseCase + .execute(thisUser: event.thisUser, thatUser: event.thatUser); + emit(LoadedOlderMessagesState(messages)); + } +} diff --git a/lib/features/chat/presentation/pages/state/chat_event.dart b/lib/features/chat/presentation/pages/state/chat_event.dart new file mode 100644 index 0000000..021fd6b --- /dev/null +++ b/lib/features/chat/presentation/pages/state/chat_event.dart @@ -0,0 +1,36 @@ +part of 'chat_bloc.dart'; + +@immutable +abstract class ChatEvent {} + +class ListenToChatEvent extends ChatEvent { + final MatchingMembers thisUser; + final MatchingMembers thatUser; + ListenToChatEvent({ + required this.thisUser, + required this.thatUser, + }); +} + +class LoadMoreMessagesEvent extends ChatEvent { + final MatchingMembers thisUser; + final MatchingMembers thatUser; + LoadMoreMessagesEvent({ + required this.thisUser, + required this.thatUser, + }); +} + +class SendMessageEvent extends ChatEvent { + final String? message; + final File? imageFile; + final String? imageFileName; + final MatchingMembers thisUser; + final MatchingMembers thatUser; + SendMessageEvent( + {required this.thisUser, + required this.thatUser, + this.message, + this.imageFile, + this.imageFileName}); +} diff --git a/lib/features/chat/presentation/pages/state/chat_state.dart b/lib/features/chat/presentation/pages/state/chat_state.dart new file mode 100644 index 0000000..8001ff2 --- /dev/null +++ b/lib/features/chat/presentation/pages/state/chat_state.dart @@ -0,0 +1,27 @@ +part of 'chat_bloc.dart'; + +@immutable +abstract class ChatState {} + +class ChatInitialState extends ChatState {} + +class ChatLoadingState extends ChatState {} + +class ListeningToChatState extends ChatState { + final Stream> stream; + ListeningToChatState(this.stream); +} + +class LoadedOlderMessagesState extends ChatState { + final List messages; + LoadedOlderMessagesState(this.messages); +} + +class SendingMessageState extends ChatState {} + +class SendingMessageFailedState extends ChatState { + final String errMsg; + SendingMessageFailedState(this.errMsg); +} + +class SendingMessageSuccessState extends ChatState {} diff --git a/lib/features/chat/utils/encrypt_txt_msg.dart b/lib/features/chat/utils/encrypt_txt_msg.dart new file mode 100644 index 0000000..ae54dbe --- /dev/null +++ b/lib/features/chat/utils/encrypt_txt_msg.dart @@ -0,0 +1,37 @@ +import 'package:encrypt/encrypt.dart'; +import 'package:flutter/foundation.dart' hide Key; + +class EncryptTxtMessage { + static String? encryptTxtMessage(String message) { + try { + final useIv = IV.fromLength(16); + final useKey = Key.fromLength(32); + + final encryptor = Encrypter(AES(useKey)); + final encrypted = encryptor.encrypt(message, iv: useIv); + return encrypted.base64; + } catch (e) { + if (kDebugMode) { + print("============ encryptTxtMessage exc $e ========="); + } + return null; + } + } + + static String? decryptTxtMessage(String encryptedMessage) { + try { + final useIv = IV.fromLength(16); + final useKey = Key.fromLength(32); + + final encryptor = Encrypter(AES(useKey)); + final decrypted = + encryptor.decrypt(Encrypted.fromBase64(encryptedMessage), iv: useIv); + return decrypted; + } catch (e) { + if (kDebugMode) { + print("============ encryptTxtMessage exc $e ========="); + } + return null; + } + } +} diff --git a/lib/features/home/presentation/components/build_app_bar.dart b/lib/features/home/presentation/components/build_app_bar.dart new file mode 100644 index 0000000..9924e1d --- /dev/null +++ b/lib/features/home/presentation/components/build_app_bar.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/assets_paths.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +PreferredSizeWidget buildAppBar( + {required VoidCallback onSettingsClicked, + required VoidCallback onSetupBlindDateClicked}) { + return AppBar( + toolbarHeight: appBarHeight, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + redLogoPath, + height: appBarHeight - (paddingStd * 2), + fit: BoxFit.fitHeight, + ), + ]), + actions: [ + IconButton( + iconSize: 32, + onPressed: onSetupBlindDateClicked, + icon: Icon( + Icons.group_add_outlined, + size: 32, + semanticLabel: blindDateSetupSemanticLbl, + color: appTheme.colorScheme.primary, + )), + IconButton( + iconSize: 32, + onPressed: onSettingsClicked, + icon: Icon( + Icons.settings, + size: 32, + semanticLabel: editDatingPreferencesSemanticLbl, + color: appTheme.colorScheme.primary, + )) + ], + ); +} diff --git a/lib/features/home/presentation/pages/home_screen.dart b/lib/features/home/presentation/pages/home_screen.dart new file mode 100644 index 0000000..4e47498 --- /dev/null +++ b/lib/features/home/presentation/pages/home_screen.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/screens/gradient_screen_container.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/features/blind_date_setup/presentation/pages/blind_date_setup_screen.dart'; +import 'package:redting/features/blind_date_setup/presentation/pages/blind_dates_screen.dart'; +import 'package:redting/features/home/presentation/components/build_app_bar.dart'; +import 'package:redting/features/matching/presentation/pages/matched_screen.dart'; +import 'package:redting/features/matching/presentation/pages/matching_screen.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/presentation/pages/edit_dating_info_screen.dart'; +import 'package:redting/features/profile/presentation/pages/view_profile_destination.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({Key? key}) : super(key: key); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + //destinations + HomeDestinations _homeDestinations = HomeDestinations.matching; + late List _destinationScreens; + late PageController _destinationPagesController; + late UserProfile _userProfile; + + @override + void initState() { + _destinationPagesController = + PageController(initialPage: _getSelectedIndex()); + super.initState(); + } + + @override + void dispose() { + _destinationPagesController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + RouteSettings? settings = ModalRoute.of(context)?.settings; + if (settings != null && settings.arguments is UserProfile) { + _userProfile = settings.arguments as UserProfile; + } + return ScaffoldWrapper( + child: Scaffold( + extendBodyBehindAppBar: false, + appBar: buildAppBar(onSettingsClicked: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditDatingInfoScreen( + userProfile: _userProfile, + ), + ), + ); + }, onSetupBlindDateClicked: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlindDateSetupScreen( + userProfile: _userProfile, + ), + ), + ); + }), + body: PageView( + controller: _destinationPagesController, + physics: const NeverScrollableScrollPhysics(), + children: _getDestinationPages(), + ), + bottomNavigationBar: NavigationBarTheme( + data: _getBottomNavThemeData(), + child: NavigationBar( + animationDuration: const Duration(seconds: 2), + selectedIndex: _getSelectedIndex(), + destinations: _getDestinations(), + onDestinationSelected: _switchDestination, + ), + ), + )); + } + + List _getDestinations() { + return [ + const NavigationDestination( + icon: Icon(Icons.local_fire_department_outlined), + label: homeDestinationLbl), + const NavigationDestination( + icon: Icon(Icons.local_fire_department_rounded), + label: myMatchesDestinationLbl), + const NavigationDestination( + icon: Icon(Icons.group_sharp), label: blindDatesScreenNavTitle), + const NavigationDestination( + icon: Icon(Icons.account_circle_outlined), + label: myProfileDestinationLbl), + ]; + } + + NavigationBarThemeData _getBottomNavThemeData() { + return NavigationBarThemeData( + iconTheme: MaterialStateProperty.resolveWith((states) { + return (states.contains(MaterialState.selected)) + ? IconThemeData(color: appTheme.colorScheme.primary, size: 32) + : const IconThemeData(color: Colors.black, size: 24); + }), + labelTextStyle: MaterialStateProperty.resolveWith((states) => (states + .contains(MaterialState.selected)) + ? appTextTheme.button?.copyWith(color: appTheme.colorScheme.primary) + : appTextTheme.button?.copyWith(color: Colors.black54)), + elevation: paddingStd, + labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected); + } + + _getSelectedIndex() { + switch (_homeDestinations) { + case HomeDestinations.matching: + return 0; + case HomeDestinations.matched: + return 1; + case HomeDestinations.blindDates: + return 2; + case HomeDestinations.profile: + return 3; + } + } + + void _switchDestination(int index) { + if (index == 0) { + setState(() { + _homeDestinations = HomeDestinations.matching; + _destinationPagesController.jumpToPage(_getSelectedIndex()); + }); + } + + if (index == 1) { + setState(() { + _homeDestinations = HomeDestinations.matched; + _destinationPagesController.jumpToPage(_getSelectedIndex()); + }); + } + + if (index == 2) { + setState(() { + _homeDestinations = HomeDestinations.blindDates; + _destinationPagesController.jumpToPage(_getSelectedIndex()); + }); + } + + if (index == 3) { + setState(() { + _homeDestinations = HomeDestinations.profile; + _destinationPagesController.jumpToPage(_getSelectedIndex()); + }); + } + } + + List _getDestinationPages() { + _destinationScreens = [ + MatchingScreen(profile: _userProfile), + MatchedScreen(profile: _userProfile), + const BlindDatesScreen(), + ViewProfileScreen(profile: _userProfile), + ]; + return _destinationScreens + .map((pageScreen) => GradientScreenContainer(screen: pageScreen)) + .toList(growable: false); + } +} + +enum HomeDestinations { matching, matched, blindDates, profile } diff --git a/lib/features/matching/data/data_sources/local/hive_matching_data_source.dart b/lib/features/matching/data/data_sources/local/hive_matching_data_source.dart new file mode 100644 index 0000000..8b7e037 --- /dev/null +++ b/lib/features/matching/data/data_sources/local/hive_matching_data_source.dart @@ -0,0 +1,48 @@ +import 'package:flutter/foundation.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_names.dart'; +import 'package:redting/features/matching/data/data_sources/local/local_matching_data_source.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; + +class HiveMatchingDataSource implements LocalMatchingDataSource { + final Box _iceBreakersHiveBox = Hive.box(iceBreakersBox); + final Box _likedUsersBox = Hive.box?>(likedUsersBox); + + @override + Future cacheIceBreakersAndGet( + IceBreakerMessages icebreakers) async { + try { + await _iceBreakersHiveBox.put(iceBreakersKey, icebreakers); + return icebreakers; + } catch (e) { + if (kDebugMode) { + print("============== cacheIceBreakersAndGet local - $e ============"); + } + return null; + } + } + + @override + Future getIceBreakerMessages() async { + try { + return _iceBreakersHiveBox.get(iceBreakersKey, defaultValue: null); + } catch (e) { + if (kDebugMode) { + print("========== getIceBreakerMessages $e =========="); + } + return null; + } + } + + @override + Future cacheLikedUser(String likedUserId) async { + Map likedUsersCache = getLikedUsersCache(); + likedUsersCache[likedUserId] = true; + await _likedUsersBox.put(likedUsersBoxKey, likedUsersCache); + } + + @override + Map getLikedUsersCache() { + return _likedUsersBox.get(likedUsersBoxKey, defaultValue: {}); + } +} diff --git a/lib/features/matching/data/data_sources/local/local_matching_data_source.dart b/lib/features/matching/data/data_sources/local/local_matching_data_source.dart new file mode 100644 index 0000000..a8630fe --- /dev/null +++ b/lib/features/matching/data/data_sources/local/local_matching_data_source.dart @@ -0,0 +1,9 @@ +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; + +abstract class LocalMatchingDataSource { + Future getIceBreakerMessages(); + Future cacheIceBreakersAndGet( + IceBreakerMessages icebreakers); + Future cacheLikedUser(String likedUserId); + Map getLikedUsersCache(); +} diff --git a/lib/features/matching/data/data_sources/remote/fire_matching_data_source.dart b/lib/features/matching/data/data_sources/remote/fire_matching_data_source.dart new file mode 100644 index 0000000..4c921b3 --- /dev/null +++ b/lib/features/matching/data/data_sources/remote/fire_matching_data_source.dart @@ -0,0 +1,407 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/data/data_sources/remote/remote_matching_data_source.dart'; +import 'package:redting/features/matching/data/entities/ice_breaker_messages_entity.dart'; +import 'package:redting/features/matching/data/entities/liked_user_entity.dart'; +import 'package:redting/features/matching/data/entities/matching_profiles_entity.dart'; +import 'package:redting/features/matching/domain/models/daily_user_feedback.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/features/matching/domain/models/liked_user.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/profile/data/data_sources/remote/fire_profile.dart'; +import 'package:redting/features/profile/data/entities/user_profile_entity.dart'; +import 'package:redting/features/profile/data/utils/enum_mappers.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/res/strings.dart'; + +const String iceBreakersCollection = "ice_breakers"; +const String iceBreakersDoc = "ice_breakers_doc"; +const String likedUsersCollection = "liked_users"; +const String usersILikeCollection = "users_i_like"; +const String matchesCollection = "matches"; +const String dailyUserFeedbackCollection = "daily_user_feedback"; + +class FireMatchingDataSource implements RemoteMatchingDataSource { + final FirebaseAuth _auth = FirebaseAuth.instance; + final FirebaseFirestore _fireStore = FirebaseFirestore.instance; + DocumentSnapshot? _lastDocInFetchedProfilesToMatchWith; + DocumentSnapshot? _startMatchesAfterDoc; + + @override + Future getIceBreakerMessages() async { + try { + var doc = await _fireStore + .collection(iceBreakersCollection) + .doc(iceBreakersDoc) + .get(); + IceBreakerMessages? icyBreakers; + if (doc.data() != null) { + icyBreakers = IceBreakerMessagesEntity.fromJson(doc.data()!); + } + return icyBreakers; + } catch (e) { + if (kDebugMode) { + print("=============== getIceBreakerMessages() exc $e =========="); + } + return null; + } + } + + /// like user + //TODO if true, then this is an instant match + Future _isAMatch(MatchingProfiles matchingProfiles) async { + try { + var doc = await _fireStore + .collection(matchesCollection) + .doc(matchingProfiles.userAUserBIdsConcatNSorted) + .get(); + return doc.exists; + } catch (e) { + if (kDebugMode) { + print("=============== isAMatch() exc $e =========="); + } + return null; + } + } + + Future _updateToMatchProfiles(MatchingProfiles matchingProfiles) async { + try { + await _fireStore + .collection(matchesCollection) + .doc(matchingProfiles.userAUserBIdsConcatNSorted) + .update({ + MatchingProfilesEntity.haveMatchedFieldName: true, + MatchingProfilesEntity.iceBreakersFieldName: + FieldValue.arrayUnion(matchingProfiles.iceBreakers), + MatchingProfilesEntity.likersFieldName: + FieldValue.arrayUnion(matchingProfiles.likers), + MatchingProfilesEntity.membersFieldName: FieldValue.arrayUnion( + matchingProfiles + .getMembers() + .map((e) => e.toJson()) + .toList(growable: false)) + }); + return true; + } catch (e) { + if (kDebugMode) { + print("=============== updateToMatchProfiles exc $e =========="); + } + return false; + } + } + + Future _addToMatchProfiles(MatchingProfiles matchingProfiles) async { + try { + await _fireStore + .collection(matchesCollection) + .doc(matchingProfiles.userAUserBIdsConcatNSorted) + .set(matchingProfiles.toJson()); + return true; + } catch (e) { + if (kDebugMode) { + print("=============== addToMatchProfiles exc $e =========="); + } + return false; + } + } + + @override + Future getUsersILike() async { + try { + var doc = await _fireStore + .collection(likedUsersCollection) + .doc(_auth.currentUser!.uid) + .collection(usersILikeCollection) + .get(); + List found = doc.docs + .map((e) => LikedUserEntity.fromJson(e.data())) + .toList(growable: false); + return ServiceResult(data: found); + } catch (e) { + if (kDebugMode) { + print("=============== getUsersILike exc $e =========="); + } + return ServiceResult(errorOccurred: true); + } + } + + @override + Future likeUser( + LikedUser likedUser, MatchingProfiles matchingProfiles) async { + try { + await _fireStore + .collection(likedUsersCollection) + .doc(_auth.currentUser!.uid) + .collection(usersILikeCollection) + .doc(likedUser.likedUserId) + .set( + likedUser.toJson(), + ); + + bool? isAMatch = await _isAMatch(matchingProfiles); + bool errorOccurred = isAMatch == null; + if (isAMatch == true) { + bool updated = await _updateToMatchProfiles(matchingProfiles); + errorOccurred = !updated; + } + + if (isAMatch == false) { + bool added = await _addToMatchProfiles(matchingProfiles); + errorOccurred = !added; + } + + return ServiceResult(errorOccurred: errorOccurred); + } catch (e) { + if (kDebugMode) { + print("=============== likeUser() exc $e =========="); + } + return ServiceResult(errorOccurred: true, errorMessage: likingUserFailed); + } + } + + /// GETTING PROFILES TO SWIPE + @override + Future?> getProfilesToMatchWith( + UserProfile userProfile, + ) async { + try { + UserGender? usersGenderPreference = userProfile.getGenderPreferences(); + int lessThanAge = userProfile.maxAgePreference + 1; + int moreThanAge = userProfile.minAgePreference - 1; + + Query> query; + if (_lastDocInFetchedProfilesToMatchWith != null) { + //get the next batch + query = _getQueryForProfilesToMatchAfterWithBatch( + lessThanAge: lessThanAge, + moreThanAge: moreThanAge, + usersGenderPreference: usersGenderPreference, + startAfterDoc: _lastDocInFetchedProfilesToMatchWith!); + } else { + //get the next batch + query = _getQueryForFirstBatchProfilesToMatchWith( + lessThanAge: lessThanAge, + moreThanAge: moreThanAge, + usersGenderPreference: usersGenderPreference); + } + + QuerySnapshot> fittingProfiles = await query.get(); + + //track the last one + if (fittingProfiles.docs.isNotEmpty) { + _lastDocInFetchedProfilesToMatchWith = fittingProfiles.docs.last; + } + + return _filterProfilesToMatchWithByGenderAndSexPreferences( + fittingProfiles, + userProfile, + ); + } catch (e) { + if (kDebugMode) { + print("================= getDatingProfiles $e =============== "); + } + return null; + } + } + + List _filterProfilesToMatchWithByGenderAndSexPreferences( + QuerySnapshot> fittingProfiles, + UserProfile userProfile, + ) { + int usersAge = userProfile.age; + UserGender? usersGender = userProfile.getGender(); + bool restrictSexualOrientation = + userProfile.onlyShowMeOthersOfSameOrientation; + List usersSexOrientation = + userProfile.getUserSexualOrientation(); + + List fittingProfilesList = []; + for (var snapshot in fittingProfiles.docs) { + UserProfile canMatchWith = UserProfileEntity.fromJson(snapshot.data()); + + if (canMatchWith.isSameAs(userProfile)) { + continue; //skip the rest - user is self + } + + /// match age + if (canMatchWith.minAgePreference > usersAge || + canMatchWith.maxAgePreference < usersAge) { + continue; + } + + /// match gender? + UserGender? otherUserGenderPref = canMatchWith.getGenderPreferences(); + bool isSuitableGender = + otherUserGenderPref == null || otherUserGenderPref == usersGender; + bool bothDoNotCareAboutSexualOrientation = + canMatchWith.onlyShowMeOthersOfSameOrientation && + restrictSexualOrientation; + if (isSuitableGender && bothDoNotCareAboutSexualOrientation) { + fittingProfilesList.add(canMatchWith); + continue; //skip the rest + } + + ///filter by orientation + final thisUsersSexOrientationSet = usersSexOrientation.toSet(); + final otherUsersSexOrientationSet = + canMatchWith.getUserSexualOrientation().toSet(); + final intersectionOfSexOrientationPreferences = + thisUsersSexOrientationSet.intersection(otherUsersSexOrientationSet); + bool matchingSexOrientation = + intersectionOfSexOrientationPreferences.isNotEmpty; + if (isSuitableGender && matchingSexOrientation) { + fittingProfilesList.add(canMatchWith); + } + } + return fittingProfilesList; + } + + Query> _getQueryForProfilesToMatchAfterWithBatch( + {required int lessThanAge, + required int moreThanAge, + UserGender? usersGenderPreference, + required DocumentSnapshot startAfterDoc}) { + if (usersGenderPreference != null) { + return _fireStore + .collection(userProfileCollection) + .where(UserProfileEntity.ageFieldName, isLessThan: lessThanAge) + .where(UserProfileEntity.ageFieldName, isGreaterThan: moreThanAge) + .where(UserProfileEntity.genderFieldName, + isEqualTo: userGenderToStringVal[usersGenderPreference]) + .where(UserProfileEntity.isBannedFieldName, isEqualTo: false) + .orderBy(UserProfileEntity.ageFieldName, descending: true) + .startAfterDocument(startAfterDoc) + .limit(queryPageResultsSize); + } else { + return _fireStore + .collection(userProfileCollection) + .where(UserProfileEntity.ageFieldName, isLessThan: lessThanAge) + .where(UserProfileEntity.ageFieldName, isGreaterThan: moreThanAge) + .where(UserProfileEntity.isBannedFieldName, isEqualTo: false) + .orderBy(UserProfileEntity.ageFieldName, descending: true) + .startAfterDocument(startAfterDoc) + .limit(queryPageResultsSize); + } + } + + Query> _getQueryForFirstBatchProfilesToMatchWith( + {required int lessThanAge, + required int moreThanAge, + UserGender? usersGenderPreference}) { + if (usersGenderPreference != null) { + return _fireStore + .collection(userProfileCollection) + .where(UserProfileEntity.ageFieldName, isLessThan: lessThanAge) + .where(UserProfileEntity.ageFieldName, isGreaterThan: moreThanAge) + .where(UserProfileEntity.genderFieldName, + isEqualTo: userGenderToStringVal[usersGenderPreference]) + .where(UserProfileEntity.isBannedFieldName, isEqualTo: false) + .orderBy(UserProfileEntity.ageFieldName, descending: true) + .limit(queryPageResultsSize); + } else { + return _fireStore + .collection(userProfileCollection) + .where(UserProfileEntity.ageFieldName, isLessThan: lessThanAge) + .where(UserProfileEntity.ageFieldName, isGreaterThan: moreThanAge) + .where(UserProfileEntity.isBannedFieldName, isEqualTo: false) + .orderBy(UserProfileEntity.ageFieldName, descending: true) + .limit(queryPageResultsSize); + } + } + + /// user feedback + @override + Future sendDailyFeedback( + DailyUserFeedback dailyUserFeedback) async { + try { + await _fireStore + .collection(dailyUserFeedbackCollection) + .doc() + .set(dailyUserFeedback.toJson()); + return ServiceResult(); + } catch (e) { + if (kDebugMode) { + print("================= sendDailyFeedback $e =============== "); + } + return ServiceResult(errorOccurred: true); + } + } + + /// listen to matches + @override + Stream> listenToMatches( + {bool loadMore = false}) { + if (_auth.currentUser == null) return const Stream.empty(); + + try { + Query> query; + + /// paginate + if (_startMatchesAfterDoc != null && loadMore) { + query = _fireStore + .collection(matchesCollection) + .where(MatchingProfilesEntity.likersFieldName, + arrayContainsAny: [_auth.currentUser!.uid]) + .where(MatchingProfilesEntity.haveMatchedFieldName, isEqualTo: true) + .startAfterDocument(_startMatchesAfterDoc!) + .orderBy(MatchingProfilesEntity.orderByFieldName, descending: true) + .limit(queryPageResultsSize); + } else { + query = _fireStore + .collection(matchesCollection) + .where(MatchingProfilesEntity.likersFieldName, + arrayContainsAny: [_auth.currentUser!.uid]) + .where(MatchingProfilesEntity.haveMatchedFieldName, isEqualTo: true) + .orderBy(MatchingProfilesEntity.orderByFieldName, descending: true) + .limit(queryPageResultsSize); + } + + //get stream + return query.snapshots().map((event) { + List results = []; + for (var change in event.docChanges) { + OperationRealTimeResult result; + switch (change.type) { + case DocumentChangeType.added: + //cache the last added for pagination + _startMatchesAfterDoc = change.doc; + result = OperationRealTimeResult( + data: MatchingProfilesEntity.fromJson(change.doc.data()!), + realTimeEventType: RealTimeEventType.added, + ); + break; + case DocumentChangeType.modified: + result = OperationRealTimeResult( + data: MatchingProfilesEntity.fromJson(change.doc.data()!), + realTimeEventType: RealTimeEventType.modified, + ); + break; + case DocumentChangeType.removed: + //reset pagination cache + if (_startMatchesAfterDoc == change.doc) { + _startMatchesAfterDoc = null; + } + + result = OperationRealTimeResult( + data: MatchingProfilesEntity.fromJson(change.doc.data()!), + realTimeEventType: RealTimeEventType.deleted, + ); + break; + } + results.add(result); + } + return results; + }); + } catch (e) { + if (kDebugMode) { + print("=============== listenToMatches exc $e =========="); + } + return Stream.error(e); + } + } +} diff --git a/lib/features/matching/data/data_sources/remote/remote_matching_data_source.dart b/lib/features/matching/data/data_sources/remote/remote_matching_data_source.dart new file mode 100644 index 0000000..8b0c785 --- /dev/null +++ b/lib/features/matching/data/data_sources/remote/remote_matching_data_source.dart @@ -0,0 +1,18 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/models/daily_user_feedback.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/features/matching/domain/models/liked_user.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +abstract class RemoteMatchingDataSource { + Future likeUser( + LikedUser likedUser, MatchingProfiles matchingProfiles); + Future getIceBreakerMessages(); + Future?> getProfilesToMatchWith( + UserProfile thisUsersProfile, + ); + Future sendDailyFeedback(DailyUserFeedback dailyUserFeedback); + Stream> listenToMatches(); + Future getUsersILike(); +} diff --git a/lib/features/matching/data/entities/daily_user_feedback_entity.dart b/lib/features/matching/data/entities/daily_user_feedback_entity.dart new file mode 100644 index 0000000..61a9106 --- /dev/null +++ b/lib/features/matching/data/entities/daily_user_feedback_entity.dart @@ -0,0 +1,37 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/utils/flutter_fire_date_time_utils.dart'; +import 'package:redting/features/matching/domain/models/daily_user_feedback.dart'; + +part 'daily_user_feedback_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +class DailyUserFeedbackEntity implements DailyUserFeedback { + @override + String feedback; + + @override + int rating; + + @TimestampConverter() + @override + DateTime recordedOn; + + @override + String userId; + + DailyUserFeedbackEntity( + this.feedback, this.rating, this.recordedOn, this.userId); + + @override + factory DailyUserFeedbackEntity.fromJson(Map json) => + _$DailyUserFeedbackEntityFromJson(json); + + @override + Map toJson() => _$DailyUserFeedbackEntityToJson(this); + + @override + DailyUserFeedback fromJson(Map json) { + return DailyUserFeedbackEntity.fromJson(json); + } +} diff --git a/lib/features/matching/data/entities/daily_user_feedback_entity.g.dart b/lib/features/matching/data/entities/daily_user_feedback_entity.g.dart new file mode 100644 index 0000000..448aae1 --- /dev/null +++ b/lib/features/matching/data/entities/daily_user_feedback_entity.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'daily_user_feedback_entity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DailyUserFeedbackEntity _$DailyUserFeedbackEntityFromJson(Map json) => + DailyUserFeedbackEntity( + json['feedback'] as String, + json['rating'] as int, + const TimestampConverter().fromJson(json['recordedOn'] as Timestamp), + json['userId'] as String, + ); + +Map _$DailyUserFeedbackEntityToJson( + DailyUserFeedbackEntity instance) => + { + 'feedback': instance.feedback, + 'rating': instance.rating, + 'recordedOn': const TimestampConverter().toJson(instance.recordedOn), + 'userId': instance.userId, + }; diff --git a/lib/features/matching/data/entities/ice_breaker_messages_entity.dart b/lib/features/matching/data/entities/ice_breaker_messages_entity.dart new file mode 100644 index 0000000..a039635 --- /dev/null +++ b/lib/features/matching/data/entities/ice_breaker_messages_entity.dart @@ -0,0 +1,29 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; + +part 'ice_breaker_messages_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +@HiveType(typeId: iceBreakerMessagesTypeId) +class IceBreakerMessagesEntity implements IceBreakerMessages { + @HiveField(0) + @override + List messages; + + IceBreakerMessagesEntity({List? messages}) + : messages = messages ?? []; + + @override + factory IceBreakerMessagesEntity.fromJson(Map json) => + _$IceBreakerMessagesEntityFromJson(json); + + @override + Map toJson() => _$IceBreakerMessagesEntityToJson(this); + + @override + IceBreakerMessages fromJson(Map json) { + return IceBreakerMessagesEntity.fromJson(json); + } +} diff --git a/lib/features/matching/data/entities/ice_breaker_messages_entity.g.dart b/lib/features/matching/data/entities/ice_breaker_messages_entity.g.dart new file mode 100644 index 0000000..aa51b46 --- /dev/null +++ b/lib/features/matching/data/entities/ice_breaker_messages_entity.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ice_breaker_messages_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class IceBreakerMessagesEntityAdapter + extends TypeAdapter { + @override + final int typeId = 7; + + @override + IceBreakerMessagesEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return IceBreakerMessagesEntity( + messages: (fields[0] as List?)?.cast(), + ); + } + + @override + void write(BinaryWriter writer, IceBreakerMessagesEntity obj) { + writer + ..writeByte(1) + ..writeByte(0) + ..write(obj.messages); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is IceBreakerMessagesEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +IceBreakerMessagesEntity _$IceBreakerMessagesEntityFromJson(Map json) => + IceBreakerMessagesEntity( + messages: (json['messages'] as List?) + ?.map((e) => e as String) + .toList(), + ); + +Map _$IceBreakerMessagesEntityToJson( + IceBreakerMessagesEntity instance) => + { + 'messages': instance.messages, + }; diff --git a/lib/features/matching/data/entities/liked_user_entity.dart b/lib/features/matching/data/entities/liked_user_entity.dart new file mode 100644 index 0000000..d95fbd0 --- /dev/null +++ b/lib/features/matching/data/entities/liked_user_entity.dart @@ -0,0 +1,37 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/utils/flutter_fire_date_time_utils.dart'; +import 'package:redting/features/matching/domain/models/liked_user.dart'; + +part 'liked_user_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +class LikedUserEntity implements LikedUser { + @override + String likedByUserId; + + @TimestampConverter() + @override + DateTime likedOn; + + @override + String likedUserId; + + LikedUserEntity({ + required this.likedByUserId, + required this.likedOn, + required this.likedUserId, + }); + + @override + factory LikedUserEntity.fromJson(Map json) => + _$LikedUserEntityFromJson(json); + + @override + Map toJson() => _$LikedUserEntityToJson(this); + + @override + LikedUser fromJson(Map json) { + return LikedUserEntity.fromJson(json); + } +} diff --git a/lib/features/matching/data/entities/liked_user_entity.g.dart b/lib/features/matching/data/entities/liked_user_entity.g.dart new file mode 100644 index 0000000..0e27de7 --- /dev/null +++ b/lib/features/matching/data/entities/liked_user_entity.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'liked_user_entity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LikedUserEntity _$LikedUserEntityFromJson(Map json) => LikedUserEntity( + likedByUserId: json['likedByUserId'] as String, + likedOn: + const TimestampConverter().fromJson(json['likedOn'] as Timestamp), + likedUserId: json['likedUserId'] as String, + ); + +Map _$LikedUserEntityToJson(LikedUserEntity instance) => + { + 'likedByUserId': instance.likedByUserId, + 'likedOn': const TimestampConverter().toJson(instance.likedOn), + 'likedUserId': instance.likedUserId, + }; diff --git a/lib/features/matching/data/entities/matching_profiles_entity.dart b/lib/features/matching/data/entities/matching_profiles_entity.dart new file mode 100644 index 0000000..a41d357 --- /dev/null +++ b/lib/features/matching/data/entities/matching_profiles_entity.dart @@ -0,0 +1,87 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/utils/flutter_fire_date_time_utils.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; + +part 'matching_profiles_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +class MatchingProfilesEntity implements MatchingProfiles { + static String likersFieldName = "likers"; + static String haveMatchedFieldName = "haveMatched"; + static String membersFieldName = "otherUser"; + static Object orderByFieldName = "updatedOn"; + static var iceBreakersFieldName = "iceBreakers"; + + @override + String userAUserBIdsConcatNSorted; + + @override + List iceBreakers; + + @override + List likers; + + @override + bool haveMatched; + + List otherUser; + + @TimestampConverter() + @override + DateTime updatedOn; + + MatchingProfilesEntity( + {required this.userAUserBIdsConcatNSorted, + this.haveMatched = false, + List? iceBreakers, + List? likers, + List? otherUser, + required this.updatedOn}) + : iceBreakers = iceBreakers ?? [], + likers = likers ?? [], + otherUser = otherUser ?? []; + + @override + factory MatchingProfilesEntity.fromJson(Map json) => + _$MatchingProfilesEntityFromJson(json); + + @override + Map toJson() => _$MatchingProfilesEntityToJson(this); + + @override + MatchingProfiles fromJson(Map json) { + return MatchingProfilesEntity.fromJson(json); + } + + @override + List getMembers() { + return otherUser; + } +} + +@JsonSerializable(anyMap: true, explicitToJson: true) +class MatchingMembersEntity implements MatchingMembers { + @override + String userId; + + @override + String userName; + + @override + String userProfilePhoto; + + MatchingMembersEntity(this.userId, this.userName, this.userProfilePhoto); + + @override + factory MatchingMembersEntity.fromJson(Map json) => + _$MatchingMembersEntityFromJson(json); + + @override + Map toJson() => _$MatchingMembersEntityToJson(this); + + @override + MatchingMembers fromJson(Map json) { + return MatchingMembersEntity.fromJson(json); + } +} diff --git a/lib/features/matching/data/entities/matching_profiles_entity.g.dart b/lib/features/matching/data/entities/matching_profiles_entity.g.dart new file mode 100644 index 0000000..f8f3415 --- /dev/null +++ b/lib/features/matching/data/entities/matching_profiles_entity.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'matching_profiles_entity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MatchingProfilesEntity _$MatchingProfilesEntityFromJson(Map json) => + MatchingProfilesEntity( + userAUserBIdsConcatNSorted: json['userAUserBIdsConcatNSorted'] as String, + haveMatched: json['haveMatched'] as bool? ?? false, + iceBreakers: (json['iceBreakers'] as List?) + ?.map((e) => e as String) + .toList(), + likers: + (json['likers'] as List?)?.map((e) => e as String).toList(), + otherUser: (json['otherUser'] as List?) + ?.map((e) => MatchingMembersEntity.fromJson( + Map.from(e as Map))) + .toList(), + updatedOn: + const TimestampConverter().fromJson(json['updatedOn'] as Timestamp), + ); + +Map _$MatchingProfilesEntityToJson( + MatchingProfilesEntity instance) => + { + 'userAUserBIdsConcatNSorted': instance.userAUserBIdsConcatNSorted, + 'iceBreakers': instance.iceBreakers, + 'likers': instance.likers, + 'haveMatched': instance.haveMatched, + 'otherUser': instance.otherUser.map((e) => e.toJson()).toList(), + 'updatedOn': const TimestampConverter().toJson(instance.updatedOn), + }; + +MatchingMembersEntity _$MatchingMembersEntityFromJson(Map json) => + MatchingMembersEntity( + json['userId'] as String, + json['userName'] as String, + json['userProfilePhoto'] as String, + ); + +Map _$MatchingMembersEntityToJson( + MatchingMembersEntity instance) => + { + 'userId': instance.userId, + 'userName': instance.userName, + 'userProfilePhoto': instance.userProfilePhoto, + }; diff --git a/lib/features/matching/data/repositories/matching_repository_impl.dart b/lib/features/matching/data/repositories/matching_repository_impl.dart new file mode 100644 index 0000000..504dec9 --- /dev/null +++ b/lib/features/matching/data/repositories/matching_repository_impl.dart @@ -0,0 +1,161 @@ +import 'package:flutter/foundation.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/core/utils/txt_helpers.dart'; +import 'package:redting/features/matching/data/data_sources/local/local_matching_data_source.dart'; +import 'package:redting/features/matching/data/data_sources/remote/remote_matching_data_source.dart'; +import 'package:redting/features/matching/data/entities/daily_user_feedback_entity.dart'; +import 'package:redting/features/matching/data/entities/liked_user_entity.dart'; +import 'package:redting/features/matching/data/entities/matching_profiles_entity.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/features/matching/domain/models/liked_user.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; +import 'package:redting/res/strings.dart'; + +class MatchingRepositoryImpl implements MatchingRepository { + final ProfileRepository _profileRepository; + final LocalMatchingDataSource _localDataSource; + final RemoteMatchingDataSource _remoteDataSource; + final List icebreakersCacheMessages = []; + MatchingRepositoryImpl( + this._profileRepository, this._localDataSource, this._remoteDataSource); + + @override + Future getProfilesToMatchWith(UserProfile userProfile) async { + List? profilesToMatchOrNull = + await _remoteDataSource.getProfilesToMatchWith(userProfile); + if (profilesToMatchOrNull == null) { + return ServiceResult(errorOccurred: true); + } + //removed liked users + Map likedUsers = _localDataSource.getLikedUsersCache(); + profilesToMatchOrNull.removeWhere((e) => likedUsers.containsKey(e.userId)); + return ServiceResult(data: profilesToMatchOrNull); + } + + @override + Future getThisUserInfo() async { + UserProfile? userProfile = await _profileRepository.getCachedUserProfile(); + return ServiceResult( + errorOccurred: userProfile == null, + errorMessage: userProfile == null ? loadingAuthUserErr : '', + data: userProfile); + } + + @override + Future likeUser(String thisUserId, String likedUserId, + String likedUserName, String likedUserProfilePhotoUrl) async { + try { + String iceBreaker = await _getRandomIceBreakerMessage(); + if (iceBreaker.isEmpty) { + return ServiceResult( + errorMessage: likingUserFailed, errorOccurred: true); + } + + LikedUser likedUser = LikedUserEntity( + likedByUserId: thisUserId, + likedOn: DateTime.now(), + likedUserId: likedUserId); + MatchingProfiles matchingProfiles = MatchingProfilesEntity( + userAUserBIdsConcatNSorted: + MatchingProfiles.concatUser1User2IdsSortAndGetAsId( + thisUserId, likedUserId), + iceBreakers: [iceBreaker], + likers: [thisUserId], + otherUser: [ + MatchingMembersEntity( + likedUserId, likedUserName, likedUserProfilePhotoUrl) + ], + updatedOn: DateTime.now() + //add self + ); + + ServiceResult result = + await _remoteDataSource.likeUser(likedUser, matchingProfiles); + if (!result.errorOccurred) { + //cache liked user + await _localDataSource.cacheLikedUser(likedUserId); + } + return result; + } catch (e) { + if (kDebugMode) { + print("=============== repository likeUser $e =========="); + } + return ServiceResult(errorOccurred: true, errorMessage: likingUserFailed); + } + } + + Future _getRandomIceBreakerMessage() async { + try { + if (icebreakersCacheMessages.isEmpty) { + IceBreakerMessages? icebreakers = + await _localDataSource.getIceBreakerMessages(); + icebreakersCacheMessages.addAll(icebreakers!.messages); + } + + return randomWordInList(icebreakersCacheMessages); + } catch (e) { + if (kDebugMode) { + print( + "=============== repository getRandomIceBreakerMessage() $e =========="); + } + return ""; + } + } + + @override + Future loadIceBreakerMessagesToCache() async { + try { + IceBreakerMessages? icebreakers = + await _remoteDataSource.getIceBreakerMessages(); + _localDataSource.cacheIceBreakersAndGet(icebreakers!); + icebreakersCacheMessages.clear(); + icebreakersCacheMessages.addAll(icebreakers.messages); + return true; + } catch (e) { + if (kDebugMode) { + print( + "=============== repository getRandomIceBreakerMessage() $e =========="); + } + return false; + } + } + + @override + Future loadLikedUsersToCache() async { + try { + ServiceResult result = await _remoteDataSource.getUsersILike(); + if (result.data is List) { + List users = result.data as List; + for (LikedUser user in users) { + await _localDataSource.cacheLikedUser(user.likedUserId); + } + } + return true; + } catch (e) { + if (kDebugMode) { + print("=========== loadLikedUsersToCache exc $e ==========="); + } + return false; + } + } + + @override + Future sendDailyFeedback( + String userId, String feedback, int rating) async { + return await _remoteDataSource.sendDailyFeedback( + DailyUserFeedbackEntity(feedback, rating, DateTime.now(), userId)); + } + + @override + Stream> listenToMatches() { + return _remoteDataSource.listenToMatches(); + } + + @override + Future getCachedIceBreakers() async { + return await _localDataSource.getIceBreakerMessages(); + } +} diff --git a/lib/features/matching/di/matching_di.dart b/lib/features/matching/di/matching_di.dart new file mode 100644 index 0000000..577b80c --- /dev/null +++ b/lib/features/matching/di/matching_di.dart @@ -0,0 +1,67 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/matching/data/data_sources/local/hive_matching_data_source.dart'; +import 'package:redting/features/matching/data/data_sources/local/local_matching_data_source.dart'; +import 'package:redting/features/matching/data/data_sources/remote/fire_matching_data_source.dart'; +import 'package:redting/features/matching/data/data_sources/remote/remote_matching_data_source.dart'; +import 'package:redting/features/matching/data/repositories/matching_repository_impl.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; +import 'package:redting/features/matching/domain/use_cases/fetch_profiles_to_match.dart'; +import 'package:redting/features/matching/domain/use_cases/get_this_usersinfo_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/like_user_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/listen_to_matches_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/matching_usecases.dart'; +import 'package:redting/features/matching/domain/use_cases/pass_on_user_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/send_daily_feedback.dart'; +import 'package:redting/features/matching/domain/use_cases/sync_with_remote.dart'; +import 'package:redting/features/matching/presentation/state/matches_listener/matches_listener_bloc.dart'; +import 'package:redting/features/matching/presentation/state/matching_bloc.dart'; + +GetIt init() { + GetIt diInstance = GetIt.instance; + + diInstance.registerFactory(() => MatchingBloc(diInstance())); + + diInstance.registerFactory( + () => MatchesListenerBloc(diInstance())); + + diInstance.registerLazySingleton(() => MatchingUseCases( + syncWithRemote: diInstance(), + fetchProfilesToMatch: diInstance(), + likeUserUseCase: diInstance(), + passOnUserUseCase: diInstance(), + sendUserDailyFeedback: diInstance(), + listenToMatchUseCase: diInstance(), + getThisUsersInfoUseCase: diInstance())); + + diInstance.registerLazySingleton( + () => SyncWithRemote(diInstance())); + + diInstance.registerLazySingleton( + () => FetchProfilesToMatch(diInstance())); + + diInstance.registerLazySingleton( + () => LikeUserUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => PassOnUserUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => SendUserDailyFeedback(diInstance())); + + diInstance.registerLazySingleton( + () => ListenToMatchUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => GetThisUsersInfoUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => MatchingRepositoryImpl(diInstance(), diInstance(), diInstance())); + + diInstance.registerLazySingleton( + () => FireMatchingDataSource()); + + diInstance.registerLazySingleton( + () => HiveMatchingDataSource()); + + return diInstance; +} diff --git a/lib/features/matching/domain/models/daily_user_feedback.dart b/lib/features/matching/domain/models/daily_user_feedback.dart new file mode 100644 index 0000000..25dd712 --- /dev/null +++ b/lib/features/matching/domain/models/daily_user_feedback.dart @@ -0,0 +1,11 @@ +abstract class DailyUserFeedback { + String userId; + String feedback; + int rating; + DateTime recordedOn; + + DailyUserFeedback(this.userId, this.feedback, this.rating, this.recordedOn); + + Map toJson(); + DailyUserFeedback fromJson(Map json); +} diff --git a/lib/features/matching/domain/models/ice_breaker_msg.dart b/lib/features/matching/domain/models/ice_breaker_msg.dart new file mode 100644 index 0000000..7ce8811 --- /dev/null +++ b/lib/features/matching/domain/models/ice_breaker_msg.dart @@ -0,0 +1,7 @@ +abstract class IceBreakerMessages { + List messages; + IceBreakerMessages(this.messages); + + Map toJson(); + IceBreakerMessages fromJson(Map json); +} diff --git a/lib/features/matching/domain/models/liked_user.dart b/lib/features/matching/domain/models/liked_user.dart new file mode 100644 index 0000000..eb3591f --- /dev/null +++ b/lib/features/matching/domain/models/liked_user.dart @@ -0,0 +1,13 @@ +abstract class LikedUser { + String likedByUserId; + String likedUserId; + DateTime likedOn; + LikedUser( + this.likedByUserId, + this.likedUserId, + this.likedOn, + ); + + Map toJson(); + LikedUser fromJson(Map json); +} diff --git a/lib/features/matching/domain/models/matching_profiles.dart b/lib/features/matching/domain/models/matching_profiles.dart new file mode 100644 index 0000000..861c6aa --- /dev/null +++ b/lib/features/matching/domain/models/matching_profiles.dart @@ -0,0 +1,31 @@ +abstract class MatchingProfiles { + List iceBreakers; + List likers; + String userAUserBIdsConcatNSorted; + bool haveMatched; + DateTime updatedOn; + MatchingProfiles(this.userAUserBIdsConcatNSorted, this.iceBreakers, + this.likers, this.haveMatched, this.updatedOn); + + Map toJson(); + MatchingProfiles fromJson(Map json); + static String concatUser1User2IdsSortAndGetAsId(String user1, user2) { + String concatUser1User2 = "$user1$user2"; + final concatUser1User2List = concatUser1User2.split(""); + concatUser1User2List.sort((a, b) => a.compareTo(b)); + return concatUser1User2List.join(""); + } + + List getMembers(); +} + +abstract class MatchingMembers { + String userId; + String userName; + String userProfilePhoto; + + MatchingMembers(this.userId, this.userName, this.userProfilePhoto); + + Map toJson(); + MatchingMembers fromJson(Map json); +} diff --git a/lib/features/matching/domain/repositories/matching_repository.dart b/lib/features/matching/domain/repositories/matching_repository.dart new file mode 100644 index 0000000..930f918 --- /dev/null +++ b/lib/features/matching/domain/repositories/matching_repository.dart @@ -0,0 +1,18 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/models/ice_breaker_msg.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +abstract class MatchingRepository { + Future getThisUserInfo(); + Future getProfilesToMatchWith(UserProfile thisUserProfiles); + Future likeUser(String thisUserId, String likedUserId, + String likedUserName, String likedUserProfilePhotoUrl); + Future sendDailyFeedback( + String userId, String feedback, int rating); + Stream> listenToMatches(); + + /// initializations ---syncing --- + Future loadLikedUsersToCache(); + Future loadIceBreakerMessagesToCache(); + Future getCachedIceBreakers(); +} diff --git a/lib/features/matching/domain/use_cases/fetch_profiles_to_match.dart b/lib/features/matching/domain/use_cases/fetch_profiles_to_match.dart new file mode 100644 index 0000000..8d82ca0 --- /dev/null +++ b/lib/features/matching/domain/use_cases/fetch_profiles_to_match.dart @@ -0,0 +1,12 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +class FetchProfilesToMatch { + final MatchingRepository repository; + FetchProfilesToMatch(this.repository); + + Future execute(UserProfile thisUserProfile) async { + return await repository.getProfilesToMatchWith(thisUserProfile); + } +} diff --git a/lib/features/matching/domain/use_cases/get_this_usersinfo_usecase.dart b/lib/features/matching/domain/use_cases/get_this_usersinfo_usecase.dart new file mode 100644 index 0000000..da53c21 --- /dev/null +++ b/lib/features/matching/domain/use_cases/get_this_usersinfo_usecase.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; + +class GetThisUsersInfoUseCase { + final MatchingRepository repository; + GetThisUsersInfoUseCase(this.repository); + + Future execute() async { + return await repository.getThisUserInfo(); + } +} diff --git a/lib/features/matching/domain/use_cases/like_user_usecase.dart b/lib/features/matching/domain/use_cases/like_user_usecase.dart new file mode 100644 index 0000000..48f1004 --- /dev/null +++ b/lib/features/matching/domain/use_cases/like_user_usecase.dart @@ -0,0 +1,13 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; + +class LikeUserUseCase { + final MatchingRepository repository; + LikeUserUseCase(this.repository); + + Future execute(String thisUser, String likedUser, + String likedUserName, String likedUserProfilePhotoUrl) async { + return await repository.likeUser( + thisUser, likedUser, likedUserName, likedUserProfilePhotoUrl); + } +} diff --git a/lib/features/matching/domain/use_cases/listen_to_matches_usecase.dart b/lib/features/matching/domain/use_cases/listen_to_matches_usecase.dart new file mode 100644 index 0000000..36b65a1 --- /dev/null +++ b/lib/features/matching/domain/use_cases/listen_to_matches_usecase.dart @@ -0,0 +1,10 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; + +class ListenToMatchUseCase { + final MatchingRepository repository; + ListenToMatchUseCase(this.repository); + + Stream> execute() => + repository.listenToMatches(); +} diff --git a/lib/features/matching/domain/use_cases/matching_usecases.dart b/lib/features/matching/domain/use_cases/matching_usecases.dart new file mode 100644 index 0000000..670abb6 --- /dev/null +++ b/lib/features/matching/domain/use_cases/matching_usecases.dart @@ -0,0 +1,25 @@ +import 'package:redting/features/matching/domain/use_cases/fetch_profiles_to_match.dart'; +import 'package:redting/features/matching/domain/use_cases/get_this_usersinfo_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/like_user_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/listen_to_matches_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/pass_on_user_usecase.dart'; +import 'package:redting/features/matching/domain/use_cases/send_daily_feedback.dart'; +import 'package:redting/features/matching/domain/use_cases/sync_with_remote.dart'; + +class MatchingUseCases { + final SyncWithRemote syncWithRemote; + final FetchProfilesToMatch fetchProfilesToMatch; + final LikeUserUseCase likeUserUseCase; + final PassOnUserUseCase passOnUserUseCase; + final SendUserDailyFeedback sendUserDailyFeedback; + final ListenToMatchUseCase listenToMatchUseCase; + final GetThisUsersInfoUseCase getThisUsersInfoUseCase; + MatchingUseCases( + {required this.syncWithRemote, + required this.fetchProfilesToMatch, + required this.likeUserUseCase, + required this.passOnUserUseCase, + required this.sendUserDailyFeedback, + required this.listenToMatchUseCase, + required this.getThisUsersInfoUseCase}); +} diff --git a/lib/features/matching/domain/use_cases/pass_on_user_usecase.dart b/lib/features/matching/domain/use_cases/pass_on_user_usecase.dart new file mode 100644 index 0000000..90c6109 --- /dev/null +++ b/lib/features/matching/domain/use_cases/pass_on_user_usecase.dart @@ -0,0 +1,13 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; + +class PassOnUserUseCase { + final MatchingRepository repository; + PassOnUserUseCase(this.repository); + + Future execute(String dislikedUserId) async { + return ServiceResult(); + + ///TODO IMPLEMENT? + } +} diff --git a/lib/features/matching/domain/use_cases/send_daily_feedback.dart b/lib/features/matching/domain/use_cases/send_daily_feedback.dart new file mode 100644 index 0000000..2d6fe96 --- /dev/null +++ b/lib/features/matching/domain/use_cases/send_daily_feedback.dart @@ -0,0 +1,12 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; + +class SendUserDailyFeedback { + final MatchingRepository repository; + SendUserDailyFeedback(this.repository); + + Future execute( + String userId, String feedback, int rating) async { + return await repository.sendDailyFeedback(userId, feedback, rating); + } +} diff --git a/lib/features/matching/domain/use_cases/sync_with_remote.dart b/lib/features/matching/domain/use_cases/sync_with_remote.dart new file mode 100644 index 0000000..24878d5 --- /dev/null +++ b/lib/features/matching/domain/use_cases/sync_with_remote.dart @@ -0,0 +1,11 @@ +import 'package:redting/features/matching/domain/repositories/matching_repository.dart'; + +class SyncWithRemote { + final MatchingRepository repository; + SyncWithRemote(this.repository); + + Future execute() async { + await repository.loadIceBreakerMessagesToCache(); + await repository.loadLikedUsersToCache(); + } +} diff --git a/lib/features/matching/presentation/components/idle_matching_card.dart b/lib/features/matching/presentation/components/idle_matching_card.dart new file mode 100644 index 0000000..518d6de --- /dev/null +++ b/lib/features/matching/presentation/components/idle_matching_card.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/cards/glass_card.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class IdleMatchingCard extends StatelessWidget { + const IdleMatchingCard({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + double screenWidth = size.width; + double screenHeight = size.height; + return GlassCard( + wrapInChildScrollable: false, + constraints: BoxConstraints( + maxWidth: screenWidth - 40, + minWidth: 300, + minHeight: screenHeight * 0.5, + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.people_alt_outlined, + color: appTheme.colorScheme.primary, + size: 64, + ), + Text( + beAuthenticAlways, + style: appTextTheme.subtitle1, + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/matching/presentation/components/loading_card.dart b/lib/features/matching/presentation/components/loading_card.dart new file mode 100644 index 0000000..1602d3c --- /dev/null +++ b/lib/features/matching/presentation/components/loading_card.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/cards/glass_card.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; + +class LoadingCard extends StatelessWidget { + const LoadingCard({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + double screenWidth = size.width; + double screenHeight = size.height; + return GlassCard( + wrapInChildScrollable: false, + constraints: BoxConstraints( + maxWidth: screenWidth - 40, + minWidth: 300, + minHeight: screenHeight * 0.7, + maxHeight: screenHeight * 0.7, + ), + child: const Center( + child: CircularProgress(), + ), + ); + } +} diff --git a/lib/features/matching/presentation/components/rate_app_card.dart b/lib/features/matching/presentation/components/rate_app_card.dart new file mode 100644 index 0000000..63a6f8d --- /dev/null +++ b/lib/features/matching/presentation/components/rate_app_card.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/cards/glass_card.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class RateAppCard extends StatefulWidget { + final bool isSendingFeedback; + final void Function(int rating, String feedback) onSubmitFeedback; + const RateAppCard( + {Key? key, + required this.isSendingFeedback, + required this.onSubmitFeedback}) + : super(key: key); + + @override + State createState() => _RateAppCardState(); +} + +class _RateAppCardState extends State { + int _rating = 3; + String _feedback = ""; + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + double screenWidth = size.width; + double screenHeight = size.height; + return GlassCard( + wrapInChildScrollable: false, + constraints: BoxConstraints( + maxWidth: screenWidth - 40, + minWidth: 300, + minHeight: screenHeight * 0.4, + ), + child: Center( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + noMoreProfiles, + textAlign: TextAlign.justify, + style: appTextTheme.subtitle2, + ), + const SizedBox( + height: paddingStd, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: _getRatingStars(), + ), + const SizedBox( + height: paddingStd, + ), + ConstrainedBox( + constraints: BoxConstraints(maxHeight: screenHeight * 0.3), + child: TextField( + keyboardType: TextInputType.multiline, + onChanged: (newVal) { + if (mounted) { + setState(() { + _feedback = newVal; + }); + } + }, + minLines: 3, + maxLines: 5, + style: + appTextTheme.bodyText1?.copyWith(color: Colors.black87), + decoration: InputDecoration( + isDense: true, + hintText: feedbackHint, + hintStyle: appTextTheme.caption), + ), + ), + Center( + child: Container( + margin: const EdgeInsets.only(top: paddingMd), + constraints: const BoxConstraints(maxWidth: 200), + child: MainElevatedBtn( + onClick: () { + if (!widget.isSendingFeedback) { + widget.onSubmitFeedback(_rating, _feedback); + } + }, + showLoading: widget.isSendingFeedback, + lbl: submitFeedbackBtn), + ), + ) + ], + ), + ), + ), + ); + } + + List _getRatingStars() { + List stars = []; + int i = 0; + while (i < maxStarsForRating) { + final starNum = i + 1; + stars.add(IconButton( + iconSize: 32, + onPressed: () { + setState(() { + _rating = starNum; + }); + }, + icon: Icon( + Icons.star_rounded, + color: _rating >= starNum + ? appTheme.colorScheme.primary + : Colors.black26, + ))); + i++; + } + return stars; + } +} diff --git a/lib/features/matching/presentation/components/swipeable_profile.dart b/lib/features/matching/presentation/components/swipeable_profile.dart new file mode 100644 index 0000000..415b461 --- /dev/null +++ b/lib/features/matching/presentation/components/swipeable_profile.dart @@ -0,0 +1,214 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile_components/detailed_view.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile_components/overview.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/res/dimens.dart'; + +class SwipeProfile extends StatefulWidget { + final List photoUrls; + final String name; + final String age; + final String title; + final bool isFrontCard; + final Function(CardSwipeType gesture) onSwiped; + final String bio; + final UserVerificationVideo verificationVideo; + final List sexualOrientation; + const SwipeProfile({ + Key? key, + required this.photoUrls, + required this.name, + required this.age, + required this.title, + required this.isFrontCard, + required this.onSwiped, + required this.bio, + required this.verificationVideo, + required this.sexualOrientation, + }) : super(key: key); + + @override + State createState() => _SwipeProfileState(); +} + +class _SwipeProfileState extends State { + bool _isDragging = false; + Offset _dragPosition = Offset.zero; + double _rotationAngle = 0; + late Size _screenSize; + CardSwipeType _stampToShow = CardSwipeType.noAction; + bool _switchToDetailedView = false; + + @override + Widget build(BuildContext context) { + _screenSize = MediaQuery.of(context).size; + return widget.isFrontCard + ? _draggableProfileCard() + : _showProfileOverview(); + } + + double cardWidth() => _screenSize.width - (paddingMd * 2); + double cardHeight() => _screenSize.height * 0.7; + + /// UI + _showProfileOverview() { + return ProfileOverView( + stampToShow: _stampToShow, + name: widget.name, + age: widget.age, + title: widget.title, + cardWidth: cardWidth(), + cardHeight: cardHeight(), + mainPhoto: widget.photoUrls.first); + } + + Widget _draggableProfileCard() { + return GestureDetector( + onPanStart: (details) { + _onStartDrag(details); + }, + onPanEnd: (details) { + _onEndDrag(details); + }, + onPanUpdate: (details) { + _onDragUpdate(details); + }, + child: LayoutBuilder(builder: (context, constraints) { + final center = constraints.smallest.center(Offset.zero); + final rotateAngle = _rotationAngle * pi / 180; + final rotatedMatrix = Matrix4.identity() + ..translate(center.dx, center.dy) + ..rotateZ(rotateAngle) + ..translate(-center.dx, -center.dy); + final animatedDuration = _isDragging ? 0 : 400; + return AnimatedContainer( + curve: Curves.easeInOut, + duration: Duration(milliseconds: animatedDuration), + transform: rotatedMatrix + ..translate(_dragPosition.dx, _dragPosition.dy), + child: _switchToDetailedView + ? DetailedViewCard( + bio: widget.bio, + verificationVideo: widget.verificationVideo, + sexualOrientation: widget.sexualOrientation, + cardWidth: cardWidth(), + cardHeight: cardHeight(), + photoUrls: widget.photoUrls) + : _showProfileOverview(), + ); + }), + ); + } + + /// DRAG LISTENERS + + void _onStartDrag(DragStartDetails details) { + if (mounted) { + setState(() { + _isDragging = true; + }); + } + } + + void _onDragUpdate(DragUpdateDetails details) { + if (mounted) { + setState(() { + _dragPosition += details.delta; + final x = _dragPosition.dx; + final y = _dragPosition.dy; + + _rotationAngle = 45 * x / _screenSize.width; //45 is angle of rotation + _stampToShow = _getCardSwipeType(); + }); + } + } + + void _onEndDrag(DragEndDetails details) { + if (mounted) { + setState(() { + _isDragging = false; + }); + CardSwipeType type = _getCardSwipeType(); + switch (type) { + case CardSwipeType.like: + _likeAnimation(); + break; + case CardSwipeType.pass: + _passAnimation(); + break; + case CardSwipeType.superlike: + setState(() { + _dragPosition = Offset.zero; + _rotationAngle = 0; + }); + break; + case CardSwipeType.showDetails: + //reset + setState(() { + _dragPosition = Offset.zero; + _rotationAngle = 0; + _switchToDetailedView = !_switchToDetailedView; + }); + break; + case CardSwipeType.noAction: + setState(() { + _dragPosition = Offset.zero; + _rotationAngle = 0; + }); + break; + } + setState(() { + _stampToShow = CardSwipeType.noAction; + }); + } + } + + CardSwipeType _getCardSwipeType() { + final x = _dragPosition.dx; + final y = _dragPosition.dy; + final isStraightUp = x.abs() < 20; + + if (x >= 100) { + return CardSwipeType.like; + } + + if (x <= -100) { + return CardSwipeType.pass; + } + + if (y <= -20 && isStraightUp) { + return CardSwipeType.superlike; + } + + if (y >= 20 && isStraightUp) { + return CardSwipeType.showDetails; + } + + return CardSwipeType.noAction; + } + + /// SWIPE ANIMATION + void _likeAnimation() async { + setState(() { + _rotationAngle = 20; + _dragPosition += Offset(_screenSize.width * 2, 0); + }); + await Future.delayed(const Duration(milliseconds: 200)); + widget.onSwiped(CardSwipeType.like); + } + + void _passAnimation() async { + setState(() { + _rotationAngle = -20; + _dragPosition -= Offset(_screenSize.width * 2, 0); + }); + await Future.delayed(const Duration(milliseconds: 200)); + widget.onSwiped(CardSwipeType.pass); + } +} + +enum CardSwipeType { like, pass, showDetails, superlike, noAction } diff --git a/lib/features/matching/presentation/components/swipeable_profile_components/container_card.dart b/lib/features/matching/presentation/components/swipeable_profile_components/container_card.dart new file mode 100644 index 0000000..df5410c --- /dev/null +++ b/lib/features/matching/presentation/components/swipeable_profile_components/container_card.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/cards/glass_card.dart'; + +class ProfileContainerCard extends StatelessWidget { + final Widget child; + final double cardWidth, cardHeight; + final Gradient? bgGradient; + const ProfileContainerCard( + {Key? key, + required this.child, + required this.cardWidth, + required this.cardHeight, + this.bgGradient}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return GlassCard( + gradient: bgGradient, + wrapInChildScrollable: false, + constraints: BoxConstraints( + maxWidth: cardWidth, + minHeight: cardHeight, + maxHeight: cardHeight, + ), + contentPadding: EdgeInsets.zero, + child: child); + } +} diff --git a/lib/features/matching/presentation/components/swipeable_profile_components/detailed_view.dart b/lib/features/matching/presentation/components/swipeable_profile_components/detailed_view.dart new file mode 100644 index 0000000..d43332a --- /dev/null +++ b/lib/features/matching/presentation/components/swipeable_profile_components/detailed_view.dart @@ -0,0 +1,215 @@ +import 'dart:async'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile_components/container_card.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/string_maps.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; +import 'package:video_player/video_player.dart'; + +class DetailedViewCard extends StatefulWidget { + final UserVerificationVideo verificationVideo; + final List sexualOrientation; + final double cardWidth, cardHeight; + final List photoUrls; + final String bio; + const DetailedViewCard({ + Key? key, + required this.bio, + required this.verificationVideo, + required this.sexualOrientation, + required this.cardWidth, + required this.cardHeight, + required this.photoUrls, + }) : super(key: key); + + @override + State createState() => _DetailedViewCardState(); +} + +class _DetailedViewCardState extends State { + late VideoPlayerController _videoPlayerController; + + @override + void initState() { + super.initState(); + _videoPlayerController = + VideoPlayerController.network(widget.verificationVideo.videoUrl) + ..initialize().then(videoPlayerInitCallback); + } + + FutureOr videoPlayerInitCallback(void value) { + if (mounted) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + setState(() {}); + } + } + + @override + void dispose() { + _videoPlayerController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ProfileContainerCard( + cardHeight: widget.cardHeight, + cardWidth: widget.cardWidth, + bgGradient: LinearGradient(colors: [ + appTheme.colorScheme.primary, + appTheme.colorScheme.primary + ]), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildMediaRow(), + Padding( + padding: const EdgeInsets.only( + left: paddingStd, right: paddingStd, top: paddingSm), + child: RichText( + text: TextSpan( + text: "\n$verificationVideoWord", + style: appTextTheme.caption?.copyWith(color: Colors.white), + children: [ + TextSpan( + text: ' ${widget.verificationVideo.verificationCode}', + style: appTextTheme.headline4 + ?.copyWith(color: Colors.white)), + ], + ), + ), + ), + const SizedBox( + height: paddingStd, + ), + Padding( + padding: const EdgeInsets.all(paddingStd), + child: SizedBox( + height: widget.cardHeight / 8, + child: Text( + widget.bio, + textAlign: TextAlign.justify, + style: + appTextTheme.bodyText1?.copyWith(color: Colors.white), + ), + ), + ), + ..._getUserSexualOrientationList() + ], + ), + )); + } + + Widget _getVideoWidget() { + return GestureDetector( + onTap: () { + setState(() { + _videoPlayerController.value.isPlaying + ? _videoPlayerController.pause() + : _videoPlayerController.play(); + }); + }, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: widget.cardWidth, + maxHeight: widget.cardHeight / 2, + minHeight: widget.cardHeight / 2), + child: Center( + child: Stack( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: widget.cardWidth, + maxHeight: widget.cardHeight / 2), + child: AspectRatio( + aspectRatio: _videoPlayerController.value.aspectRatio, + child: VideoPlayer(_videoPlayerController), + ), + ), + Container( + decoration: const BoxDecoration( + color: Colors.white, shape: BoxShape.circle), + child: Icon( + Icons.play_circle, + color: appTheme.colorScheme.inversePrimary, + )) + ], + ), + ), + ), + ); + } + + /// IMAGES ROW & SCROL + List _getProfilePhotosArr() { + return widget.photoUrls + .map((e) => CachedNetworkImage( + imageUrl: e, + width: widget.cardWidth, + height: widget.cardHeight / 2, + fit: BoxFit.cover, + errorWidget: (_, __, ___) { + return Container( + decoration: + BoxDecoration(gradient: threeColorOpaqueGradientTB), + child: const Center( + child: CircularProgress(), + ), + ); + }, + progressIndicatorBuilder: (_, __, ___) { + return Container( + decoration: + BoxDecoration(gradient: threeColorOpaqueGradientTB), + child: const Center( + child: CircularProgress(), + ), + ); + }, + )) + .toList(growable: false); + } + + _buildMediaRow() { + return SizedBox( + width: widget.cardWidth, + height: widget.cardHeight / 2, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [..._getProfilePhotosArr(), _getVideoWidget()], + ), + ), + ); + } + + /// SEXUAL ORIENTATION + + List _getUserSexualOrientationList() { + return widget.sexualOrientation + .map((e) => Padding( + padding: const EdgeInsets.all(paddingStd), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: Colors.black26), + child: Padding( + padding: const EdgeInsets.all(paddingSm), + child: Text( + sexualOrientationToStrMap[e] ?? '', + style: appTextTheme.button?.copyWith(color: Colors.white), + ), + ), + ))) + .toList(growable: false); + } +} diff --git a/lib/features/matching/presentation/components/swipeable_profile_components/overview.dart b/lib/features/matching/presentation/components/swipeable_profile_components/overview.dart new file mode 100644 index 0000000..054eae3 --- /dev/null +++ b/lib/features/matching/presentation/components/swipeable_profile_components/overview.dart @@ -0,0 +1,75 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:redting/core/components/misc/darkish_transparent_layer.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile_components/container_card.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile_components/overview_text.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile_components/stamp.dart'; + +class ProfileOverView extends StatelessWidget { + final CardSwipeType stampToShow; + final String name; + final String age; + final String title; + final double cardWidth; + final double cardHeight; + final String mainPhoto; + const ProfileOverView( + {Key? key, + required this.stampToShow, + required this.name, + required this.age, + required this.title, + required this.cardWidth, + required this.cardHeight, + required this.mainPhoto}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ProfileContainerCard( + cardWidth: cardWidth, + cardHeight: cardHeight, + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: CachedNetworkImage( + height: cardHeight, + imageUrl: mainPhoto, + fit: BoxFit.cover, + placeholder: (_, __) { + return const Center( + child: SizedBox( + width: 40, + height: 40, + child: CircularProgress(), + ), + ); + }), + ), + const TransparentLayer(), + Visibility( + visible: stampToShow == CardSwipeType.pass, + child: const Align( + alignment: Alignment.centerLeft, + child: CardStamp(forType: CardSwipeType.pass), + ), + ), + Visibility( + visible: stampToShow == CardSwipeType.like, + child: const Align( + alignment: Alignment.centerRight, + child: CardStamp(forType: CardSwipeType.like), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: OverviewText( + name: name, age: age, title: title, cardWidth: cardWidth), + ) + ], + )); + } +} diff --git a/lib/features/matching/presentation/components/swipeable_profile_components/overview_text.dart b/lib/features/matching/presentation/components/swipeable_profile_components/overview_text.dart new file mode 100644 index 0000000..a06789a --- /dev/null +++ b/lib/features/matching/presentation/components/swipeable_profile_components/overview_text.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +class OverviewText extends StatelessWidget { + final String name; + final String age; + final String title; + final double cardWidth; + const OverviewText( + {Key? key, + required this.name, + required this.age, + required this.title, + required this.cardWidth}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: cardWidth, + height: 100, + child: Padding( + padding: const EdgeInsets.all(paddingStd), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + name, + style: appTextTheme.subtitle1 + ?.copyWith(color: appTheme.colorScheme.onPrimary), + ), + const SizedBox( + width: paddingStd, + ), + Expanded( + child: Text(age, + style: appTextTheme.headline6 + ?.copyWith(color: appTheme.colorScheme.onPrimary))) + ], + ), + Text( + title.toUpperCase(), + overflow: TextOverflow.ellipsis, + style: appTextTheme.bodyText1 + ?.copyWith(color: appTheme.colorScheme.onPrimary), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/matching/presentation/components/swipeable_profile_components/stamp.dart b/lib/features/matching/presentation/components/swipeable_profile_components/stamp.dart new file mode 100644 index 0000000..6bde3c1 --- /dev/null +++ b/lib/features/matching/presentation/components/swipeable_profile_components/stamp.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class CardStamp extends StatelessWidget { + final CardSwipeType forType; + + const CardStamp({Key? key, required this.forType}) : super(key: key); + + @override + Widget build(BuildContext context) { + String stampTxt = ""; + EdgeInsets pushByMargin = EdgeInsets.zero; + switch (forType) { + case CardSwipeType.like: + stampTxt = likeStamp; + pushByMargin = const EdgeInsets.only(right: 40); + break; + case CardSwipeType.pass: + stampTxt = passStamp; + pushByMargin = const EdgeInsets.only(left: 40); + break; + case CardSwipeType.superlike: + break; + case CardSwipeType.noAction: + break; + } + return Container( + margin: pushByMargin, + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.circular(12), + border: + Border.all(color: appTheme.colorScheme.inversePrimary, width: 2)), + child: Padding( + padding: const EdgeInsets.all(paddingStd), + child: Text( + stampTxt, + style: appTextTheme.headline3 + ?.copyWith(color: appTheme.colorScheme.inversePrimary), + ), + ), + ); + } +} diff --git a/lib/features/matching/presentation/pages/matched_screen.dart b/lib/features/matching/presentation/pages/matched_screen.dart new file mode 100644 index 0000000..e579b4d --- /dev/null +++ b/lib/features/matching/presentation/pages/matched_screen.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/core/utils/txt_helpers.dart'; +import 'package:redting/features/chat/presentation/pages/chat_screen.dart'; +import 'package:redting/features/matching/domain/models/matching_profiles.dart'; +import 'package:redting/features/matching/presentation/state/matches_listener/matches_listener_bloc.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_uneditable.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/theme.dart'; + +//TODO load more on scroll +class MatchedScreen extends StatefulWidget { + final UserProfile profile; + const MatchedScreen({Key? key, required this.profile}) : super(key: key); + + @override + State createState() => _MatchedScreenState(); +} + +class _MatchedScreenState extends State + with AutomaticKeepAliveClientMixin { + Stream>? _stream; + MatchesListenerBloc? _eventDispatcher; + + @override + bool get wantKeepAlive => true; + + final matchesScrollController = ScrollController(); + final Map _matchedProfiles = {}; + + @override + Widget build(BuildContext context) { + super.build(context); + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStates, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is MatchesListenerInitialState) { + _onInitState(blocContext); + } + return StreamBuilder>( + stream: _stream, + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data != null) { + _respondToDataChanges(snapshot.data!); + } + + return ListView.builder( + itemCount: _matchedProfiles.values.length, + physics: const BouncingScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return _matchToMessageWidget( + _matchedProfiles.values.toList()[index]); + }, + ); + }); + }))); + } + + void _listenToStates(BuildContext context, MatchesListenerState state) { + if (state is ListeningToMatchesState) { + setState(() { + _stream = state.stream; + }); + } + if (state is LoadingThisUserProfileFailedState) { + _showSnack(state.errMsg); + } + } + + void _onInitState(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(ListenToMatchesEvent()); + } + + @override + void dispose() { + _eventDispatcher?.close(); + super.dispose(); + } + + void _respondToDataChanges(List data) { + for (var realTimeResult in data) { + MatchingProfiles matchingProfile = + realTimeResult.data as MatchingProfiles; + switch (realTimeResult.realTimeEventType) { + case RealTimeEventType.added: + _matchedProfiles[matchingProfile.userAUserBIdsConcatNSorted] = + matchingProfile; + break; + + case RealTimeEventType.modified: + if (_matchedProfiles + .containsKey(matchingProfile.userAUserBIdsConcatNSorted)) { + _matchedProfiles[matchingProfile.userAUserBIdsConcatNSorted] = + matchingProfile; + } + break; + + case RealTimeEventType.deleted: + if (_matchedProfiles + .containsKey(matchingProfile.userAUserBIdsConcatNSorted)) { + _matchedProfiles.remove(matchingProfile.userAUserBIdsConcatNSorted); + } + break; + } + } + } + + Widget _matchToMessageWidget(MatchingProfiles profiles) { + List thisAndTheOtherUser = profiles.getMembers(); + final otherUser = thisAndTheOtherUser + .firstWhere((profile) => profile.userId != widget.profile.userId); + final thisUser = thisAndTheOtherUser + .firstWhere((profile) => profile.userId == widget.profile.userId); + final iceBreaker = profiles.iceBreakers[0]; + return Container( + key: ValueKey(otherUser.userId), + margin: const EdgeInsets.only(bottom: paddingMd), + child: InkWell( + splashColor: appTheme.colorScheme.primary, + radius: 8, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatScreen( + thisUser: thisUser, + thatUser: otherUser, + iceBreaker: iceBreaker)), + ); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + constraints: const BoxConstraints( + maxHeight: avatarRadiusSmall * 2.5, + maxWidth: avatarRadiusSmall * 2.5), + child: Center( + child: UneditableProfilePhoto( + isSmall: true, + profilePhoto: otherUser.userProfilePhoto))), + const SizedBox( + width: paddingStd, + ), + Expanded( + child: ConstrainedBox( + constraints: + const BoxConstraints(minHeight: avatarRadiusSmall * 2.5), + child: Align( + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + clipText(txt: otherUser.userName, maxChars: 24), + style: appTextTheme.subtitle1 + ?.copyWith(color: Colors.black87), + ), + const SizedBox( + width: paddingStd, + ), + Text( + clipWords( + txt: iceBreaker, + ), + style: appTextTheme.bodyText1 + ?.copyWith(color: Colors.black87)) + ], + ), + ), + ), + ) + ], + ), + ), + ); + } + + ///snack + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } +} diff --git a/lib/features/matching/presentation/pages/matching_screen.dart b/lib/features/matching/presentation/pages/matching_screen.dart new file mode 100644 index 0000000..61039d5 --- /dev/null +++ b/lib/features/matching/presentation/pages/matching_screen.dart @@ -0,0 +1,272 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/features/matching/presentation/components/idle_matching_card.dart'; +import 'package:redting/features/matching/presentation/components/loading_card.dart'; +import 'package:redting/features/matching/presentation/components/rate_app_card.dart'; +import 'package:redting/features/matching/presentation/components/swipeable_profile.dart'; +import 'package:redting/features/matching/presentation/state/matching_bloc.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class MatchingScreen extends StatefulWidget { + final UserProfile profile; + const MatchingScreen({Key? key, required this.profile}) : super(key: key); + + @override + State createState() => _MatchingScreenState(); +} + +class _MatchingScreenState extends State + with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + + MatchingBloc? _eventDispatcher; + bool _isLoading = false; + bool _loadedAllProfiles = false; + + /// WE LOAD PROFILES IN BATCHES + /// EACH TIME WE ADD THEM TO THE CURRENT SWIPE BATCH LIST [_currentSwipeBatchProfiles] IN REVERSE ORDER + /// WE ADD THEM IN REVERSE ORDER BECAUSE THEY ARE STACKED, SO THE LAST WILL BE SHOWN FIRST + /// THEN WE KEEP TRACK OF PASSED i.e. disliked profiles [_passedProfiles] + /// WHEN WE REACH THE END [_loadedAllProfiles], THE USER CAN VIEW THE DISLIKED PROFILES AGAIN + List _currentSwipeBatchProfiles = []; + final List _passedProfiles = []; + bool _todaysFeedbackReceived = false; + + @override + Widget build(BuildContext context) { + super.build(context); + + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocBuilder( + builder: (blocContext, state) { + if (state is MatchingInitialState) { + _onInitState(blocContext); + } + return BlocListener( + listener: _listenToStates, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (_isLoading) const LoadingCard(), + if (_currentSwipeBatchProfiles.isNotEmpty) _getSwipeAbleCards(), + if (_shouldRevisitPassedOnProfiles()) _showRefreshOption(), + if (_hasViewedAllProfiles()) + _getRateCard(state is SendingFeedbackState), + if (_nothingElseToDo()) const IdleMatchingCard() + ], + ), + ); + }), + ); + } + + void _listenToStates(BuildContext context, MatchingState state) { + if (state is InitializedMatchingState) { + setState(() { + _isLoading = false; + }); + _loadNextProfilesToMatchWithBatch(); + } + + if (state is InitializingMatchingFailedState) { + setState(() { + _isLoading = false; + }); + _showSnack(state.errMsg); + } + + if (state is LoadingState) { + if (mounted) { + setState(() { + _isLoading = true; + }); + } + } + if (state is FetchingMatchesFailedState) { + _showSnack(state.errMsg); + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + + if (state is FetchedProfilesToMatchState) { + if (mounted) { + setState(() { + _isLoading = false; + if (state.matchingProfiles.isNotEmpty) { + _currentSwipeBatchProfiles = + state.matchingProfiles.reversed.toList(); + } else { + _loadedAllProfiles = true; + } + }); + } + } + + /// liking user + if (state is LikingUserFailedState) { + if (mounted) { + setState(() { + _passedProfiles.add(state.likedUserProfile); + }); + } + } + + /// sending feedback + if (state is SendingFeedbackSuccessState) { + setState(() { + _todaysFeedbackReceived = true; + }); + _showSnack(feedbackReceived, isError: false); + } + + if (state is SendingFeedbackFailedState) { + _showSnack(feedbackNotSentErr, isError: false); + } + } + + void _onInitState(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(InitializeEvent()); + } + + void _loadNextProfilesToMatchWithBatch() { + _eventDispatcher?.add(LoadProfilesToMatchEvent(widget.profile)); + } + + /// CARDS + _getSwipeAbleCards() { + if (_currentSwipeBatchProfiles.isEmpty) return const SizedBox.shrink(); + return Stack( + children: _currentSwipeBatchProfiles.map((e) { + bool isFrontCard = _currentSwipeBatchProfiles.last.userId == e.userId; + return Align( + alignment: Alignment.topCenter, + child: SwipeProfile( + photoUrls: e.datingPhotos, + name: e.name, + age: e.age.toString(), + title: e.title, + bio: e.bio, + verificationVideo: e.getVerificationVideo(), + sexualOrientation: + e.makeMyOrientationPublic ? e.getUserSexualOrientation() : [], + isFrontCard: isFrontCard, + onSwiped: _onSwipe, + ), + ); + }).toList(), + ); + } + + _onSwipe(CardSwipeType gesture) { + if (mounted) { + if (_currentSwipeBatchProfiles.isEmpty) return; //safe check + final swipedProfile = _currentSwipeBatchProfiles.removeLast(); + if (_currentSwipeBatchProfiles.isEmpty) { + _loadNextProfilesToMatchWithBatch(); + } + //on like + if (gesture == CardSwipeType.like) { + setState(() { + _currentSwipeBatchProfiles = _currentSwipeBatchProfiles; + }); + _eventDispatcher?.add(LikeUserEvent( + likedByUser: widget.profile.userId, + likedUserProfile: swipedProfile)); + return; + } + //on pass + if (gesture == CardSwipeType.pass) { + _passedProfiles.add(swipedProfile); + setState(() { + _currentSwipeBatchProfiles = _currentSwipeBatchProfiles; + }); + return; + } + } + } + + /// RE VISIT PROFILES AGAIN + Widget _showRefreshOption() { + return Center( + child: IconButton( + icon: Icon( + Icons.refresh_rounded, + color: appTheme.colorScheme.primary, + ), + iconSize: 40, + onPressed: _onReVisitProfiles, + ), + ); + } + + void _onReVisitProfiles() { + if (mounted) { + _currentSwipeBatchProfiles.clear(); + _currentSwipeBatchProfiles.addAll(_passedProfiles); + _passedProfiles.clear(); + setState(() { + _currentSwipeBatchProfiles = _currentSwipeBatchProfiles; + }); + } + } + + void _onSubmitDailyFeedback(int rating, String feedback) { + _eventDispatcher?.add(SendUserFeedBackEvent( + rating: rating, feedback: feedback, userId: widget.profile.userId)); + } + + _hasViewedAllProfiles() { + return _passedProfiles.isEmpty && + _currentSwipeBatchProfiles.isEmpty && + _loadedAllProfiles && + !_todaysFeedbackReceived; + } + + _shouldRevisitPassedOnProfiles() { + return _passedProfiles.isNotEmpty && + _currentSwipeBatchProfiles.isEmpty && + _loadedAllProfiles; + } + + _nothingElseToDo() { + return _passedProfiles.isEmpty && + _currentSwipeBatchProfiles.isEmpty && + _loadedAllProfiles && + _todaysFeedbackReceived; + } + + @override + void dispose() { + _eventDispatcher?.close(); + super.dispose(); + } + + ///snack + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + Widget _getRateCard(bool isSendingFeedback) { + return RateAppCard( + isSendingFeedback: isSendingFeedback, + onSubmitFeedback: _onSubmitDailyFeedback, + ); + } +} diff --git a/lib/features/matching/presentation/state/matches_listener/matches_listener_bloc.dart b/lib/features/matching/presentation/state/matches_listener/matches_listener_bloc.dart new file mode 100644 index 0000000..00dea01 --- /dev/null +++ b/lib/features/matching/presentation/state/matches_listener/matches_listener_bloc.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/use_cases/matching_usecases.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/res/strings.dart'; + +part 'matches_listener_event.dart'; +part 'matches_listener_state.dart'; + +class MatchesListenerBloc + extends Bloc { + final MatchingUseCases matchingUseCases; + MatchesListenerBloc(this.matchingUseCases) + : super(MatchesListenerInitialState()) { + on(_onListenToMatchesEvent); + on(_onLoadThisUserProfileEvent); + } + + FutureOr _onListenToMatchesEvent( + ListenToMatchesEvent event, Emitter emit) { + var stream = matchingUseCases.listenToMatchUseCase.execute(); + emit(ListeningToMatchesState(stream)); + } + + FutureOr _onLoadThisUserProfileEvent(LoadThisUserProfileEvent event, + Emitter emit) async { + ServiceResult result = + await matchingUseCases.getThisUsersInfoUseCase.execute(); + + if (result.errorOccurred || result.data is! UserProfile) { + emit(LoadingThisUserProfileFailedState( + result.errorMessage ?? loadingAuthUserErr)); + } else { + emit(LoadedThisUserProfileState(result.data as UserProfile)); + } + } +} diff --git a/lib/features/matching/presentation/state/matches_listener/matches_listener_event.dart b/lib/features/matching/presentation/state/matches_listener/matches_listener_event.dart new file mode 100644 index 0000000..783e96b --- /dev/null +++ b/lib/features/matching/presentation/state/matches_listener/matches_listener_event.dart @@ -0,0 +1,8 @@ +part of 'matches_listener_bloc.dart'; + +@immutable +abstract class MatchesListenerEvent {} + +class ListenToMatchesEvent extends MatchesListenerEvent {} + +class LoadThisUserProfileEvent extends MatchesListenerEvent {} diff --git a/lib/features/matching/presentation/state/matches_listener/matches_listener_state.dart b/lib/features/matching/presentation/state/matches_listener/matches_listener_state.dart new file mode 100644 index 0000000..f9c7c02 --- /dev/null +++ b/lib/features/matching/presentation/state/matches_listener/matches_listener_state.dart @@ -0,0 +1,21 @@ +part of 'matches_listener_bloc.dart'; + +@immutable +abstract class MatchesListenerState {} + +class MatchesListenerInitialState extends MatchesListenerState {} + +class ListeningToMatchesState extends MatchesListenerState { + final Stream> stream; + ListeningToMatchesState(this.stream); +} + +class LoadedThisUserProfileState extends MatchesListenerState { + final UserProfile thisUserProfile; + LoadedThisUserProfileState(this.thisUserProfile); +} + +class LoadingThisUserProfileFailedState extends MatchesListenerState { + final String errMsg; + LoadingThisUserProfileFailedState(this.errMsg); +} diff --git a/lib/features/matching/presentation/state/matching_bloc.dart b/lib/features/matching/presentation/state/matching_bloc.dart new file mode 100644 index 0000000..27cc8fb --- /dev/null +++ b/lib/features/matching/presentation/state/matching_bloc.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/matching/domain/use_cases/matching_usecases.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/res/strings.dart'; + +part 'matching_event.dart'; +part 'matching_state.dart'; + +class MatchingBloc extends Bloc { + final MatchingUseCases matchingUseCases; + MatchingBloc(this.matchingUseCases) : super(MatchingInitialState()) { + on(_onInitializeEvent); + on(_onLoadProfilesEvent); + on(_onLikeUserEvent); + on(_onSendUserFeedBackEvent); + } + + FutureOr _onInitializeEvent( + InitializeEvent event, Emitter emit) async { + emit(LoadingState()); + + await matchingUseCases.syncWithRemote.execute(); + ServiceResult result = + await matchingUseCases.getThisUsersInfoUseCase.execute(); + if (result.errorOccurred || result.data is! UserProfile) { + emit(InitializingMatchingFailedState( + result.errorMessage ?? loadingAuthUserErr)); + } + + if (result.data is UserProfile) { + emit(InitializedMatchingState(result.data as UserProfile)); + } + } + + FutureOr _onLoadProfilesEvent( + LoadProfilesToMatchEvent event, Emitter emit) async { + emit(LoadingState()); + ServiceResult result = + await matchingUseCases.fetchProfilesToMatch.execute(event.profiles); + if (result.errorOccurred || result.data is! List) { + emit(FetchingMatchesFailedState( + result.errorMessage ?? fetchingProfilesToMatchFailed)); + } else { + emit(FetchedProfilesToMatchState(result.data as List)); + } + } + + FutureOr _onLikeUserEvent( + LikeUserEvent event, Emitter emit) async { + emit(LikingUserState()); + ServiceResult result = await matchingUseCases.likeUserUseCase.execute( + event.likedByUser, + event.likedUserProfile.userId, + event.likedUserProfile.name, + event.likedUserProfile.profilePhotoUrl); + if (result.errorOccurred) { + emit(LikingUserFailedState(event.likedUserProfile)); + } else { + emit(LikingUserSuccessState()); + } + } + + FutureOr _onSendUserFeedBackEvent( + SendUserFeedBackEvent event, Emitter emit) async { + emit(SendingFeedbackState()); + ServiceResult result = await matchingUseCases.sendUserDailyFeedback + .execute(event.userId, event.feedback, event.rating); + + if (result.errorOccurred) { + emit(SendingFeedbackFailedState()); + } + + if (!result.errorOccurred) { + emit(SendingFeedbackSuccessState()); + } + } +} diff --git a/lib/features/matching/presentation/state/matching_event.dart b/lib/features/matching/presentation/state/matching_event.dart new file mode 100644 index 0000000..7cdfec1 --- /dev/null +++ b/lib/features/matching/presentation/state/matching_event.dart @@ -0,0 +1,25 @@ +part of 'matching_bloc.dart'; + +@immutable +abstract class MatchingEvent {} + +class InitializeEvent extends MatchingEvent {} + +class LoadProfilesToMatchEvent extends MatchingEvent { + final UserProfile profiles; + LoadProfilesToMatchEvent(this.profiles); +} + +class LikeUserEvent extends MatchingEvent { + final String likedByUser; + final UserProfile likedUserProfile; + LikeUserEvent({required this.likedUserProfile, required this.likedByUser}); +} + +class SendUserFeedBackEvent extends MatchingEvent { + final int rating; + final String feedback; + final String userId; + SendUserFeedBackEvent( + {required this.rating, required this.feedback, required this.userId}); +} diff --git a/lib/features/matching/presentation/state/matching_state.dart b/lib/features/matching/presentation/state/matching_state.dart new file mode 100644 index 0000000..2e55386 --- /dev/null +++ b/lib/features/matching/presentation/state/matching_state.dart @@ -0,0 +1,46 @@ +part of 'matching_bloc.dart'; + +@immutable +abstract class MatchingState {} + +class MatchingInitialState extends MatchingState {} + +class LoadingState extends MatchingState {} + +class InitializedMatchingState extends MatchingState { + final UserProfile thisUserInfo; + InitializedMatchingState(this.thisUserInfo); +} + +class InitializingMatchingFailedState extends MatchingState { + final String errMsg; + InitializingMatchingFailedState(this.errMsg); +} + +class FetchedProfilesToMatchState extends MatchingState { + final List matchingProfiles; + FetchedProfilesToMatchState(this.matchingProfiles); +} + +class FetchingMatchesFailedState extends MatchingState { + final String errMsg; + FetchingMatchesFailedState(this.errMsg); +} + +/// LIKING USER +class LikingUserState extends MatchingState {} + +class LikingUserFailedState extends MatchingState { + //so you can undo like + final UserProfile likedUserProfile; + LikingUserFailedState(this.likedUserProfile); +} + +class LikingUserSuccessState extends MatchingState {} + +/// SENDING FEEDBACK +class SendingFeedbackState extends MatchingState {} + +class SendingFeedbackFailedState extends MatchingState {} + +class SendingFeedbackSuccessState extends MatchingState {} diff --git a/lib/features/profile/.DS_Store b/lib/features/profile/.DS_Store new file mode 100644 index 0000000..6220d1c Binary files /dev/null and b/lib/features/profile/.DS_Store differ diff --git a/lib/features/profile/data/.DS_Store b/lib/features/profile/data/.DS_Store new file mode 100644 index 0000000..da47741 Binary files /dev/null and b/lib/features/profile/data/.DS_Store differ diff --git a/lib/features/profile/data/data_sources/local/hive_profile.dart b/lib/features/profile/data/data_sources/local/hive_profile.dart new file mode 100644 index 0000000..ff22e7c --- /dev/null +++ b/lib/features/profile/data/data_sources/local/hive_profile.dart @@ -0,0 +1,66 @@ +import 'package:flutter/foundation.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_names.dart'; +import 'package:redting/features/profile/data/data_sources/local/local_profile_source.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +class UserProfileHive implements LocalProfileDataSource { + final Box _profileHiveBox = Hive.box(userProfileBox); + + @override + Future cacheAndReturnUserProfile( + {required UserProfile profile}) async { + try { + await _profileHiveBox.put(userProfileKey, profile); + return profile; + } catch (e) { + if (kDebugMode) { + print( + "============ cacheAndReturnUserProfile exception hive $e ==========="); + } + return null; + } + } + + @override + Future clearUserProfileCache() async { + try { + await _profileHiveBox.delete(userProfileKey); + return true; //success + } catch (e) { + if (kDebugMode) { + print( + "============ clearUserProfileCache exception hive $e ==========="); + } + return false; + } + } + + @override + Future updateUserProfileCache( + {required UserProfile profile}) async { + try { + bool isCleared = await clearUserProfileCache(); + if (!isCleared) return null; + return await cacheAndReturnUserProfile(profile: profile); + } catch (e) { + if (kDebugMode) { + print( + "============ updateUserProfileCache exception hive $e ==========="); + } + return null; + } + } + + @override + Future getCachedUserProfile() async { + try { + return await _profileHiveBox.get(userProfileKey) as UserProfile?; + } catch (e) { + if (kDebugMode) { + print("============ $e ==========="); + } + return null; + } + } +} diff --git a/lib/features/profile/data/data_sources/local/local_profile_source.dart b/lib/features/profile/data/data_sources/local/local_profile_source.dart new file mode 100644 index 0000000..864a673 --- /dev/null +++ b/lib/features/profile/data/data_sources/local/local_profile_source.dart @@ -0,0 +1,9 @@ +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +abstract class LocalProfileDataSource { + Future getCachedUserProfile(); + Future clearUserProfileCache(); + Future updateUserProfileCache({required UserProfile profile}); + Future cacheAndReturnUserProfile( + {required UserProfile profile}); +} diff --git a/lib/features/profile/data/data_sources/remote/fire_profile.dart b/lib/features/profile/data/data_sources/remote/fire_profile.dart new file mode 100644 index 0000000..5c23156 --- /dev/null +++ b/lib/features/profile/data/data_sources/remote/fire_profile.dart @@ -0,0 +1,216 @@ +import 'dart:io'; +import "dart:math"; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; +import 'package:redting/features/profile/data/data_sources/remote/remote_profile_source.dart'; +import 'package:redting/features/profile/data/entities/user_phones_entity.dart'; +import 'package:redting/features/profile/data/entities/user_profile_entity.dart'; +import 'package:redting/features/profile/data/entities/user_verification_video_entity.dart'; +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; +import 'package:redting/features/profile/data/utils/compressors/video_compressor.dart'; +import 'package:redting/features/profile/domain/models/user_phones.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/res/string_arrays.dart'; + +const String userProfileCollection = "users"; +const String archivedProfilesCollection = "archived_users"; +const String usersProfilePhotosBucket = "profilePhotos"; +const String thisUserProfilePhotosBucket = "myProfilePhotos"; +const String datingProfilesPhotosBucket = "datingProfilePhotos"; +const String thisUserDatingProfilePhotosBucket = "myDatingProfilePhotos"; + +const String verificationVideosCollection = "users_verification_videos"; +const String usersVerificationVideosBucket = "verificationVideos"; +const String thisUserVerificationVideosBucket = "myVerificationVideos"; + +const userPhonesCollection = "user_phones"; +const userPhonesCollectionIdField = "userId"; + +class FireProfile implements RemoteProfileDataSource { + final FirebaseAuth _auth = FirebaseAuth.instance; + final FirebaseStorage _storage = FirebaseStorage.instance; + final FirebaseFirestore _fireStore = FirebaseFirestore.instance; + + @override + Future createUserProfile({required UserProfile profile}) async { + try { + await _fireStore + .collection(userProfileCollection) + .doc(profile.userId) + .set(profile.toJson()); + //store phone - id map + UserPhone userPhone = UserPhoneEntity( + userId: profile.userId, phoneNumber: _auth.currentUser!.phoneNumber!); + await _fireStore + .collection(userPhonesCollection) + .doc(userPhone.phoneNumber) + .set(userPhone.toJson()); + + return profile; + } catch (e) { + if (kDebugMode) { + print("============== createUserProfile --fire store $e ============"); + } + return null; + } + } + + @override + Future getUserProfile() async { + try { + String? uid = _auth.currentUser?.uid; + if (uid == null) return null; + + var doc = + await _fireStore.collection(userProfileCollection).doc(uid).get(); + UserProfile? profile; + if (doc.data() != null) { + profile = UserProfileEntity.fromJson(doc.data()!); + } + return profile; + } catch (e) { + if (kDebugMode) { + print("============== getUserProfile fire store $e ============"); + } + return null; + } + } + + @override + Future updateUserProfile({required UserProfile profile}) async { + try { + await _fireStore + .collection(userProfileCollection) + .doc(profile.userId) + .update(profile.toJson()); + return profile; + } catch (e) { + if (kDebugMode) { + print("============== updateUserProfile fire store $e ============"); + } + return null; + } + } + + @override + Future uploadProfilePhoto( + {required File file, + required String filename, + required ImageCompressor imageCompressor}) async { + try { + final fileToUpload = + await imageCompressor.compressImageAndGetCompressedOrOg(file); + final storageRef = _storage.ref(); + final photoRef = storageRef.child( + "$usersProfilePhotosBucket/${_auth.currentUser!.uid}/$thisUserProfilePhotosBucket/${DateTime.now().millisecondsSinceEpoch.toString()}_$filename"); + await photoRef.putFile(fileToUpload); + String downloadUrl = await photoRef.getDownloadURL(); + return downloadUrl; + } catch (e) { + if (kDebugMode) { + print("============== uploadProfilePhoto fire store $e ============"); + } + return null; + } + } + + /// VERIFICATION VIDEO + @override + Future generateVerificationWord() async { + try { + final List possibleWordsLengths = [6, 8, 10]; //must be evens + + final random = Random(); + final int randomLength = + possibleWordsLengths[random.nextInt(possibleWordsLengths.length)]; + + String verificationWord = ""; + while (verificationWord.length < randomLength) { + String randomConsonant = consonants[random.nextInt(consonants.length)]; + String randomVowel = vowels[random.nextInt(vowels.length)]; + verificationWord = "$verificationWord$randomConsonant$randomVowel"; + } + return verificationWord; + } catch (e) { + if (kDebugMode) { + print( + "============== generateVerificationWord fire store $e ============"); + } + return null; + } + } + + @override + Future compressAndUploadVerificationVideo( + {required File file, + required String verificationCode, + required VideoCompressor compressor}) async { + try { + final fileToUpload = + await compressor.compressVideoReturnCompressedOrOg(file); + final storageRef = _storage.ref(); + final videoRef = storageRef.child( + "$usersVerificationVideosBucket/${_auth.currentUser!.uid}/$thisUserVerificationVideosBucket/${DateTime.now().millisecondsSinceEpoch.toString()}_vv_${verificationCode.trim()}"); + await videoRef.putFile(fileToUpload); + String verificationVideoUrl = await videoRef.getDownloadURL(); + UserVerificationVideoEntity usersVerificationVideo = + UserVerificationVideoEntity( + verificationCode: verificationCode, + videoUrl: verificationVideoUrl); + compressor.dispose(); + await _fireStore + .collection(verificationVideosCollection) + .doc(_auth.currentUser!.uid) + .set(usersVerificationVideo.toJson()); + return usersVerificationVideo; + } catch (e) { + if (kDebugMode) { + print( + "============== compressAndUploadVerificationVideo fire store $e ============"); + } + compressor.dispose(); + return null; + } + } + + @override + Future deleteVerificationVideo() async { + try { + await _fireStore + .collection(verificationVideosCollection) + .doc(_auth.currentUser!.uid) + .delete(); + return true; //success + } catch (e) { + if (kDebugMode) { + print( + "============== deleteVerificationVideo fire store $e ============"); + } + return false; // fail + } + } + + @override + Future uploadDatingPhoto(File photo, String filename, String userId, + ImageCompressor imageCompressor) async { + try { + final fileToUpload = + await imageCompressor.compressImageAndGetCompressedOrOg(photo); + final storageRef = _storage.ref(); + final photoRef = storageRef.child( + "$datingProfilesPhotosBucket/$userId/$thisUserDatingProfilePhotosBucket/${DateTime.now().millisecondsSinceEpoch.toString()}_$filename"); + await photoRef.putFile(fileToUpload); + String downloadUrl = await photoRef.getDownloadURL(); + return downloadUrl; + } catch (e) { + if (kDebugMode) { + print("===== uploadDatingPhoto firestore exception raised $e ===="); + } + return null; + } + } +} diff --git a/lib/features/profile/data/data_sources/remote/remote_profile_source.dart b/lib/features/profile/data/data_sources/remote/remote_profile_source.dart new file mode 100644 index 0000000..7dd7cef --- /dev/null +++ b/lib/features/profile/data/data_sources/remote/remote_profile_source.dart @@ -0,0 +1,24 @@ +import 'dart:io'; + +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; +import 'package:redting/features/profile/data/utils/compressors/video_compressor.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; + +abstract class RemoteProfileDataSource { + Future getUserProfile(); + Future updateUserProfile({required UserProfile profile}); + Future createUserProfile({required UserProfile profile}); + Future uploadProfilePhoto( + {required File file, + required String filename, + required ImageCompressor imageCompressor}); + Future compressAndUploadVerificationVideo( + {required File file, + required String verificationCode, + required VideoCompressor compressor}); + Future generateVerificationWord(); + Future deleteVerificationVideo(); + Future uploadDatingPhoto(File photo, String filename, String userId, + ImageCompressor imageCompressor); +} diff --git a/lib/features/profile/data/entities/sexual_orientation_entity.dart b/lib/features/profile/data/entities/sexual_orientation_entity.dart new file mode 100644 index 0000000..7ace494 --- /dev/null +++ b/lib/features/profile/data/entities/sexual_orientation_entity.dart @@ -0,0 +1,69 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; + +part 'sexual_orientation_entity.g.dart'; + +@HiveType(typeId: sexualOrientationTypeId) +enum SexualOrientationEntity { + @HiveField(0) + straight, + @HiveField(1) + gay, + @HiveField(2) + asexual, + @HiveField(3) + bisexual, + @HiveField(4) + demiSexual, + @HiveField(5) + panSexual, + @HiveField(6) + queer, + @HiveField(7) + questioning, +} + +SexualOrientation sexualOrientationEntityToModel( + SexualOrientationEntity entity) { + switch (entity) { + case SexualOrientationEntity.straight: + return SexualOrientation.straight; + case SexualOrientationEntity.gay: + return SexualOrientation.gay; + case SexualOrientationEntity.asexual: + return SexualOrientation.asexual; + case SexualOrientationEntity.bisexual: + return SexualOrientation.bisexual; + case SexualOrientationEntity.demiSexual: + return SexualOrientation.demiSexual; + case SexualOrientationEntity.panSexual: + return SexualOrientation.panSexual; + case SexualOrientationEntity.queer: + return SexualOrientation.queer; + case SexualOrientationEntity.questioning: + return SexualOrientation.questioning; + } +} + +SexualOrientationEntity sexualOrientationModelToEntity( + SexualOrientation model) { + switch (model) { + case SexualOrientation.straight: + return SexualOrientationEntity.straight; + case SexualOrientation.gay: + return SexualOrientationEntity.gay; + case SexualOrientation.asexual: + return SexualOrientationEntity.asexual; + case SexualOrientation.bisexual: + return SexualOrientationEntity.bisexual; + case SexualOrientation.demiSexual: + return SexualOrientationEntity.demiSexual; + case SexualOrientation.panSexual: + return SexualOrientationEntity.panSexual; + case SexualOrientation.queer: + return SexualOrientationEntity.queer; + case SexualOrientation.questioning: + return SexualOrientationEntity.questioning; + } +} diff --git a/lib/features/profile/data/entities/sexual_orientation_entity.g.dart b/lib/features/profile/data/entities/sexual_orientation_entity.g.dart new file mode 100644 index 0000000..6ec006c --- /dev/null +++ b/lib/features/profile/data/entities/sexual_orientation_entity.g.dart @@ -0,0 +1,77 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sexual_orientation_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class SexualOrientationEntityAdapter + extends TypeAdapter { + @override + final int typeId = 5; + + @override + SexualOrientationEntity read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return SexualOrientationEntity.straight; + case 1: + return SexualOrientationEntity.gay; + case 2: + return SexualOrientationEntity.asexual; + case 3: + return SexualOrientationEntity.bisexual; + case 4: + return SexualOrientationEntity.demiSexual; + case 5: + return SexualOrientationEntity.panSexual; + case 6: + return SexualOrientationEntity.queer; + case 7: + return SexualOrientationEntity.questioning; + default: + return SexualOrientationEntity.straight; + } + } + + @override + void write(BinaryWriter writer, SexualOrientationEntity obj) { + switch (obj) { + case SexualOrientationEntity.straight: + writer.writeByte(0); + break; + case SexualOrientationEntity.gay: + writer.writeByte(1); + break; + case SexualOrientationEntity.asexual: + writer.writeByte(2); + break; + case SexualOrientationEntity.bisexual: + writer.writeByte(3); + break; + case SexualOrientationEntity.demiSexual: + writer.writeByte(4); + break; + case SexualOrientationEntity.panSexual: + writer.writeByte(5); + break; + case SexualOrientationEntity.queer: + writer.writeByte(6); + break; + case SexualOrientationEntity.questioning: + writer.writeByte(7); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SexualOrientationEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/features/profile/data/entities/user_gender_entity.dart b/lib/features/profile/data/entities/user_gender_entity.dart new file mode 100644 index 0000000..2207083 --- /dev/null +++ b/lib/features/profile/data/entities/user_gender_entity.dart @@ -0,0 +1,37 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; + +part 'user_gender_entity.g.dart'; + +@HiveType(typeId: userGenderTypeId) +enum UserGenderEntity { + @HiveField(0) + male, + @HiveField(1) + female, + @HiveField(2) + stated +} + +UserGender genderEntityToGenderModel(UserGenderEntity genderEntity) { + switch (genderEntity) { + case UserGenderEntity.male: + return UserGender.male; + case UserGenderEntity.female: + return UserGender.female; + case UserGenderEntity.stated: + return UserGender.stated; + } +} + +UserGenderEntity genderModelToGenderEntity(UserGender gender) { + switch (gender) { + case UserGender.male: + return UserGenderEntity.male; + case UserGender.female: + return UserGenderEntity.female; + case UserGender.stated: + return UserGenderEntity.stated; + } +} diff --git a/lib/features/profile/data/entities/user_gender_entity.g.dart b/lib/features/profile/data/entities/user_gender_entity.g.dart new file mode 100644 index 0000000..970eeb8 --- /dev/null +++ b/lib/features/profile/data/entities/user_gender_entity.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_gender_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class UserGenderEntityAdapter extends TypeAdapter { + @override + final int typeId = 2; + + @override + UserGenderEntity read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return UserGenderEntity.male; + case 1: + return UserGenderEntity.female; + case 2: + return UserGenderEntity.stated; + default: + return UserGenderEntity.male; + } + } + + @override + void write(BinaryWriter writer, UserGenderEntity obj) { + switch (obj) { + case UserGenderEntity.male: + writer.writeByte(0); + break; + case UserGenderEntity.female: + writer.writeByte(1); + break; + case UserGenderEntity.stated: + writer.writeByte(2); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UserGenderEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/features/profile/data/entities/user_phones_entity.dart b/lib/features/profile/data/entities/user_phones_entity.dart new file mode 100644 index 0000000..785e605 --- /dev/null +++ b/lib/features/profile/data/entities/user_phones_entity.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/features/profile/domain/models/user_phones.dart'; + +part 'user_phones_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +class UserPhoneEntity implements UserPhone { + static const userPhonesCollectionIdFieldName = "userId"; + @override + String phoneNumber; + + @override + String userId; + + UserPhoneEntity({ + required this.userId, + required this.phoneNumber, + }); + + @override + factory UserPhoneEntity.fromJson(Map json) => + _$UserPhoneEntityFromJson(json); + + @override + Map toJson() => _$UserPhoneEntityToJson(this); +} diff --git a/lib/features/profile/data/entities/user_phones_entity.g.dart b/lib/features/profile/data/entities/user_phones_entity.g.dart new file mode 100644 index 0000000..ba137da --- /dev/null +++ b/lib/features/profile/data/entities/user_phones_entity.g.dart @@ -0,0 +1,12 @@ +part of 'user_phones_entity.dart'; + +UserPhoneEntity _$UserPhoneEntityFromJson(Map json) => UserPhoneEntity( + userId: json['userId'] as String, + phoneNumber: json['phoneNumber'] as String, + ); + +Map _$UserPhoneEntityToJson(UserPhoneEntity instance) => + { + 'userId': instance.userId, + 'phoneNumber': instance.phoneNumber, + }; diff --git a/lib/features/profile/data/entities/user_profile_entity.dart b/lib/features/profile/data/entities/user_profile_entity.dart new file mode 100644 index 0000000..585c891 --- /dev/null +++ b/lib/features/profile/data/entities/user_profile_entity.dart @@ -0,0 +1,194 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/features/profile/data/entities/sexual_orientation_entity.dart'; +import 'package:redting/features/profile/data/entities/user_gender_entity.dart'; +import 'package:redting/features/profile/data/entities/user_verification_video_entity.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; + +part 'user_profile_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +@HiveType(typeId: userProfileTypeId) +class UserProfileEntity implements UserProfile { + static String ageFieldName = "age"; + static String genderFieldName = "gender"; + static String isBannedFieldName = "isBanned"; + static String genderPreferencesFieldName = "genderPreferences"; + static String sexualOrientationFieldName = "sexualOrientation"; + static String onlyShowMeOthersOfSameOrientationFieldName = + "onlyShowMeOthersOfSameOrientation"; + static String minAgePreferenceFieldName = "minAgePreference"; + static String maxAgePreferenceFieldName = "maxAgePreference"; + + @HiveField(0) + @override + int age; + + @HiveField(1) + @override + String bio; + + @HiveField(2) + @override + DateTime birthDay; + + @HiveField(3) + @override + DateTime createdOn; + + @HiveField(4) + @override + List datingPhotos; + + @HiveField(5) + @override + String? genderOther; + + @HiveField(6) + @override + bool isBanned; + + @HiveField(7) + @override + DateTime lastUpdatedOn; + + @HiveField(8) + @override + bool makeMyOrientationPublic; + + @HiveField(9) + @override + int maxAgePreference; + + @HiveField(10) + @override + int minAgePreference; + + @HiveField(11) + @override + String name; + + @HiveField(12) + @override + bool onlyShowMeOthersOfSameOrientation; + + @HiveField(13) + @override + String profilePhotoUrl; + + @HiveField(14) + @override + String registerCountry; + + @HiveField(15) + @override + String title; + + @HiveField(16) + @override + String userId; + + @HiveField(17) + List sexualOrientation; + + @HiveField(18) + UserGenderEntity gender; + + @HiveField(19) + UserGenderEntity? genderPreferences; + + @HiveField(20) + UserVerificationVideoEntity verificationVideo; + + UserProfileEntity( + {required this.genderOther, + required this.gender, + required this.name, + required this.profilePhotoUrl, + required this.registerCountry, + required this.title, + required this.isBanned, + required this.age, + required this.bio, + required this.birthDay, + required this.createdOn, + required this.verificationVideo, + required this.lastUpdatedOn, + this.makeMyOrientationPublic = false, + this.onlyShowMeOthersOfSameOrientation = true, + this.maxAgePreference = 60, + this.minAgePreference = 18, + List? datingPhotos, + List? sexualOrientation, + this.userId = "", + this.genderPreferences}) + : datingPhotos = datingPhotos ?? [], + sexualOrientation = + sexualOrientation ?? [SexualOrientationEntity.straight]; + + @override + UserGender getGender() { + return genderEntityToGenderModel(gender); + } + + @override + UserGender? getGenderPreferences() { + if (genderPreferences == null) return null; + return genderEntityToGenderModel(genderPreferences!); + } + + @override + List getUserSexualOrientation() { + return sexualOrientation + .map((e) => sexualOrientationEntityToModel(e)) + .toList(); + } + + @override + UserVerificationVideo getVerificationVideo() { + return verificationVideo; + } + + @override + bool isSameAs(UserProfile user) { + return userId == user.userId; + } + + @override + factory UserProfileEntity.fromJson(Map json) => + _$UserProfileEntityFromJson(json); + + @override + Map toJson() => _$UserProfileEntityToJson(this); + + @override + UserProfile fromJson(Map json) { + return UserProfileEntity.fromJson(json); + } + + @override + setGender(UserGender genderModel) { + gender = genderModelToGenderEntity(genderModel); + } + + @override + setGenderPreferences(UserGender? gender) { + genderPreferences = + gender != null ? genderModelToGenderEntity(gender) : null; + } + + @override + setUserSexualOrientation(List orientation) { + sexualOrientation = + orientation.map((e) => sexualOrientationModelToEntity(e)).toList(); + } + + @override + setVerificationVideo(UserVerificationVideo video) { + verificationVideo = mapUserVerificationVideoModelToEntity(video); + } +} diff --git a/lib/features/profile/data/entities/user_profile_entity.g.dart b/lib/features/profile/data/entities/user_profile_entity.g.dart new file mode 100644 index 0000000..f5e02e7 --- /dev/null +++ b/lib/features/profile/data/entities/user_profile_entity.g.dart @@ -0,0 +1,200 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_profile_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class UserProfileEntityAdapter extends TypeAdapter { + @override + final int typeId = 3; + + @override + UserProfileEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return UserProfileEntity( + genderOther: fields[5] as String?, + gender: fields[18] as UserGenderEntity, + name: fields[11] as String, + profilePhotoUrl: fields[13] as String, + registerCountry: fields[14] as String, + title: fields[15] as String, + isBanned: fields[6] as bool, + age: fields[0] as int, + bio: fields[1] as String, + birthDay: fields[2] as DateTime, + createdOn: fields[3] as DateTime, + verificationVideo: fields[20] as UserVerificationVideoEntity, + lastUpdatedOn: fields[7] as DateTime, + makeMyOrientationPublic: fields[8] as bool, + onlyShowMeOthersOfSameOrientation: fields[12] as bool, + maxAgePreference: fields[9] as int, + minAgePreference: fields[10] as int, + datingPhotos: (fields[4] as List?)?.cast(), + sexualOrientation: (fields[17] as List?)?.cast(), + userId: fields[16] as String, + genderPreferences: fields[19] as UserGenderEntity?, + ); + } + + @override + void write(BinaryWriter writer, UserProfileEntity obj) { + writer + ..writeByte(21) + ..writeByte(0) + ..write(obj.age) + ..writeByte(1) + ..write(obj.bio) + ..writeByte(2) + ..write(obj.birthDay) + ..writeByte(3) + ..write(obj.createdOn) + ..writeByte(4) + ..write(obj.datingPhotos) + ..writeByte(5) + ..write(obj.genderOther) + ..writeByte(6) + ..write(obj.isBanned) + ..writeByte(7) + ..write(obj.lastUpdatedOn) + ..writeByte(8) + ..write(obj.makeMyOrientationPublic) + ..writeByte(9) + ..write(obj.maxAgePreference) + ..writeByte(10) + ..write(obj.minAgePreference) + ..writeByte(11) + ..write(obj.name) + ..writeByte(12) + ..write(obj.onlyShowMeOthersOfSameOrientation) + ..writeByte(13) + ..write(obj.profilePhotoUrl) + ..writeByte(14) + ..write(obj.registerCountry) + ..writeByte(15) + ..write(obj.title) + ..writeByte(16) + ..write(obj.userId) + ..writeByte(17) + ..write(obj.sexualOrientation) + ..writeByte(18) + ..write(obj.gender) + ..writeByte(19) + ..write(obj.genderPreferences) + ..writeByte(20) + ..write(obj.verificationVideo); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UserProfileEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserProfileEntity _$UserProfileEntityFromJson(Map json) => UserProfileEntity( + genderOther: json['genderOther'] as String?, + gender: $enumDecode(_$UserGenderEntityEnumMap, json['gender']), + name: json['name'] as String, + profilePhotoUrl: json['profilePhotoUrl'] as String, + registerCountry: json['registerCountry'] as String, + title: json['title'] as String, + isBanned: json['isBanned'] as bool, + age: json['age'] as int, + bio: json['bio'] as String, + birthDay: DateTime.parse(json['birthDay'] as String), + createdOn: DateTime.parse(json['createdOn'] as String), + verificationVideo: UserVerificationVideoEntity.fromJson( + Map.from(json['verificationVideo'] as Map)), + lastUpdatedOn: DateTime.parse(json['lastUpdatedOn'] as String), + makeMyOrientationPublic: + json['makeMyOrientationPublic'] as bool? ?? false, + onlyShowMeOthersOfSameOrientation: + json['onlyShowMeOthersOfSameOrientation'] as bool? ?? true, + maxAgePreference: json['maxAgePreference'] as int? ?? 60, + minAgePreference: json['minAgePreference'] as int? ?? 18, + datingPhotos: (json['datingPhotos'] as List?) + ?.map((e) => e as String) + .toList(), + sexualOrientation: (json['sexualOrientation'] as List?) + ?.map((e) => $enumDecode(_$SexualOrientationEntityEnumMap, e)) + .toList(), + userId: json['userId'] as String? ?? "", + genderPreferences: $enumDecodeNullable( + _$UserGenderEntityEnumMap, json['genderPreferences']), + ); + +Map _$UserProfileEntityToJson(UserProfileEntity instance) => + { + 'age': instance.age, + 'bio': instance.bio, + 'birthDay': instance.birthDay.toIso8601String(), + 'createdOn': instance.createdOn.toIso8601String(), + 'datingPhotos': instance.datingPhotos, + 'genderOther': instance.genderOther, + 'isBanned': instance.isBanned, + 'lastUpdatedOn': instance.lastUpdatedOn.toIso8601String(), + 'makeMyOrientationPublic': instance.makeMyOrientationPublic, + 'maxAgePreference': instance.maxAgePreference, + 'minAgePreference': instance.minAgePreference, + 'name': instance.name, + 'onlyShowMeOthersOfSameOrientation': + instance.onlyShowMeOthersOfSameOrientation, + 'profilePhotoUrl': instance.profilePhotoUrl, + 'registerCountry': instance.registerCountry, + 'title': instance.title, + 'userId': instance.userId, + 'sexualOrientation': instance.sexualOrientation + .map((e) => _$SexualOrientationEntityEnumMap[e]!) + .toList(), + 'gender': _$UserGenderEntityEnumMap[instance.gender]!, + 'genderPreferences': + _$UserGenderEntityEnumMap[instance.genderPreferences], + 'verificationVideo': instance.verificationVideo.toJson(), + }; + +const _$UserGenderEntityEnumMap = { + UserGenderEntity.male: 'male', + UserGenderEntity.female: 'female', + UserGenderEntity.stated: 'stated', +}; + +const _$SexualOrientationEntityEnumMap = { + SexualOrientationEntity.straight: 'straight', + SexualOrientationEntity.gay: 'gay', + SexualOrientationEntity.asexual: 'asexual', + SexualOrientationEntity.bisexual: 'bisexual', + SexualOrientationEntity.demiSexual: 'demiSexual', + SexualOrientationEntity.panSexual: 'panSexual', + SexualOrientationEntity.queer: 'queer', + SexualOrientationEntity.questioning: 'questioning', +}; + +const _$UserGenderModelEnumMap = { + UserGender.male: 'male', + UserGender.female: 'female', + UserGender.stated: 'stated', +}; + +const _$SexualOrientationModelEnumMap = { + SexualOrientation.straight: 'straight', + SexualOrientation.gay: 'gay', + SexualOrientation.asexual: 'asexual', + SexualOrientation.bisexual: 'bisexual', + SexualOrientation.demiSexual: 'demiSexual', + SexualOrientation.panSexual: 'panSexual', + SexualOrientation.queer: 'queer', + SexualOrientation.questioning: 'questioning', +}; diff --git a/lib/features/profile/data/entities/user_verification_video_entity.dart b/lib/features/profile/data/entities/user_verification_video_entity.dart new file mode 100644 index 0000000..74bd5f2 --- /dev/null +++ b/lib/features/profile/data/entities/user_verification_video_entity.dart @@ -0,0 +1,37 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:redting/core/data/hive_type_ids.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; + +part 'user_verification_video_entity.g.dart'; + +@JsonSerializable(anyMap: true, explicitToJson: true) +@HiveType(typeId: userVerificationVideoTypeId) +class UserVerificationVideoEntity implements UserVerificationVideo { + @HiveField(0) + @override + String verificationCode; + + @HiveField(1) + @override + String videoUrl; + + UserVerificationVideoEntity( + {required this.verificationCode, required this.videoUrl}); + + factory UserVerificationVideoEntity.fromJson(Map json) => + _$UserVerificationVideoEntityFromJson(json); + Map toJson() => _$UserVerificationVideoEntityToJson(this); + + @override + UserVerificationVideo fromJson(Map json) { + return UserVerificationVideoEntity.fromJson(json); + } +} + +UserVerificationVideoEntity mapUserVerificationVideoModelToEntity( + UserVerificationVideo verificationVideo) { + return UserVerificationVideoEntity( + verificationCode: verificationVideo.verificationCode, + videoUrl: verificationVideo.videoUrl); +} diff --git a/lib/features/profile/data/entities/user_verification_video_entity.g.dart b/lib/features/profile/data/entities/user_verification_video_entity.g.dart new file mode 100644 index 0000000..0df8152 --- /dev/null +++ b/lib/features/profile/data/entities/user_verification_video_entity.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_verification_video_entity.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class UserVerificationVideoEntityAdapter + extends TypeAdapter { + @override + final int typeId = 4; + + @override + UserVerificationVideoEntity read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return UserVerificationVideoEntity( + verificationCode: fields[0] as String, + videoUrl: fields[1] as String, + ); + } + + @override + void write(BinaryWriter writer, UserVerificationVideoEntity obj) { + writer + ..writeByte(2) + ..writeByte(0) + ..write(obj.verificationCode) + ..writeByte(1) + ..write(obj.videoUrl); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UserVerificationVideoEntityAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserVerificationVideoEntity _$UserVerificationVideoEntityFromJson(Map json) => + UserVerificationVideoEntity( + verificationCode: json['verificationCode'] as String, + videoUrl: json['videoUrl'] as String, + ); + +Map _$UserVerificationVideoEntityToJson( + UserVerificationVideoEntity instance) => + { + 'verificationCode': instance.verificationCode, + 'videoUrl': instance.videoUrl, + }; diff --git a/lib/features/profile/data/repositories/profile_repository_impl.dart b/lib/features/profile/data/repositories/profile_repository_impl.dart new file mode 100644 index 0000000..32a4cf3 --- /dev/null +++ b/lib/features/profile/data/repositories/profile_repository_impl.dart @@ -0,0 +1,361 @@ +import 'dart:io'; + +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/data/data_sources/local/local_profile_source.dart'; +import 'package:redting/features/profile/data/data_sources/remote/remote_profile_source.dart'; +import 'package:redting/features/profile/data/entities/user_gender_entity.dart'; +import 'package:redting/features/profile/data/entities/user_profile_entity.dart'; +import 'package:redting/features/profile/data/entities/user_verification_video_entity.dart'; +import 'package:redting/features/profile/data/utils/compressors/image_compressor.dart'; +import 'package:redting/features/profile/data/utils/compressors/video_compressor.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; +import 'package:redting/res/strings.dart'; + +class ProfileRepositoryImpl implements ProfileRepository { + final RemoteProfileDataSource remoteProfileDataSource; + final LocalProfileDataSource localProfileDataSource; + final VideoCompressor videoCompressor; + final ImageCompressor imageCompressor; + + ProfileRepositoryImpl( + {required this.remoteProfileDataSource, + required this.localProfileDataSource, + required this.videoCompressor, + required this.imageCompressor}); + + @override + Future createUserProfile( + {required String name, + required String userId, + required String profilePhotoUrl, + String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime? birthDay, + required String registerCountry, + required UserVerificationVideo? verificationVideo}) async { + //check the data + + if (name.isEmpty) { + return ServiceResult(errorOccurred: true, errorMessage: nameMissingErr); + } + if (userId.isEmpty) { + return ServiceResult( + errorOccurred: true, + errorMessage: userIdMissingDuringProfileCreateErr); + } + if (profilePhotoUrl.isEmpty) { + return ServiceResult( + errorOccurred: true, errorMessage: emptyProfilePhotoErr); + } + if (verificationVideo == null) { + return ServiceResult( + errorOccurred: true, errorMessage: noVerificationVideo); + } + + if (!UserProfile.isValidGender(gender: gender, genderOther: genderOther)) { + return ServiceResult( + errorOccurred: true, errorMessage: noGenderSpecified); + } + if ((bio.isEmpty || bio.length < UserProfile.userBioMinLen)) { + return ServiceResult(errorOccurred: true, errorMessage: bioIsEmptyErr); + } + if (title.isEmpty || title.length < UserProfile.userTitleMinLen) { + return ServiceResult(errorOccurred: true, errorMessage: titleIsEmptyErr); + } + if (title.length > UserProfile.userTitleMaxLen) { + return ServiceResult( + errorOccurred: true, errorMessage: titleIsTooLongErr); + } + if (!UserProfile.isOfLegalAge(birthDay: birthDay)) { + return ServiceResult(errorOccurred: true, errorMessage: bDayNotSet); + } + + UserProfile profile = _createNewUserProfileInstance( + name: name, + userId: userId, + profilePhotoUrl: profilePhotoUrl, + gender: gender, + genderOther: genderOther, + bio: bio, + registerCountry: registerCountry, + title: title, + birthDay: birthDay!, + isBanned: false, + verificationVideo: verificationVideo); + + //remote + UserProfile? savedProfile = + await remoteProfileDataSource.createUserProfile(profile: profile); + if (savedProfile == null) { + return ServiceResult( + errorOccurred: true, errorMessage: createProfileError); + } + + return await _cacheUserProfileAndReturn(savedProfile); + } + + Future _cacheUserProfileAndReturn(UserProfile profile) async { + UserProfile? cachedProfile = await localProfileDataSource + .cacheAndReturnUserProfile(profile: profile); + return ServiceResult( + errorOccurred: (cachedProfile == null), + errorMessage: (cachedProfile == null) ? createProfileError : '', + data: cachedProfile); + } + + @override + Future loadUserProfileFromRemoteIfExists() async { + //remote + final UserProfile? profile = await remoteProfileDataSource.getUserProfile(); + + if (profile == null) return ServiceResult(); //does not exist + + //update cache + return _cacheUserProfileAndReturn(profile); + } + + @override + Future uploadProfilePhoto( + {required File file, required String filename}) async { + String? downloadUrl = await remoteProfileDataSource.uploadProfilePhoto( + file: file, filename: filename, imageCompressor: imageCompressor); + return ServiceResult(errorOccurred: downloadUrl == null, data: downloadUrl); + } + + @override + Future generateVerificationWord() async { + String? verificationWord = + await remoteProfileDataSource.generateVerificationWord(); + return ServiceResult( + errorOccurred: verificationWord == null, data: verificationWord); + } + + @override + Future uploadVerificationVideo( + {required File file, required String verificationCode}) async { + UserVerificationVideo? video = + await remoteProfileDataSource.compressAndUploadVerificationVideo( + file: file, + verificationCode: verificationCode, + compressor: videoCompressor); + return ServiceResult(errorOccurred: video == null, data: video); + } + + @override + Future deleteVerificationVideo() async { + bool isSuccess = await remoteProfileDataSource.deleteVerificationVideo(); + return ServiceResult(errorOccurred: !isSuccess); + } + + @override + Future getCachedUserProfile() { + return localProfileDataSource.getCachedUserProfile(); + } + + @override + Future addDatingPhoto( + File photo, String filename, String userId) async { + String? downloadUrl = await remoteProfileDataSource.uploadDatingPhoto( + photo, filename, userId, imageCompressor); + return downloadUrl != null + ? ServiceResult(data: downloadUrl) + : ServiceResult( + errorOccurred: true, errorMessage: uploadingDatingProfilePhotoErr); + } + + /// DATING PROFILE + @override + Future setDatingInfo( + UserProfile profile, + List datingPics, + int minAgePreference, + int maxAgePreference, + UserGender? genderPreference, + List userOrientation, + bool makeMyOrientationPublic, + bool onlyShowMeOthersOfSameOrientation) async { + if (datingPics.length < minDatingProfilePhotosRequired) { + return ServiceResult( + errorOccurred: true, errorMessage: datingProfilePicsMissingErr); + } + + //upload the files + List newDatingPicsUrls = []; + for (DatingPic dPic in datingPics) { + if (dPic.file != null) { + //there is a file to upload + File file = dPic.file!; + String name = dPic.fileName ?? "${DateTime.now()}.jpeg"; + ServiceResult result = await addDatingPhoto(file, name, profile.userId); + if (result.data is String) { + newDatingPicsUrls.add(result.data); + } + } else { + if (dPic.photoUrl != null) { + newDatingPicsUrls.add(dPic.photoUrl!); + } + } + } + + if (userOrientation.isEmpty) { + //default orientation + userOrientation.add(SexualOrientation.straight); + } + + profile.datingPhotos = newDatingPicsUrls; + profile.makeMyOrientationPublic = makeMyOrientationPublic; + profile.onlyShowMeOthersOfSameOrientation = + onlyShowMeOthersOfSameOrientation; + profile.minAgePreference = minAgePreference; + profile.maxAgePreference = maxAgePreference; + profile.setGenderPreferences(genderPreference); + profile.setUserSexualOrientation(userOrientation); + + UserProfile? updatedProfile = + await remoteProfileDataSource.updateUserProfile(profile: profile); + if (updatedProfile == null) { + return ServiceResult(errorOccurred: true, errorMessage: setDatingInfoErr); + } + return _cacheUserProfileAndReturn(updatedProfile); + } + + @override + Future updateUserProfile({ + required UserProfile profile, + required String name, + required String profilePhotoUrl, + required String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime birthDay, + required String registerCountry, + }) async { + if (name.isEmpty) { + return ServiceResult(errorOccurred: true, errorMessage: nameMissingErr); + } + if (profilePhotoUrl.isEmpty) { + return ServiceResult( + errorOccurred: true, errorMessage: emptyProfilePhotoErr); + } + + if (!UserProfile.isValidGender(gender: gender, genderOther: genderOther)) { + return ServiceResult( + errorOccurred: true, errorMessage: noGenderSpecified); + } + if ((bio.isEmpty || bio.length < UserProfile.userBioMinLen)) { + return ServiceResult(errorOccurred: true, errorMessage: bioIsEmptyErr); + } + if (title.isEmpty || title.length < UserProfile.userTitleMinLen) { + return ServiceResult(errorOccurred: true, errorMessage: titleIsEmptyErr); + } + if (title.length > UserProfile.userTitleMaxLen) { + return ServiceResult( + errorOccurred: true, errorMessage: titleIsTooLongErr); + } + if (!UserProfile.isOfLegalAge(birthDay: birthDay)) { + return ServiceResult(errorOccurred: true, errorMessage: bDayNotSet); + } + + UserProfile updatedProfile = _createUpdatedProfileInstance( + oldProfile: profile, + name: name, + profilePhotoUrl: profilePhotoUrl, + genderOther: genderOther, + gender: gender, + bio: bio, + title: title, + birthDay: birthDay, + registerCountry: registerCountry); + //remote + UserProfile? newProfile = await remoteProfileDataSource.updateUserProfile( + profile: updatedProfile); + if (newProfile == null) { + return ServiceResult( + errorOccurred: true, errorMessage: updateProfileError); + } + return await _cacheUserProfileAndReturn(newProfile); + } + + /// INSTANCES + + UserProfile _createUpdatedProfileInstance({ + required UserProfile oldProfile, + required String name, + required String profilePhotoUrl, + required String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime birthDay, + required String registerCountry, + }) { + int age = DateTime.now().year - birthDay.year; + UserProfile updatedProfile = UserProfileEntity( + name: name, + userId: oldProfile.userId, + profilePhotoUrl: profilePhotoUrl, + gender: genderModelToGenderEntity(gender), + genderOther: genderOther, + bio: bio, + registerCountry: registerCountry, + title: title, + createdOn: oldProfile.createdOn, + lastUpdatedOn: DateTime.now(), + birthDay: birthDay, + isBanned: oldProfile.isBanned, + age: age, + verificationVideo: mapUserVerificationVideoModelToEntity( + oldProfile.getVerificationVideo()), + ); + updatedProfile.datingPhotos = oldProfile.datingPhotos; + updatedProfile.makeMyOrientationPublic = oldProfile.makeMyOrientationPublic; + updatedProfile.onlyShowMeOthersOfSameOrientation = + oldProfile.onlyShowMeOthersOfSameOrientation; + updatedProfile.minAgePreference = oldProfile.minAgePreference; + updatedProfile.maxAgePreference = oldProfile.maxAgePreference; + updatedProfile.setGenderPreferences(oldProfile.getGenderPreferences()); + updatedProfile + .setUserSexualOrientation(oldProfile.getUserSexualOrientation()); + return updatedProfile; + } + + UserProfile _createNewUserProfileInstance( + {required String name, + required String userId, + required String profilePhotoUrl, + String? genderOther, + required UserGender gender, + required String bio, + required String registerCountry, + required String title, + required DateTime birthDay, + bool isBanned = false, + required UserVerificationVideo verificationVideo}) { + int age = DateTime.now().year - birthDay.year; + return UserProfileEntity( + name: name, + userId: userId, + profilePhotoUrl: profilePhotoUrl, + gender: genderModelToGenderEntity(gender), + genderOther: genderOther, + bio: bio, + registerCountry: registerCountry, + title: title, + createdOn: DateTime.now(), + lastUpdatedOn: DateTime.now(), + birthDay: birthDay, + isBanned: isBanned, + age: age, + verificationVideo: + mapUserVerificationVideoModelToEntity(verificationVideo)); + } +} diff --git a/lib/features/profile/data/utils/compressors/image_compressor.dart b/lib/features/profile/data/utils/compressors/image_compressor.dart new file mode 100644 index 0000000..89a5f05 --- /dev/null +++ b/lib/features/profile/data/utils/compressors/image_compressor.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +abstract class ImageCompressor { + Future compressImageAndGetCompressedOrOg(File file); +} + +class ImageCompressorImpl implements ImageCompressor { + @override + Future compressImageAndGetCompressedOrOg(File file) async { + try { + final targetPath = p.join((await getTemporaryDirectory()).path, + '${DateTime.now()}.${p.extension(file.path)}'); + var compressedFile = await FlutterImageCompress.compressAndGetFile( + file.absolute.path, + targetPath, + autoCorrectionAngle: true, + quality: 70, + numberOfRetries: 1, + minHeight: 720, + minWidth: 720, + ); + + return compressedFile ?? file; + } catch (e) { + return file; + } + } +} diff --git a/lib/features/profile/data/utils/compressors/video_compressor.dart b/lib/features/profile/data/utils/compressors/video_compressor.dart new file mode 100644 index 0000000..cb0f4ac --- /dev/null +++ b/lib/features/profile/data/utils/compressors/video_compressor.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:video_compress/video_compress.dart'; + +abstract class VideoCompressor { + Future compressVideoReturnCompressedOrOg(File file); + dispose(); +} + +class VideoCompressorImpl implements VideoCompressor { + @override + Future compressVideoReturnCompressedOrOg(File file) async { + try { + MediaInfo? info = await VideoCompress.compressVideo( + file.path, + quality: VideoQuality.Res1280x720Quality, + includeAudio: true, + ); + if (info != null && info.path != null) { + return File(info.path!); + } else { + return file; + } + } catch (_) { + VideoCompress.cancelCompression(); + return file; + } + } + + @override + dispose() async { + await VideoCompress.deleteAllCache(); + } +} diff --git a/lib/features/profile/data/utils/enum_mappers.dart b/lib/features/profile/data/utils/enum_mappers.dart new file mode 100644 index 0000000..acb3501 --- /dev/null +++ b/lib/features/profile/data/utils/enum_mappers.dart @@ -0,0 +1,14 @@ +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/data/entities/user_gender_entity.dart'; + +const userGenderToStringVal = { + UserGender.male: 'male', + UserGender.female: 'female', + UserGender.stated: 'stated', +}; + +const userGenderEntityToStringVal = { + UserGenderEntity.male: 'male', + UserGenderEntity.female: 'female', + UserGenderEntity.stated: 'stated', +}; diff --git a/lib/features/profile/data/utils/fire_verification_result.dart b/lib/features/profile/data/utils/fire_verification_result.dart new file mode 100644 index 0000000..2eaf94f --- /dev/null +++ b/lib/features/profile/data/utils/fire_verification_result.dart @@ -0,0 +1,33 @@ +import 'package:redting/features/auth/data/utils/phone_verification_result.dart'; + +class FireAuthSendCodeResult implements PhoneVerificationResult { + @override + dynamic credential; + + @override + String? errMsg; + + @override + bool errorOccurred; + + @override + bool isAutoVerified; + + @override + bool isCodeSent; + + @override + int? resendToken; + + @override + String? verificationId; + + FireAuthSendCodeResult( + {this.credential, + this.errMsg, + this.errorOccurred = false, + this.isAutoVerified = false, + this.isCodeSent = false, + this.resendToken, + this.verificationId}); +} diff --git a/lib/features/profile/data/utils/phone_verification_result.dart b/lib/features/profile/data/utils/phone_verification_result.dart new file mode 100644 index 0000000..68ad0be --- /dev/null +++ b/lib/features/profile/data/utils/phone_verification_result.dart @@ -0,0 +1,18 @@ +abstract class PhoneVerificationResult { + dynamic credential; + String? verificationId; + int? resendToken; + bool isCodeSent; + bool isAutoVerified; + bool errorOccurred; + String? errMsg; + + PhoneVerificationResult( + {this.errorOccurred = false, + this.errMsg, + this.resendToken, + this.isCodeSent = false, + this.verificationId, + this.isAutoVerified = false, + this.credential}); +} diff --git a/lib/features/profile/di/profile_di.dart b/lib/features/profile/di/profile_di.dart new file mode 100644 index 0000000..2b172f6 --- /dev/null +++ b/lib/features/profile/di/profile_di.dart @@ -0,0 +1,90 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/profile/data/data_sources/local/hive_profile.dart'; +import 'package:redting/features/profile/data/data_sources/local/local_profile_source.dart'; +import 'package:redting/features/profile/data/data_sources/remote/fire_profile.dart'; +import 'package:redting/features/profile/data/data_sources/remote/remote_profile_source.dart'; +import 'package:redting/features/profile/data/repositories/profile_repository_impl.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/create_profile_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/get_cached_profile_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/get_profile_from_remote_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/set_dating_info_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/update_user_profile_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile_photo/add_dating_pic.dart'; +import 'package:redting/features/profile/domain/use_cases/profile_photo/upload_profile_photo_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile_usecases.dart'; +import 'package:redting/features/profile/domain/use_cases/verification_video/delete_verification_video_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/verification_video/generate_verification_video_code_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/verification_video/upload_verification_video_usecase.dart'; +import 'package:redting/features/profile/presentation/state/user_profile_bloc.dart'; + +/// FACTORY - instantiated every time we request +/// SINGLETON - only a single instance is created + +GetIt init() { + final GetIt diInstance = GetIt.instance; + //auth bloc + diInstance.registerFactory( + () => UserProfileBloc(profileUseCases: diInstance())); + + //useCases + diInstance.registerLazySingleton(() => ProfileUseCases( + createProfileUseCase: diInstance(), + getProfileFromRemoteUseCase: diInstance(), + uploadProfilePhotoUseCase: diInstance(), + generateVideoVerificationCodeUseCase: diInstance(), + uploadVerificationVideoUseCase: diInstance(), + deleteVerificationVideoUseCase: diInstance(), + getCachedProfileUseCase: diInstance(), + setDatingInfoUseCase: diInstance(), + addDatingPicUseCase: diInstance(), + updateUserProfileUseCase: diInstance())); + + diInstance.registerLazySingleton( + () => GetProfileFromRemoteUseCase(profileRepository: diInstance())); + + diInstance.registerLazySingleton( + () => CreateProfileUseCase(profileRepository: diInstance())); + + diInstance.registerLazySingleton( + () => UploadProfilePhotoUseCase(profileRepository: diInstance())); + + diInstance.registerLazySingleton(() => + GenerateVideoVerificationCodeUseCase(profileRepository: diInstance())); + + diInstance.registerLazySingleton( + () => GetCachedProfileUseCase(profileRepository: diInstance())); + + diInstance.registerLazySingleton( + () => UploadVerificationVideoUseCase(profileRepository: diInstance())); + + diInstance.registerLazySingleton( + () => DeleteVerificationVideoUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => UpdateUserProfileUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => SetDatingInfoUseCase(diInstance())); + diInstance.registerLazySingleton( + () => AddDatingPicUseCase(diInstance())); + + //repository + diInstance + .registerLazySingleton(() => ProfileRepositoryImpl( + remoteProfileDataSource: diInstance(), + imageCompressor: diInstance(), + localProfileDataSource: diInstance(), + videoCompressor: diInstance(), + )); + + //remote data source + diInstance + .registerLazySingleton(() => FireProfile()); + + //local data source + diInstance + .registerLazySingleton(() => UserProfileHive()); + + return diInstance; +} diff --git a/lib/features/profile/domain/models/sexual_orientation.dart b/lib/features/profile/domain/models/sexual_orientation.dart new file mode 100644 index 0000000..03d8ec4 --- /dev/null +++ b/lib/features/profile/domain/models/sexual_orientation.dart @@ -0,0 +1,10 @@ +enum SexualOrientation { + straight, + gay, + asexual, + bisexual, + demiSexual, + panSexual, + queer, + questioning, +} diff --git a/lib/features/profile/domain/models/user_gender.dart b/lib/features/profile/domain/models/user_gender.dart new file mode 100644 index 0000000..8d58b29 --- /dev/null +++ b/lib/features/profile/domain/models/user_gender.dart @@ -0,0 +1 @@ +enum UserGender { male, female, stated } diff --git a/lib/features/profile/domain/models/user_phones.dart b/lib/features/profile/domain/models/user_phones.dart new file mode 100644 index 0000000..e663f97 --- /dev/null +++ b/lib/features/profile/domain/models/user_phones.dart @@ -0,0 +1,9 @@ +abstract class UserPhone { + String userId; + String phoneNumber; + UserPhone({ + required this.userId, + required this.phoneNumber, + }); + Map toJson(); +} diff --git a/lib/features/profile/domain/models/user_profile.dart b/lib/features/profile/domain/models/user_profile.dart new file mode 100644 index 0000000..32e6c5d --- /dev/null +++ b/lib/features/profile/domain/models/user_profile.dart @@ -0,0 +1,78 @@ +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; + +abstract class UserProfile { + String name; + String userId; + String profilePhotoUrl; + String? genderOther; + String bio; + String registerCountry; + String title; + + DateTime createdOn; + + DateTime lastUpdatedOn; + + DateTime birthDay; + + bool isBanned; + int age; + + List datingPhotos; + int minAgePreference; + int maxAgePreference; + bool makeMyOrientationPublic; + bool onlyShowMeOthersOfSameOrientation; + + static const int userBioMinLen = 20; + static const int userBioMaxLen = 120; + static const int userTitleMinLen = 4; + static const int userTitleMaxLen = 40; + + UserProfile( + {required this.name, + required this.userId, + required this.profilePhotoUrl, + this.genderOther, + required this.bio, + required this.registerCountry, + required this.title, + required this.createdOn, + required this.lastUpdatedOn, + required this.birthDay, + required this.isBanned, + required this.age, + required this.datingPhotos, + required this.minAgePreference, + required this.maxAgePreference, + this.makeMyOrientationPublic = true, + this.onlyShowMeOthersOfSameOrientation = true}); + + bool isSameAs(UserProfile user); + UserProfile fromJson(Map json); + Map toJson(); + + static bool isOfLegalAge({DateTime? birthDay}) { + if (birthDay == null) return false; + return (DateTime.now().year - birthDay.year) >= 18; + } + + static bool isValidGender({required UserGender gender, String? genderOther}) { + if (gender != UserGender.stated) return true; + return (genderOther != null) && + (genderOther.trim().length > 2) && + (genderOther.isEmpty == false); + } + + /// OTHER ENUM DATA + UserGender getGender(); + UserVerificationVideo getVerificationVideo(); + UserGender? getGenderPreferences(); + List getUserSexualOrientation(); + setVerificationVideo(UserVerificationVideo video); + setGenderPreferences(UserGender? genderPreference); + setUserSexualOrientation(List orientation); + setGender(UserGender gender); +} diff --git a/lib/features/profile/domain/models/user_verification_video.dart b/lib/features/profile/domain/models/user_verification_video.dart new file mode 100644 index 0000000..926eae0 --- /dev/null +++ b/lib/features/profile/domain/models/user_verification_video.dart @@ -0,0 +1,8 @@ +abstract class UserVerificationVideo { + final String videoUrl; + final String verificationCode; + UserVerificationVideo( + {required this.videoUrl, required this.verificationCode}); + UserVerificationVideo fromJson(Map json); + Map toJson(); +} diff --git a/lib/features/profile/domain/repositories/profile_repository.dart b/lib/features/profile/domain/repositories/profile_repository.dart new file mode 100644 index 0000000..dd0fd6b --- /dev/null +++ b/lib/features/profile/domain/repositories/profile_repository.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; + +abstract class ProfileRepository { + Future loadUserProfileFromRemoteIfExists(); + Future getCachedUserProfile(); + + Future createUserProfile({ + required String name, + required String userId, + required String profilePhotoUrl, + String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime? birthDay, + required String registerCountry, + required UserVerificationVideo? verificationVideo, + }); + + Future uploadProfilePhoto({ + required File file, + required String filename, + }); + + Future uploadVerificationVideo( + {required File file, required String verificationCode}); + Future generateVerificationWord(); + Future deleteVerificationVideo(); + + Future addDatingPhoto( + File photo, String filename, String userId); + Future setDatingInfo( + UserProfile profile, + List datingPics, + int minAgePreference, + int maxAgePreference, + UserGender? genderPreference, + List userOrientation, + bool makeMyOrientationPublic, + bool onlyShowMeOthersOfSameOrientation); + + Future updateUserProfile({ + required UserProfile profile, + required String name, + required String profilePhotoUrl, + required String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime birthDay, + required String registerCountry, + }); +} diff --git a/lib/features/profile/domain/use_cases/profile/create_profile_usecase.dart b/lib/features/profile/domain/use_cases/profile/create_profile_usecase.dart new file mode 100644 index 0000000..ee088a7 --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile/create_profile_usecase.dart @@ -0,0 +1,34 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class CreateProfileUseCase { + final ProfileRepository profileRepository; + CreateProfileUseCase({required this.profileRepository}); + + Future execute({ + required String name, + required String userId, + required String profilePhotoUrl, + String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime? birthDay, + required String registerCountry, + required UserVerificationVideo? verificationVideo, + }) async { + return await profileRepository.createUserProfile( + name: name, + userId: userId, + profilePhotoUrl: profilePhotoUrl, + gender: gender, + genderOther: genderOther, + bio: bio, + title: title, + registerCountry: registerCountry, + birthDay: birthDay, + verificationVideo: verificationVideo); + } +} diff --git a/lib/features/profile/domain/use_cases/profile/get_cached_profile_usecase.dart b/lib/features/profile/domain/use_cases/profile/get_cached_profile_usecase.dart new file mode 100644 index 0000000..51c642c --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile/get_cached_profile_usecase.dart @@ -0,0 +1,11 @@ +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class GetCachedProfileUseCase { + final ProfileRepository profileRepository; + GetCachedProfileUseCase({required this.profileRepository}); + + Future execute() async { + return await profileRepository.getCachedUserProfile(); + } +} diff --git a/lib/features/profile/domain/use_cases/profile/get_profile_from_remote_usecase.dart b/lib/features/profile/domain/use_cases/profile/get_profile_from_remote_usecase.dart new file mode 100644 index 0000000..484971b --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile/get_profile_from_remote_usecase.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class GetProfileFromRemoteUseCase { + final ProfileRepository profileRepository; + GetProfileFromRemoteUseCase({required this.profileRepository}); + + Future execute() async { + return await profileRepository.loadUserProfileFromRemoteIfExists(); + } +} diff --git a/lib/features/profile/domain/use_cases/profile/set_dating_info_usecase.dart b/lib/features/profile/domain/use_cases/profile/set_dating_info_usecase.dart new file mode 100644 index 0000000..57a930b --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile/set_dating_info_usecase.dart @@ -0,0 +1,33 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; + +class SetDatingInfoUseCase { + final ProfileRepository repository; + SetDatingInfoUseCase( + this.repository, + ); + + Future execute( + UserProfile profile, + List datingPics, + int minAgePreference, + int maxAgePreference, + UserGender? genderPreference, + List userOrientation, + bool makeMyOrientationPublic, + bool onlyShowMeOthersOfSameOrientation) async { + return await repository.setDatingInfo( + profile, + datingPics, + minAgePreference, + maxAgePreference, + genderPreference, + userOrientation, + makeMyOrientationPublic, + onlyShowMeOthersOfSameOrientation); + } +} diff --git a/lib/features/profile/domain/use_cases/profile/update_user_profile_usecase.dart b/lib/features/profile/domain/use_cases/profile/update_user_profile_usecase.dart new file mode 100644 index 0000000..d46edb0 --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile/update_user_profile_usecase.dart @@ -0,0 +1,32 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class UpdateUserProfileUseCase { + final ProfileRepository repository; + UpdateUserProfileUseCase(this.repository); + + Future execute({ + required UserProfile profile, + required String name, + required String profilePhotoUrl, + required String? genderOther, + required UserGender gender, + required String bio, + required String title, + required DateTime birthDay, + required String registerCountry, + }) async { + return await repository.updateUserProfile( + profile: profile, + name: name, + profilePhotoUrl: profilePhotoUrl, + genderOther: genderOther, + gender: gender, + bio: bio, + title: title, + birthDay: birthDay, + registerCountry: registerCountry); + } +} diff --git a/lib/features/profile/domain/use_cases/profile_photo/add_dating_pic.dart b/lib/features/profile/domain/use_cases/profile_photo/add_dating_pic.dart new file mode 100644 index 0000000..1b0083b --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile_photo/add_dating_pic.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class AddDatingPicUseCase { + final ProfileRepository repository; + AddDatingPicUseCase( + this.repository, + ); + + Future execute( + File photo, String filename, String userId) async { + return await repository.addDatingPhoto(photo, filename, userId); + } +} diff --git a/lib/features/profile/domain/use_cases/profile_photo/upload_profile_photo_usecase.dart b/lib/features/profile/domain/use_cases/profile_photo/upload_profile_photo_usecase.dart new file mode 100644 index 0000000..a9eace6 --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile_photo/upload_profile_photo_usecase.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class UploadProfilePhotoUseCase { + final ProfileRepository profileRepository; + UploadProfilePhotoUseCase({required this.profileRepository}); + + Future execute( + {required File file, required String filename}) async { + return await profileRepository.uploadProfilePhoto( + file: file, filename: filename); + } +} diff --git a/lib/features/profile/domain/use_cases/profile_usecases.dart b/lib/features/profile/domain/use_cases/profile_usecases.dart new file mode 100644 index 0000000..ddc2721 --- /dev/null +++ b/lib/features/profile/domain/use_cases/profile_usecases.dart @@ -0,0 +1,35 @@ +import 'package:redting/features/profile/domain/use_cases/profile/create_profile_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/get_cached_profile_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/get_profile_from_remote_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/set_dating_info_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile/update_user_profile_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/profile_photo/add_dating_pic.dart'; +import 'package:redting/features/profile/domain/use_cases/profile_photo/upload_profile_photo_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/verification_video/delete_verification_video_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/verification_video/generate_verification_video_code_usecase.dart'; +import 'package:redting/features/profile/domain/use_cases/verification_video/upload_verification_video_usecase.dart'; + +class ProfileUseCases { + final CreateProfileUseCase createProfileUseCase; + final GetProfileFromRemoteUseCase getProfileFromRemoteUseCase; + final UploadProfilePhotoUseCase uploadProfilePhotoUseCase; + final GenerateVideoVerificationCodeUseCase + generateVideoVerificationCodeUseCase; + final UploadVerificationVideoUseCase uploadVerificationVideoUseCase; + final DeleteVerificationVideoUseCase deleteVerificationVideoUseCase; + final GetCachedProfileUseCase getCachedProfileUseCase; + final AddDatingPicUseCase addDatingPicUseCase; + final SetDatingInfoUseCase setDatingInfoUseCase; + final UpdateUserProfileUseCase updateUserProfileUseCase; + ProfileUseCases( + {required this.createProfileUseCase, + required this.getProfileFromRemoteUseCase, + required this.uploadProfilePhotoUseCase, + required this.uploadVerificationVideoUseCase, + required this.generateVideoVerificationCodeUseCase, + required this.deleteVerificationVideoUseCase, + required this.getCachedProfileUseCase, + required this.addDatingPicUseCase, + required this.setDatingInfoUseCase, + required this.updateUserProfileUseCase}); +} diff --git a/lib/features/profile/domain/use_cases/verification_video/delete_verification_video_usecase.dart b/lib/features/profile/domain/use_cases/verification_video/delete_verification_video_usecase.dart new file mode 100644 index 0000000..30d9598 --- /dev/null +++ b/lib/features/profile/domain/use_cases/verification_video/delete_verification_video_usecase.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class DeleteVerificationVideoUseCase { + final ProfileRepository profileRepository; + DeleteVerificationVideoUseCase(this.profileRepository); + + Future execute() async { + return await profileRepository.deleteVerificationVideo(); + } +} diff --git a/lib/features/profile/domain/use_cases/verification_video/generate_verification_video_code_usecase.dart b/lib/features/profile/domain/use_cases/verification_video/generate_verification_video_code_usecase.dart new file mode 100644 index 0000000..838e635 --- /dev/null +++ b/lib/features/profile/domain/use_cases/verification_video/generate_verification_video_code_usecase.dart @@ -0,0 +1,11 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class GenerateVideoVerificationCodeUseCase { + final ProfileRepository profileRepository; + GenerateVideoVerificationCodeUseCase({required this.profileRepository}); + + Future execute() async { + return await profileRepository.generateVerificationWord(); + } +} diff --git a/lib/features/profile/domain/use_cases/verification_video/upload_verification_video_usecase.dart b/lib/features/profile/domain/use_cases/verification_video/upload_verification_video_usecase.dart new file mode 100644 index 0000000..f508227 --- /dev/null +++ b/lib/features/profile/domain/use_cases/verification_video/upload_verification_video_usecase.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; + +class UploadVerificationVideoUseCase { + final ProfileRepository profileRepository; + UploadVerificationVideoUseCase({required this.profileRepository}); + + Future execute( + {required File file, required String verificationCode}) async { + return await profileRepository.uploadVerificationVideo( + file: file, verificationCode: verificationCode); + } +} diff --git a/lib/features/profile/domain/utils/dating_pic.dart b/lib/features/profile/domain/utils/dating_pic.dart new file mode 100644 index 0000000..d6bf79e --- /dev/null +++ b/lib/features/profile/domain/utils/dating_pic.dart @@ -0,0 +1,9 @@ +import 'dart:io'; + +class DatingPic { + String? fileName; + File? file; + String? photoUrl; + int position; + DatingPic({this.fileName, this.file, this.photoUrl, required this.position}); +} diff --git a/lib/features/profile/presentation/components/age_preference_slider.dart b/lib/features/profile/presentation/components/age_preference_slider.dart new file mode 100644 index 0000000..26133db --- /dev/null +++ b/lib/features/profile/presentation/components/age_preference_slider.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class AgePreferenceSlider extends StatefulWidget { + final Function(int from, int to) onAgeRangeSet; + final double fromAge, toAge; + const AgePreferenceSlider( + {Key? key, + required this.onAgeRangeSet, + required this.fromAge, + required this.toAge}) + : super(key: key); + + @override + State createState() => _AgeSliderState(); +} + +class _AgeSliderState extends State { + late RangeValues _currentRangeAge; + + @override + void initState() { + _currentRangeAge = RangeValues(widget.fromAge, widget.toAge); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "$ageRangeLbl ${_currentRangeAge.start.round().toString()} - ${_currentRangeAge.end.round().toString()}", + style: appTextTheme.subtitle1?.copyWith(color: Colors.black), + ), + const SizedBox( + height: paddingStd, + ), + SliderTheme( + data: SliderThemeData( + valueIndicatorTextStyle: appTextTheme.headline6, + valueIndicatorColor: + appTheme.colorScheme.primary.withOpacity(0.6), + showValueIndicator: ShowValueIndicator.never), + child: RangeSlider( + values: _currentRangeAge, + max: 100, + divisions: (100 - 18), + min: 18, + labels: RangeLabels( + _currentRangeAge.start.round().toString(), + _currentRangeAge.end.round().toString(), + ), + onChanged: (RangeValues values) { + setState(() { + _currentRangeAge = values; + }); + widget.onAgeRangeSet(values.start.round(), values.end.round()); + }, + ), + ) + ], + ); + } +} diff --git a/lib/features/profile/presentation/components/app_bars/build_edit_profile_appbar.dart b/lib/features/profile/presentation/components/app_bars/build_edit_profile_appbar.dart new file mode 100644 index 0000000..7f43ffd --- /dev/null +++ b/lib/features/profile/presentation/components/app_bars/build_edit_profile_appbar.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/assets_paths.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/theme.dart'; + +PreferredSizeWidget buildEditProfileAppBar( + {required VoidCallback onSaveProfile}) { + return AppBar( + toolbarHeight: appBarHeight, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + redLogoPath, + height: appBarHeight - (paddingStd * 2), + fit: BoxFit.fitHeight, + ), + ]), + actions: [ + IconButton( + iconSize: 32, + onPressed: onSaveProfile, + icon: Icon( + Icons.check_circle, + size: 32, + color: appTheme.colorScheme.primary, + )) + ], + ); +} diff --git a/lib/features/profile/presentation/components/dating_pics.dart b/lib/features/profile/presentation/components/dating_pics.dart new file mode 100644 index 0000000..77ff807 --- /dev/null +++ b/lib/features/profile/presentation/components/dating_pics.dart @@ -0,0 +1,153 @@ +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class DatingPicsWidget extends StatefulWidget { + final List datingPics; + final bool disableClick; + final Function(String errMsg) onError; + final Function(int pos) onRemoveFile; + final Function(File newFile, String filename, int photoNum) onChange; + const DatingPicsWidget({ + Key? key, + required this.datingPics, + required this.disableClick, + required this.onError, + required this.onChange, + required this.onRemoveFile, + }) : super(key: key); + + @override + State createState() => _DatingPicsWidgetState(); +} + +class _DatingPicsWidgetState extends State { + final double thumbnailHeight = datingProfilePhotoSizeHeight / 4; + final double thumbnailWidth = datingProfilePhotoSizeWidth / 2; + final ImagePicker picker = ImagePicker(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingStd), + child: Wrap( + runAlignment: WrapAlignment.spaceEvenly, + alignment: WrapAlignment.spaceEvenly, + children: _getProfilePhotosAsThumbnails(), + ), + ); + } + + List _getProfilePhotosAsThumbnails() { + int i = 0; + List thumbnails = []; + while (i < maxDatingProfilePhotos) { + thumbnails.add(_getThumbnail(pos: i)); + i++; + } + return thumbnails; + } + + Widget _getThumbnail({required int pos}) { + File? localImgFile; + String? existingPhoto; + if (pos < maxDatingProfilePhotos) { + if (widget.datingPics.length > pos) { + localImgFile = widget.datingPics[pos].file; + existingPhoto = widget.datingPics[pos].photoUrl; + } + } + return SizedBox( + width: thumbnailWidth + paddingMd, + height: thumbnailHeight + paddingMd, + child: Stack(children: [ + Container( + width: thumbnailWidth, + height: thumbnailHeight, + decoration: BoxDecoration( + color: appTheme.colorScheme.inversePrimary.withOpacity(0.2), + borderRadius: BorderRadius.circular(12)), + child: AspectRatio( + aspectRatio: thumbnailWidth / thumbnailHeight, + child: localImgFile != null + ? Image.file( + localImgFile, + fit: BoxFit.cover, + ) + : existingPhoto != null + ? CachedNetworkImage( + imageUrl: existingPhoto, + fit: BoxFit.cover, + errorWidget: (_, __, ___) { + return Center( + child: Icon( + Icons.image_not_supported_outlined, + size: 32, + color: appTheme.colorScheme.inversePrimary + .withOpacity(0.2), + ), + ); + }, + progressIndicatorBuilder: (_, __, ___) { + return const Center(child: CircularProgress()); + }, + ) + : const SizedBox.shrink(), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Material( + elevation: paddingStd, + color: appTheme.colorScheme.primaryContainer, + shape: const CircleBorder(side: BorderSide.none), + child: IconButton( + iconSize: 24, + onPressed: () { + if (localImgFile == null && existingPhoto == null) { + _addPhoto(photoNum: pos); + } else { + widget.onRemoveFile(pos); + } + }, + icon: Icon( + (localImgFile != null || existingPhoto != null) + ? Icons.cancel + : Icons.add_circle_outlined, + size: 32, + color: appTheme.colorScheme.primary.withOpacity(0.7), + ), + ), + ), + ) + ]), + ); + } + + void _addPhoto({required int photoNum}) async { + try { + if (widget.disableClick) return; + final XFile? pickedFile = await picker.pickImage( + source: ImageSource.gallery, + imageQuality: 100, + ); + + File? newFile = pickedFile != null ? File(pickedFile.path) : null; + String? filename = pickedFile?.name; + if (newFile != null && filename != null) { + widget.onChange(newFile, filename, photoNum); + } + } catch (e) { + widget.onError(errPickingPhotoGallery); + } + } +} diff --git a/lib/features/profile/presentation/components/gender_preferences.dart b/lib/features/profile/presentation/components/gender_preferences.dart new file mode 100644 index 0000000..ec2fb22 --- /dev/null +++ b/lib/features/profile/presentation/components/gender_preferences.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class GenderPreferences extends StatefulWidget { + final UserGender? initialGender; + final Function(UserGender? value) onChangeGender; + const GenderPreferences( + {Key? key, this.initialGender, required this.onChangeGender}) + : super(key: key); + + @override + State createState() => _GenderPreferencesState(); +} + +class _GenderPreferencesState extends State { + UserGender? _gender; + @override + void initState() { + _gender = widget.initialGender; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + showMeGendersAndPreferencesLbl, + style: appTextTheme.subtitle1?.copyWith(color: Colors.black), + ), + const SizedBox( + height: paddingStd, + ), + Wrap( + runAlignment: WrapAlignment.spaceBetween, + alignment: WrapAlignment.spaceBetween, + children: [ + _getGender(value: UserGender.male, lbl: menLbl), + _getGender(value: UserGender.female, lbl: womenLbl), + _getGender(value: null, lbl: allGendersLbl), + ], + ) + ]); + } + + Widget _getGender({required UserGender? value, required String lbl}) { + return InkWell( + splashColor: appTheme.colorScheme.secondary.withOpacity(0.1), + radius: 8.0, + borderRadius: BorderRadius.circular(8.0), + onTap: () { + setState(() { + _gender = value; + }); + widget.onChangeGender(value); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: _gender == value + ? appTheme.colorScheme.primary + : Colors.black26, + width: 1.0)), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + lbl, + style: appTextTheme.bodyText1?.copyWith( + color: _gender == value + ? appTheme.colorScheme.primary + : Colors.black54, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + } +} diff --git a/lib/features/profile/presentation/components/profile_photo_editor.dart b/lib/features/profile/presentation/components/profile_photo_editor.dart new file mode 100644 index 0000000..5f0c8ba --- /dev/null +++ b/lib/features/profile/presentation/components/profile_photo_editor.dart @@ -0,0 +1,78 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_circular_container.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_loading_placeholder.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_uneditable.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class ProfilePhotoEditor extends StatelessWidget { + final String? profilePhoto; + final Function(File? file, String? filename) onChange; + final Function(String errMsg) onError; + final bool isLoading; + final File? localPhoto; + const ProfilePhotoEditor({ + Key? key, + this.profilePhoto, + required this.onChange, + required this.isLoading, + required this.localPhoto, + required this.onError, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + splashColor: appTheme.colorScheme.secondary.withOpacity(0.2), + borderRadius: BorderRadius.circular(avatarRadiusLg / 2), + radius: avatarRadiusLg, + onTap: _onPickImage, + child: Center(child: _getDisplayImage()), + ); + } + + void _onPickImage() async { + try { + if (isLoading) return; + final ImagePicker picker = ImagePicker(); + final XFile? pickedFile = await picker.pickImage( + source: ImageSource.gallery, + imageQuality: 100, + ); + + File? newFile = pickedFile != null ? File(pickedFile.path) : null; + String? filename = pickedFile?.name; + onChange(newFile, filename); + } catch (e) { + onError(errPickingPhotoGallery); + } + } + + Widget _getDisplayImage() { + if (profilePhoto != null) { + return UneditableProfilePhoto( + profilePhoto: profilePhoto!, + placeholderImageProvider: + (localPhoto != null ? FileImage(localPhoto!) : null), + ); + } + + if (localPhoto != null) { + //upload in progress + return ProfilePhotoLoadingPlaceholder( + imgProvider: localPhoto != null ? FileImage(localPhoto!) : null, + ); + } + + return ProfilePhotoCircularContainer( + child: Icon( + Icons.person_add_alt, + size: avatarRadiusLg / 1.5, + color: appTheme.colorScheme.primary, + )); + } +} diff --git a/lib/features/profile/presentation/components/screen_containers/edit_profile_container.dart b/lib/features/profile/presentation/components/screen_containers/edit_profile_container.dart new file mode 100644 index 0000000..9f73446 --- /dev/null +++ b/lib/features/profile/presentation/components/screen_containers/edit_profile_container.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/features/profile/presentation/components/app_bars/build_edit_profile_appbar.dart'; +import 'package:redting/res/dimens.dart'; + +class EditProfileContainer extends StatelessWidget { + final VoidCallback onSaveProfile; + final Widget child; + const EditProfileContainer( + {Key? key, required this.onSaveProfile, required this.child}) + : super(key: key); + + @override + Widget build(BuildContext context) { + var screenHeight = MediaQuery.of(context).size.height; + var screenWidth = MediaQuery.of(context).size.width; + return ScaffoldWrapper( + child: Scaffold( + extendBodyBehindAppBar: false, + appBar: buildEditProfileAppBar(onSaveProfile: onSaveProfile), + body: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Container( + decoration: + BoxDecoration(gradient: threeColorOpaqueGradientTB), + constraints: BoxConstraints( + minWidth: screenWidth, minHeight: screenHeight), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingStd), + child: child))))); + ; + } +} diff --git a/lib/features/profile/presentation/components/sexual_preferences.dart b/lib/features/profile/presentation/components/sexual_preferences.dart new file mode 100644 index 0000000..1454e4b --- /dev/null +++ b/lib/features/profile/presentation/components/sexual_preferences.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/string_maps.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class SexualPreferences extends StatefulWidget { + final List orientationPreferences; + final Function(List orientationPreferences) + onUpdatedPreferences; + final bool makeMyOrientationPublic; + final bool showMeMyOrientationOnly; + final Function(bool value) onUpdateRestrictions; + final Function(bool value) onUpdateVisibility; + const SexualPreferences( + {Key? key, + required this.orientationPreferences, + required this.onUpdatedPreferences, + required this.onUpdateRestrictions, + required this.onUpdateVisibility, + required this.makeMyOrientationPublic, + required this.showMeMyOrientationOnly}) + : super(key: key); + + @override + State createState() => _SexualPreferencesState(); +} + +class _SexualPreferencesState extends State { + Map _myPreferences = {}; + bool _makeMyOrientationPublic = true; + bool _showMeMyOrientationOnly = true; + + @override + void initState() { + _myPreferences.clear(); + _myPreferences.addEntries( + widget.orientationPreferences.map((e) => MapEntry(e, true))); + _makeMyOrientationPublic = widget.makeMyOrientationPublic; + _showMeMyOrientationOnly = widget.showMeMyOrientationOnly; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ListBody( + children: [ + const SizedBox( + height: paddingMd, + ), + Text( + mySexualOrientation, + style: appTextTheme.subtitle1?.copyWith(color: Colors.black), + ), + ..._getPreferences(), + const SizedBox( + height: paddingStd, + ), + SwitchListTile( + visualDensity: VisualDensity.compact, + dense: true, + activeColor: appTheme.colorScheme.primary, + title: Text( + makeMyOrientationPublicLbl, + style: appTextTheme.bodyText1, + ), + value: _makeMyOrientationPublic, + onChanged: (bool value) { + setState(() { + _makeMyOrientationPublic = value; + }); + widget.onUpdateVisibility(value); + }, + ), + const SizedBox( + height: paddingStd, + ), + SwitchListTile( + visualDensity: VisualDensity.compact, + dense: true, + activeColor: appTheme.colorScheme.primary, + title: Text( + showMeMyOrientationOnly, + style: appTextTheme.bodyText1, + ), + value: _showMeMyOrientationOnly, + onChanged: (bool value) { + setState(() { + _showMeMyOrientationOnly = value; + }); + widget.onUpdateRestrictions(value); + }, + ), + const SizedBox( + height: paddingStd, + ), + ], + ); + } + + List _getPreferences() { + List orientationWidgets = sexualOrientationToStrMap.entries + .map((e) => _getPreference(e.value, e.key)) + .toList(); + return orientationWidgets; + } + + Widget _getPreference(String lbl, SexualOrientation value) { + return GestureDetector( + onTap: () { + bool alreadySelected = _myPreferences.containsKey(value); + if (!alreadySelected) { + _myPreferences[value] = true; + } else { + _myPreferences.remove(value); + } + if (mounted) { + setState(() { + _myPreferences = _myPreferences; + }); + widget.onUpdatedPreferences( + _myPreferences.keys.toList(growable: false)); + } + }, + child: Container( + margin: const EdgeInsets.only( + top: paddingStd, + ), + height: 40, + decoration: const BoxDecoration( + border: Border(top: BorderSide(color: Colors.black12))), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + lbl, + style: appTextTheme.bodyText1?.copyWith( + color: _myPreferences.containsKey(value) + ? appTheme.colorScheme.primary + : Colors.black54), + ), + ), + const SizedBox( + width: paddingStd, + ), + Icon( + Icons.check, + color: _myPreferences.containsKey(value) + ? appTheme.colorScheme.primary + : Colors.black12, + size: 24, + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/profile/presentation/components/verification_video_editor.dart b/lib/features/profile/presentation/components/verification_video_editor.dart new file mode 100644 index 0000000..4abf687 --- /dev/null +++ b/lib/features/profile/presentation/components/verification_video_editor.dart @@ -0,0 +1,374 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:redting/core/components/cards/glass_card.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; +import 'package:video_player/video_player.dart'; + +class VerificationVideoEditor extends StatefulWidget { + final String? verificationCode; + final bool loadingVerificationCode; + final bool isProcessingVideo; + final Function(File? createdLocalVideoFile) onChanged; + final File? localVideoFile; + final Function(String err) onCameraError; + final VoidCallback discardLocalVideo; + final bool isVerified; + const VerificationVideoEditor({ + Key? key, + required this.verificationCode, + required this.loadingVerificationCode, + required this.isProcessingVideo, + required this.onChanged, + required this.localVideoFile, + required this.onCameraError, + required this.discardLocalVideo, + required this.isVerified, + }) : super(key: key); + + @override + State createState() => + _VerificationVideoEditorState(); +} + +class _VerificationVideoEditorState extends State { + final Widget _progressIndicator = const Center( + child: CircularProgress(), + ); + + bool _isShowingPopUp = false; + VideoPlayerController? _videoController; + final ImagePicker _videoPicker = ImagePicker(); + + _initVideoController() { + if (_videoController == null && widget.localVideoFile != null) { + _videoController = VideoPlayerController.file( + widget.localVideoFile!, + )..initialize().then((_) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + if (mounted) { + setState(() {}); + } + }); + } + } + + @override + Widget build(BuildContext context) { + _initVideoController(); + return Container( + constraints: const BoxConstraints(maxHeight: 52, maxWidth: 52), + child: Stack( + children: [ + InkWell( + onTap: _onTapVideoIcon, + child: Container( + margin: widget.isVerified + ? const EdgeInsets.only(top: 8) + : EdgeInsets.zero, + child: CircleAvatar( + backgroundColor: appTheme.colorScheme.primary, + radius: 40, + child: !(widget.isProcessingVideo || + widget.loadingVerificationCode) + ? Icon( + Icons.video_camera_front_outlined, + size: 32, + color: appTheme.colorScheme.primaryContainer, + ) + : _progressIndicator, + ), + ), + ), + _getVerifiedWidgetIfVerified() + ], + ), + ); + } + + Widget _getVerifiedWidgetIfVerified() { + if (widget.isVerified) { + return Align( + alignment: Alignment.topRight, + child: Icon( + Icons.verified_user_rounded, + size: 24, + color: appTheme.colorScheme.inversePrimary, + ), + ); + } + return const SizedBox.shrink(); + } + + _onTapVideoIcon() { + if (widget.verificationCode == null || + widget.isProcessingVideo || + widget.loadingVerificationCode) { + return; + } + if (widget.localVideoFile == null) { + _showRecordingInstructions(); + } else { + _showVideoPlayer(); + } + } + + /// VIDEO RECORD + _pickVideo() async { + try { + final XFile? video = await _videoPicker.pickVideo( + source: ImageSource.camera, + preferredCameraDevice: CameraDevice.front, + maxDuration: const Duration(seconds: 6)); + widget.onChanged(File(video!.path)); + } catch (_) { + widget.onCameraError(returnedVideoWasNullErr); + } + } + + void _showRecordingInstructions() async { + if (_isShowingPopUp) return; + if (mounted) { + setState(() { + _isShowingPopUp = true; + }); + } + + bool? recordVideo = await showDialog( + barrierDismissible: true, + context: context, + builder: (BuildContext context) { + final navigator = Navigator.of(context); + return AlertDialog( + contentPadding: EdgeInsets.zero, + insetPadding: EdgeInsets.zero, + backgroundColor: Colors.transparent, + content: Container( + constraints: const BoxConstraints(maxHeight: 400), + child: Center( + child: GlassCard( + margins: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingLg), + contentPadding: const EdgeInsets.symmetric( + vertical: paddingStd, horizontal: paddingMd), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + verificationVideoTitle, + style: appTextTheme.headline6, + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + Text( + verificationVideoInstructions, + style: appTextTheme.subtitle2, + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + Text( + widget.verificationCode!, + style: appTextTheme.headline5?.copyWith( + color: appTheme.colorScheme.primary, + letterSpacing: 1), + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + Text( + verificationVideoHint, + style: appTextTheme.caption, + textAlign: TextAlign.justify, + ), + Center( + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateColor.resolveWith((states) => + appTheme.colorScheme.inversePrimary), + ), + onPressed: () { + navigator.pop(true); + }, + child: Text( + verificationVideoInstructionsGotIt, + style: appTextTheme.button, + textAlign: TextAlign.center, + )), + ) + ], + )), + ), + )); + }); + + if (mounted) { + setState(() { + _isShowingPopUp = false; + }); + if (recordVideo == true) { + _pickVideo(); + } + } + } + + /// VIDEO PLAY + void _showVideoPlayer() async { + if (_isShowingPopUp) return; + if (mounted) { + setState(() { + _isShowingPopUp = true; + }); + } + + bool? discardVideo = await showDialog( + barrierDismissible: true, + context: context, + builder: (BuildContext context) { + final navigator = Navigator.of(context); + + return StatefulBuilder( + builder: (BuildContext context, + void Function(void Function()) setState) { + playOrPauseVideo() { + bool isCurrentlyPlaying = _videoController!.value.isPlaying; + isCurrentlyPlaying + ? _videoController!.pause() + : _videoController!.play(); + } + + popWithResult({bool discardTheVideo = false}) { + if (mounted) { + navigator.pop(discardTheVideo); + } + } + + return AlertDialog( + contentPadding: const EdgeInsets.symmetric( + horizontal: paddingMd, vertical: paddingLg), + insetPadding: EdgeInsets.zero, + backgroundColor: Colors.transparent, + content: Center( + child: Stack( + children: [ + Align( + alignment: Alignment.topCenter, + child: AspectRatio( + aspectRatio: _videoController!.value.aspectRatio, + child: VideoPlayer(_videoController!)), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + constraints: const BoxConstraints( + maxHeight: 70, + ), + child: Center( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateColor.resolveWith( + (states) => appTheme.colorScheme + .inversePrimary), + ), + onPressed: () { + popWithResult(discardTheVideo: true); + }, + child: Text( + verificationVideoDelete, + style: appTextTheme.button, + textAlign: TextAlign.center, + )), + const SizedBox( + width: paddingMd, + ), + InkWell( + onTap: () { + playOrPauseVideo(); + }, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: appTheme.colorScheme.primary, + shape: BoxShape.circle), + child: Center( + child: Icon( + Icons.play_arrow, + color: appTheme.colorScheme.onPrimary, + size: 32, + ), + ), + ), + ), + const SizedBox( + width: paddingMd, + ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: + MaterialStateColor.resolveWith( + (states) => appTheme.colorScheme + .inversePrimary), + ), + onPressed: () { + popWithResult(); + }, + child: Text( + verificationVideoKeep, + style: appTextTheme.button, + textAlign: TextAlign.center, + )), + ], + ), + ), + ), + ) + ], + ), + )); + }, + ); + }); + + if (mounted) { + setState(() { + _isShowingPopUp = false; + }); + if (discardVideo == true) { + widget.discardLocalVideo(); + } + } + } + + /// life cycle + @override + void deactivate() { + if (_videoController != null) { + _videoController!.setVolume(0.0); + _videoController!.pause(); + } + super.deactivate(); + } + + @override + void dispose() { + _videoController?.dispose(); + _videoController = null; + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/components/view_only/profile_photo_circular_container.dart b/lib/features/profile/presentation/components/view_only/profile_photo_circular_container.dart new file mode 100644 index 0000000..cabb65e --- /dev/null +++ b/lib/features/profile/presentation/components/view_only/profile_photo_circular_container.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/theme.dart'; + +class ProfilePhotoCircularContainer extends StatelessWidget { + final ImageProvider? imageProvider; + final Widget? child; + final bool isSmall, isSmallest; + final double? useRadius; + const ProfilePhotoCircularContainer( + {Key? key, + this.imageProvider, + this.child, + this.isSmall = false, + this.isSmallest = false, + this.useRadius}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return CircleAvatar( + backgroundColor: appTheme.colorScheme.inversePrimary, + radius: useRadius ?? + (isSmallest + ? avatarRadiusSmallest + : (isSmall ? avatarRadiusSmall : avatarRadiusLg)), + backgroundImage: imageProvider, + child: child, + ); + } +} diff --git a/lib/features/profile/presentation/components/view_only/profile_photo_loading_placeholder.dart b/lib/features/profile/presentation/components/view_only/profile_photo_loading_placeholder.dart new file mode 100644 index 0000000..fbfc9f9 --- /dev/null +++ b/lib/features/profile/presentation/components/view_only/profile_photo_loading_placeholder.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_circular_container.dart'; + +class ProfilePhotoLoadingPlaceholder extends StatelessWidget { + final ImageProvider? imgProvider; + const ProfilePhotoLoadingPlaceholder({Key? key, this.imgProvider}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ProfilePhotoCircularContainer( + imageProvider: imgProvider, + child: const Center( + child: CircularProgress(), + )); + } +} diff --git a/lib/features/profile/presentation/components/view_only/profile_photo_uneditable.dart b/lib/features/profile/presentation/components/view_only/profile_photo_uneditable.dart new file mode 100644 index 0000000..aedd7b2 --- /dev/null +++ b/lib/features/profile/presentation/components/view_only/profile_photo_uneditable.dart @@ -0,0 +1,47 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_circular_container.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_loading_placeholder.dart'; + +class UneditableProfilePhoto extends StatelessWidget { + final String profilePhoto; + final ImageProvider? placeholderImageProvider; + final bool isSmall, isSmallest; + final double? useRadius; + const UneditableProfilePhoto( + {Key? key, + required this.profilePhoto, + this.placeholderImageProvider, + this.isSmall = false, + this.isSmallest = false, + this.useRadius}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return CachedNetworkImage( + imageUrl: profilePhoto, + imageBuilder: (context, imageProvider) => ProfilePhotoCircularContainer( + imageProvider: imageProvider, + isSmall: isSmall, + useRadius: useRadius, + isSmallest: isSmallest), + errorWidget: (___, __, _) => const SizedBox( + width: 40, + height: 40, + child: Center( + child: Icon(Icons.error_outline), + ), + ), + placeholder: (context, _) => SizedBox( + width: 40, + height: 40, + child: Center( + child: ProfilePhotoLoadingPlaceholder( + imgProvider: placeholderImageProvider, + ), + ), + ), + ); + } +} diff --git a/lib/features/profile/presentation/pages/create_profile_screen.dart b/lib/features/profile/presentation/pages/create_profile_screen.dart new file mode 100644 index 0000000..809e377 --- /dev/null +++ b/lib/features/profile/presentation/pages/create_profile_screen.dart @@ -0,0 +1,584 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/components/text/app_name_std_style.dart'; +import 'package:redting/core/components/text_input/unstyled_input_txt.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/features/profile/presentation/components/profile_photo_editor.dart'; +import 'package:redting/features/profile/presentation/components/verification_video_editor.dart'; +import 'package:redting/features/profile/presentation/state/user_profile_bloc.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class CreateProfileScreen extends StatefulWidget { + const CreateProfileScreen({ + Key? key, + }) : super(key: key); + + @override + State createState() => _CreateProfileScreenState(); +} + +class _CreateProfileScreenState extends State { + late AuthUser loggedInUser; + late TextEditingController _nameController, + _titleController, + _bioController, + _otherGenderController, + _bDayController; + UserGender _gender = UserGender.stated; + String? _otherGender; + + UserProfileBloc? _eventDispatcher; + bool _isDialogOpen = false; + + //birthday stuff + final DateTime today = DateTime.now(); + final int eighteenYears = 365 * 18; + final int twentyFourYears = 365 * 24; + final int hundredTwentyYears = 365 * 120; + + //PROFILE PHOTO + String? _profilePhoto; + File? _selectedLocalPhotoFile; + bool _isUploadingPhoto = false; + + //VERIFICATION VIDEO + String? _verificationCode; + bool _loadingVerificationCode = false; + bool _onGoingOpOnVideo = false; + UserVerificationVideo? _profileVerificationVideo; + File? _createdLocalVideoFile; + + //saving user profile + bool _isCreatingUserProfile = false; + + @override + void initState() { + _nameController = TextEditingController(); + _titleController = TextEditingController(); + _bioController = TextEditingController(); + _otherGenderController = TextEditingController(); + _bDayController = TextEditingController(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + RouteSettings? settings = ModalRoute.of(context)?.settings; + if (settings != null && settings.arguments is AuthUser) { + loggedInUser = settings.arguments as AuthUser; + } + + var screenHeight = MediaQuery.of(context).size.height; + var screenWidth = MediaQuery.of(context).size.width; + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is UserProfileInitialState) { + _onInitState(blocContext); + } + return ScaffoldWrapper( + child: Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + toolbarHeight: appBarHeight, + backgroundColor: Colors.transparent, + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [StdAppName()]), + ), + body: SingleChildScrollView( + child: Container( + decoration: BoxDecoration( + gradient: threeColorOpaqueGradientTB), + constraints: BoxConstraints( + minWidth: screenWidth, minHeight: screenHeight), + child: Padding( + padding: const EdgeInsets.only( + top: (appBarHeight * 1.5), + ), + child: ListBody( + children: [ + _profileMediaSection( + screenWidth, screenHeight, blocContext), + _profileNonMediaSection(), + _getSaveButton(blocContext) + ], + ), + ), + ), + ))); + }))); + } + + /// WIDGETS + Widget _profileMediaSection( + double screenWidth, double screenHeight, BuildContext blocContext) { + return Container( + decoration: _getTopHalfDecor(screenHeight, screenWidth), + child: Padding( + padding: const EdgeInsets.only(top: paddingMd, bottom: paddingLg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ProfilePhotoEditor( + isLoading: _isUploadingPhoto, + profilePhoto: _profilePhoto, + localPhoto: _selectedLocalPhotoFile, + onError: (String err) { + _showSnack(err); + }, + onChange: (File? file, String? filename) { + _onNewProfileImage(file, filename, blocContext); + }, + ), + const SizedBox( + height: paddingSm, + ), + UnStyledTxtInput( + controller: _nameController, + keyboardType: TextInputType.name, + hint: nameHint, + constraints: const BoxConstraints(maxWidth: 200), + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + VerificationVideoEditor( + isVerified: _profileVerificationVideo != null, + onCameraError: (String err) { + _showSnack(err); + }, + loadingVerificationCode: _loadingVerificationCode, + isProcessingVideo: _onGoingOpOnVideo, + verificationCode: _verificationCode, + localVideoFile: _createdLocalVideoFile, + onChanged: (File? createdLocalVideoFile) { + _onNewVerificationVideoRecorded( + createdLocalVideoFile, blocContext); + }, + discardLocalVideo: () { + _onDeleteVerificationVideo(blocContext); + }, + ), + ], + ), + ), + ); + } + + Widget _profileNonMediaSection() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: paddingMd), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + UnStyledTxtInput( + controller: _titleController, + hint: titleHint, + label: titleLbl, + textAlign: TextAlign.start, + ), + const SizedBox( + height: paddingStd, + ), + UnStyledTxtInput( + controller: _bioController, + hint: bioHint, + label: bioLbl, + maxCharacters: 120, + keyboardType: TextInputType.multiline, + textAlign: TextAlign.start, + ), + const SizedBox( + height: paddingStd, + ), + Text( + gender, + style: appTextTheme.subtitle1?.copyWith(color: Colors.black), + ), + const SizedBox( + height: paddingStd, + ), + _getGenderWidgets(), + const SizedBox( + height: paddingStd, + ), + _getBirthdayPicker(), + const SizedBox( + height: paddingStd, + ), + ], + ), + ); + } + + Widget _getSaveButton(BuildContext buildContext) { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.symmetric(horizontal: paddingMd), + constraints: const BoxConstraints(maxWidth: 400), + child: MainElevatedBtn( + onClick: () { + _createProfile(buildContext); + }, + lbl: createProfileBtn, + showLoading: _isCreatingUserProfile, + primaryBg: true, + ), + ); + } + + /// ON CREATE PROFILE + /// + + /// STATE CHANGE + void _onInitState(BuildContext blocContext) { + //get the verification code asap + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(GetVerificationVideoCodeEvent()); + } + + void _listenToProfilePhotoState(UserProfileState state) { + if (state is UpdatingProfilePhotoState) { + setState(() { + _isUploadingPhoto = true; + }); + } + if (state is UpdatedProfilePhotoState) { + setState(() { + _isUploadingPhoto = false; + _profilePhoto = state.photoUrl; + _showSnack(uploadingPhotoSuccess, isError: false); + }); + } + if (state is UpdatingProfilePhotoFailedState) { + setState(() { + _isUploadingPhoto = false; + _profilePhoto = null; + _selectedLocalPhotoFile = null; + _showSnack(state.errMsg); + }); + } + } + + void _listenToVerificationVideoCodeState(UserProfileState state) { + if (state is LoadingVerificationVideoCodeState) { + setState(() { + _loadingVerificationCode = true; + }); + } + + if (state is LoadedVerificationVideoCodeState) { + setState(() { + _verificationCode = state.verificationVideoCode; + _loadingVerificationCode = false; + }); + } + + if (state is LoadingVerificationVideoCodeFailedState) { + setState(() { + _showSnack(state.errMsg); + }); + } + } + + void _listenToVerificationVideoState(UserProfileState state) { + //creating / updating + if (state is UpdatingVerificationVideoState) { + setState(() { + _onGoingOpOnVideo = true; + }); + } + + if (state is UpdatedVerificationVideoState) { + setState(() { + _onGoingOpOnVideo = false; + _profileVerificationVideo = state.userVerificationVideo; + _showSnack(successUploadingVerificationVideo, isError: false); + }); + } + + if (state is UpdatingVerificationVideoFailedState) { + setState(() { + _onGoingOpOnVideo = false; + _profileVerificationVideo = null; + _createdLocalVideoFile = null; + _showSnack(state.errMsg); + }); + } + + //deleting + if (state is DeletedVerificationVideoState) { + setState(() { + _profileVerificationVideo = null; + _onGoingOpOnVideo = false; + _createdLocalVideoFile = null; + }); + } + + if (state is DeletingVerificationVideoFailedState) { + setState(() { + _onGoingOpOnVideo = false; + _showSnack(state.errMsg); + }); + } + + if (state is DeletingVerificationVideoState) { + setState(() { + _onGoingOpOnVideo = true; + }); + } + } + + void _listenToProfileState(UserProfileState state) { + if (state is CreatingUserProfileState) { + setState(() { + _isCreatingUserProfile = true; + }); + } + + if (state is CreatedUserProfileState) { + setState(() { + _isCreatingUserProfile = false; + }); + _showSnack(createProfileSuccess, isError: false); + Navigator.pushReplacementNamed( + context, + splashRoute, + ); + } + + if (state is ErrorCreatingUserProfileState) { + setState(() { + _isCreatingUserProfile = false; + }); + _showSnack(state.errMsg ?? createProfileFail); + } + } + + void _listenToStateChange(BuildContext context, UserProfileState state) { + _listenToProfilePhotoState(state); + _listenToVerificationVideoCodeState(state); + _listenToVerificationVideoState(state); + _listenToProfileState(state); + } + + /// EVENTS + /// PROFILE PHOTO EVENTS + bool _onGoingProcessBlockPhotoUpload() => + (_isCreatingUserProfile || _isUploadingPhoto); + void _onNewProfileImage( + File? file, String? filename, BuildContext blocContext) { + if (_onGoingProcessBlockPhotoUpload()) return; + if (file == null || filename == null) return; + setState(() { + _selectedLocalPhotoFile = file; + }); + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(ChangeProfilePhotoEvent(file, filename)); + } + + /// PROFILE VIDEO EVENTS + bool _onGoingProcessBlockVideoUpload() => + (_isCreatingUserProfile || _onGoingOpOnVideo); + void _onNewVerificationVideoRecorded(File? file, BuildContext blocContext) { + if (_onGoingProcessBlockVideoUpload()) return; + if (file == null || _verificationCode == null) return; + setState(() { + _createdLocalVideoFile = file; + }); + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher + ?.add(ChangeVerificationVideoEvent(file, _verificationCode!)); + } + + void _onDeleteVerificationVideo(BuildContext blocContext) { + if (_onGoingProcessBlockVideoUpload()) return; + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(DeleteVerificationVideoEvent()); + } + + ///PROFILE CREATION + bool _onGoingProcessBlockProfileCreation() => + (_isCreatingUserProfile || _isUploadingPhoto || _onGoingOpOnVideo); + void _createProfile(BuildContext blocContext) { + if (_onGoingProcessBlockProfileCreation()) return; + _eventDispatcher ??= BlocProvider.of(blocContext); + + _eventDispatcher?.add(CreateUserProfileEvent( + name: _nameController.text, + userId: loggedInUser.userId, + profilePhotoUrl: _profilePhoto ?? '', + gender: _gender, + genderOther: _gender == UserGender.stated + ? _otherGender ?? _otherGenderController.text + : null, + bio: _bioController.text, + title: _titleController.text, + birthDay: _selectedBDay, + registerCountry: '', //todo ? + verificationVideo: _profileVerificationVideo)); + } + + /// GENDER + Widget _getGenderWidgets() { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _getGender(value: UserGender.female, lbl: femaleGender), + const SizedBox( + width: paddingStd, + ), + _getGender(value: UserGender.male, lbl: maleGender), + const SizedBox( + width: paddingStd, + ), + Expanded( + child: UnStyledTxtInput( + controller: _otherGenderController, + hint: otherGenderHint, + onTxtChanged: _onGenderTyped, + )) + ], + ); + } + + Widget _getGender({required UserGender value, required String lbl}) { + return InkWell( + splashColor: appTheme.colorScheme.secondary.withOpacity(0.1), + radius: 8.0, + borderRadius: BorderRadius.circular(8.0), + onTap: () { + setState(() { + _gender = value; + _otherGenderController.clear(); + _otherGender = null; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: _gender == value + ? appTheme.colorScheme.primary + : Colors.black26, + width: 1.0)), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + lbl, + style: appTextTheme.subtitle2, + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + _onGenderTyped(String other) { + setState(() { + _gender = UserGender.stated; + _otherGender = other; + }); + } + + /// BIRTHDAY + Widget _getBirthdayPicker() { + return UnStyledTxtInput( + onTap: () { + _showBDatePickerDialog(); + }, + controller: _bDayController, + hint: birthDayHint, + keyboardType: TextInputType.none, + label: birthDay, + ); + } + + DateTime? _selectedBDay; + void _showBDatePickerDialog() async { + if (_isDialogOpen) return; + setState(() { + _isDialogOpen = true; + }); + DateTime eighteenYearsAgo = today.subtract(Duration(days: eighteenYears)); + DateTime hundredTwentyYearsBefore = + today.subtract(Duration(days: hundredTwentyYears)); + DateTime twentyFourYearsAgo = + today.subtract(Duration(days: twentyFourYears)); + + _selectedBDay = await showDatePicker( + context: context, + initialDate: twentyFourYearsAgo, + firstDate: hundredTwentyYearsBefore, + lastDate: eighteenYearsAgo); + + if (mounted) { + setState(() { + _isDialogOpen = false; + if (_selectedBDay != null) { + _bDayController.text = + "${_selectedBDay?.year} / ${_selectedBDay?.month} / ${_selectedBDay?.day} "; + } + }); + } + } + + /// TOP DECOR + _getTopHalfDecor(double screenHeight, double screenWidth) { + return BoxDecoration( + boxShadow: const [ + BoxShadow( + offset: Offset(0.0, 3.0), + color: Colors.black26, + blurRadius: 12, + spreadRadius: 2) + ], + color: appTheme.colorScheme.primaryContainer, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(screenWidth / 2), + bottomRight: Radius.circular(screenWidth / 2), + ), + ); + } + + /// SNACK + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + @override + void dispose() { + _eventDispatcher?.close(); + _nameController.dispose(); + _titleController.dispose(); + _bioController.dispose(); + _otherGenderController.dispose(); + _bDayController.dispose(); + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/pages/edit_dating_info_screen.dart b/lib/features/profile/presentation/pages/edit_dating_info_screen.dart new file mode 100644 index 0000000..a86fd3d --- /dev/null +++ b/lib/features/profile/presentation/pages/edit_dating_info_screen.dart @@ -0,0 +1,282 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; +import 'package:redting/features/profile/presentation/components/age_preference_slider.dart'; +import 'package:redting/features/profile/presentation/components/dating_pics.dart'; +import 'package:redting/features/profile/presentation/components/gender_preferences.dart'; +import 'package:redting/features/profile/presentation/components/screen_containers/edit_profile_container.dart'; +import 'package:redting/features/profile/presentation/components/sexual_preferences.dart'; +import 'package:redting/features/profile/presentation/state/user_profile_bloc.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class EditDatingInfoScreen extends StatefulWidget { + final UserProfile userProfile; + const EditDatingInfoScreen({Key? key, required this.userProfile}) + : super(key: key); + + @override + State createState() => _EditDatingInfoScreenState(); +} + +class _EditDatingInfoScreenState extends State { + bool _isSavingProfile = false; + List _datingPics = []; + late int _minAge, _maxAge; + UserGender? _myGenderPreference; + late List _mySexualOrientationPreferences; + late bool _makeMyOrientationPublic, _showMeMyOrientationOnly; + UserProfileBloc? _eventDispatcher; + + @override + void initState() { + _makeMyOrientationPublic = widget.userProfile.makeMyOrientationPublic; + _showMeMyOrientationOnly = + widget.userProfile.onlyShowMeOthersOfSameOrientation; + _myGenderPreference = widget.userProfile.getGenderPreferences() ?? + (widget.userProfile.getGender() == UserGender.male + ? UserGender.female + : (widget.userProfile.getGender() == UserGender.female) + ? UserGender.male + : null); + + final usersDatingPics = widget.userProfile.datingPhotos; + for (int i = 0; i < maxDatingProfilePhotos; i++) { + if (usersDatingPics.length > i) { + String photoUrl = usersDatingPics[i]; + _datingPics.add(DatingPic(position: i, photoUrl: photoUrl)); + } + } + _minAge = widget.userProfile.minAgePreference; + _maxAge = widget.userProfile.maxAgePreference; + _mySexualOrientationPreferences = + widget.userProfile.getUserSexualOrientation(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is UserProfileInitialState) { + _onInitState(blocContext); + } + + return EditProfileContainer( + onSaveProfile: () { + _onSaveProfile(blocContext); + }, + child: ListBody( + children: [ + Text(datingPicsHint, + style: appTextTheme.bodyText1 + ?.copyWith(color: appTheme.colorScheme.primary)), + DatingPicsWidget( + onError: (String errMsg) { + _showSnack(errMsg); + }, + onRemoveFile: _onRemovePhoto, + onChange: _onAddPhoto, + datingPics: _datingPics, + disableClick: _isSavingProfile, + ), + const SizedBox( + height: paddingMd, + ), + AgePreferenceSlider( + fromAge: _minAge.toDouble(), + toAge: _maxAge.toDouble(), + onAgeRangeSet: (int from, int to) { + if (mounted) { + setState(() { + _minAge = from; + _maxAge = to; + }); + } + }, + ), + const SizedBox( + height: paddingMd, + ), + GenderPreferences( + onChangeGender: (UserGender? value) { + if (mounted) { + setState(() { + _myGenderPreference = value; + }); + } + }, + initialGender: _myGenderPreference, + ), + const SizedBox( + height: paddingMd, + ), + SexualPreferences( + orientationPreferences: _mySexualOrientationPreferences, + onUpdatedPreferences: + (List orientationPreferences) { + if (mounted) { + setState(() { + _mySexualOrientationPreferences = + orientationPreferences; + }); + } + }, + onUpdateVisibility: (bool isSexOrientationPublic) { + if (mounted) { + setState(() { + _makeMyOrientationPublic = isSexOrientationPublic; + }); + } + }, + onUpdateRestrictions: (bool onlySimilarSexOrientation) { + if (mounted) { + setState(() { + _showMeMyOrientationOnly = + onlySimilarSexOrientation; + }); + } + }, + makeMyOrientationPublic: _makeMyOrientationPublic, + showMeMyOrientationOnly: _showMeMyOrientationOnly, + ), + const SizedBox( + height: paddingMd, + ), + Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 200), + child: MainElevatedBtn( + onClick: () { + _onSaveProfile(blocContext); + }, + primaryBg: true, + showLoading: _isSavingProfile, + lbl: createDatingProfileBtn, + loadingLbl: creatingDatingProfilePleaseWait, + ), + ), + ) + ], + )); + }))); + } + + void _listenToStateChange(BuildContext context, UserProfileState state) { + if (state is SettingDatingInfoState) { + if (mounted) { + setState(() { + _isSavingProfile = true; + }); + } + } + + if (state is SetDatingInfoState) { + if (mounted) { + setState(() { + _isSavingProfile = false; + }); + } + _showSnack(updateProfileSuccess, isError: false); + Navigator.pushReplacementNamed(context, homeRoute, + arguments: state.profile); + } + + if (state is SettingDatingInfoFailedState) { + if (mounted) { + setState(() { + _isSavingProfile = false; + }); + _showSnack(state.errMsg); + } + } + } + + void _onInitState(BuildContext blocContext) {} + + /// EVENTS + void _onSaveProfile(BuildContext blocContext) { + if (_isSavingProfile) return; + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(SetDatingInfoEvent( + widget.userProfile, + _datingPics, + _minAge, + _maxAge, + _myGenderPreference, + _mySexualOrientationPreferences, + _makeMyOrientationPublic, + _showMeMyOrientationOnly, + )); + } + + _onAddPhoto(File newFile, String filename, int photoNum) { + if (photoNum < maxDatingProfilePhotos) { + if (_datingPics.length > photoNum) { + _datingPics[photoNum].fileName = filename; + _datingPics[photoNum].file = newFile; + } else { + _datingPics.add( + DatingPic(position: photoNum, file: newFile, fileName: filename)); + } + } + if (mounted) { + setState(() { + _datingPics = _datingPics; + }); + } + } + + _onRemovePhoto(int photoNum) { + if (photoNum < maxDatingProfilePhotos) { + if (_datingPics.length > photoNum) { + _datingPics[photoNum].fileName = null; + _datingPics[photoNum].file = null; + _datingPics[photoNum].photoUrl = null; + } else { + _datingPics.add(DatingPic( + position: photoNum, + )); + } + } + if (mounted) { + setState(() { + _datingPics = _datingPics; + }); + } + } + + /// SNACK + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + @override + void dispose() { + _eventDispatcher?.close(); + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/pages/edit_profile_screen.dart b/lib/features/profile/presentation/pages/edit_profile_screen.dart new file mode 100644 index 0000000..c2a8333 --- /dev/null +++ b/lib/features/profile/presentation/pages/edit_profile_screen.dart @@ -0,0 +1,415 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/selectors/country_selector.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/components/text_input/unstyled_input_txt.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/presentation/components/profile_photo_editor.dart'; +import 'package:redting/features/profile/presentation/components/screen_containers/edit_profile_container.dart'; +import 'package:redting/features/profile/presentation/state/user_profile_bloc.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class EditProfileScreen extends StatefulWidget { + final UserProfile profile; + const EditProfileScreen({Key? key, required this.profile}) : super(key: key); + + @override + State createState() => _EditProfileScreenState(); +} + +class _EditProfileScreenState extends State { + late UserProfile userProfile; + late TextEditingController _nameController, + _titleController, + _bioController, + _otherGenderController, + _bDayController; + late UserGender _gender; + String? _otherGender; + String _selectedCountry = ''; + + UserProfileBloc? _eventDispatcher; + bool _isDialogOpen = false; + + //birthday stuff + final DateTime today = DateTime.now(); + final int eighteenYears = 365 * 18; + final int twentyFourYears = 365 * 24; + final int hundredTwentyYears = 365 * 120; + + //PROFILE PHOTO + String? _profilePhoto; + File? _selectedLocalPhotoFile; + bool _isUploadingPhoto = false; + + //saving user profile + bool _isUpdatingUserProfile = false; + + /// INIT + @override + void initState() { + _initControllers(); + _initializeProfile(); + super.initState(); + } + + _initControllers() { + _nameController = TextEditingController(); + _titleController = TextEditingController(); + _bioController = TextEditingController(); + _otherGenderController = TextEditingController(); + _bDayController = TextEditingController(); + } + + _initializeProfile() { + UserProfile profile = widget.profile; + userProfile = profile; + _profilePhoto = profile.profilePhotoUrl; + _nameController.text = profile.name; + _titleController.text = profile.title; + _bioController.text = profile.bio; + _otherGenderController.text = profile.genderOther ?? ''; + _bDayController.text = + "${profile.birthDay.year} / ${profile.birthDay.month} / ${profile.birthDay.day}"; + _gender = profile.getGender(); + _otherGender = profile.genderOther; + _selectedCountry = profile.registerCountry; + } + + /// BUILD + @override + Widget build(BuildContext context) { + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is UserProfileInitialState) { + _onInitState(blocContext); + } + + return EditProfileContainer( + onSaveProfile: () { + _onSaveProfile(blocContext); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: ProfilePhotoEditor( + isLoading: _isUploadingPhoto, + profilePhoto: _profilePhoto, + localPhoto: _selectedLocalPhotoFile, + onError: (String err) { + _showSnack(err); + }, + onChange: (File? file, String? filename) { + _onNewProfileImage(file, filename, blocContext); + }, + ), + ), + const SizedBox( + height: paddingSm, + ), + UnStyledTxtInput( + controller: _nameController, + keyboardType: TextInputType.name, + hint: nameHint, + constraints: const BoxConstraints(maxWidth: 200), + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + UnStyledTxtInput( + controller: _titleController, + hint: titleHint, + label: titleLbl, + textAlign: TextAlign.start, + ), + const SizedBox( + height: paddingStd, + ), + UnStyledTxtInput( + controller: _bioController, + hint: bioHint, + label: bioLbl, + maxCharacters: 120, + keyboardType: TextInputType.multiline, + textAlign: TextAlign.start, + ), + const SizedBox( + height: paddingStd, + ), + Text( + gender, + style: appTextTheme.subtitle1 + ?.copyWith(color: Colors.black), + ), + const SizedBox( + height: paddingStd, + ), + _getGenderWidgets(), + const SizedBox( + height: paddingStd, + ), + _getBirthdayPicker(), + const SizedBox( + height: paddingStd, + ), + CountrySelector( + selectedCountry: _selectedCountry, + onCountrySelected: (String country) { + setState(() { + _selectedCountry = country; + }); + }), + const SizedBox( + height: paddingMd, + ), + Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 240), + child: MainElevatedBtn( + primaryBg: true, + showLoading: _isUpdatingUserProfile, + onClick: () { + _onSaveProfile(blocContext); + }, + lbl: updateProfileBtn, + ), + ), + ), + const SizedBox( + height: paddingMd, + ), + ], + )); + }))); + } + + void _onInitState(BuildContext blocContext) { + //get the verification code asap + _eventDispatcher ??= BlocProvider.of(blocContext); + } + + void _listenToStateChange(BuildContext context, UserProfileState state) { + _listenToProfilePhotoState(state); + _listenToProfileState(state); + } + + void _listenToProfileState(UserProfileState state) { + if (state is UpdatingUserProfileState) { + setState(() { + _isUpdatingUserProfile = true; + }); + } + + if (state is UpdatedUserProfileState) { + setState(() { + _isUpdatingUserProfile = false; + }); + _showSnack(updateProfileSuccess, isError: false); + Navigator.pushReplacementNamed(context, homeRoute, + arguments: state.newProfile); + } + + if (state is ErrorUpdatingUserProfileState) { + setState(() { + _isUpdatingUserProfile = false; + }); + _showSnack(state.errMsg); + } + } + + void _listenToProfilePhotoState(UserProfileState state) { + if (state is UpdatingProfilePhotoState) { + setState(() { + _isUploadingPhoto = true; + }); + } + if (state is UpdatedProfilePhotoState) { + setState(() { + _isUploadingPhoto = false; + _profilePhoto = state.photoUrl; + _showSnack(uploadingPhotoSuccess, isError: false); + }); + } + if (state is UpdatingProfilePhotoFailedState) { + setState(() { + _isUploadingPhoto = false; + _profilePhoto = null; + _selectedLocalPhotoFile = null; + _showSnack(state.errMsg); + }); + } + } + + /// UI + /// GENDER + Widget _getGenderWidgets() { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _getGender(value: UserGender.female, lbl: femaleGender), + const SizedBox( + width: paddingStd, + ), + _getGender(value: UserGender.male, lbl: maleGender), + const SizedBox( + width: paddingStd, + ), + Expanded( + child: UnStyledTxtInput( + controller: _otherGenderController, + hint: otherGenderHint, + onTxtChanged: _onGenderTyped, + )) + ], + ); + } + + Widget _getGender({required UserGender value, required String lbl}) { + return InkWell( + splashColor: appTheme.colorScheme.secondary.withOpacity(0.1), + radius: 8.0, + borderRadius: BorderRadius.circular(8.0), + onTap: () { + setState(() { + _gender = value; + _otherGenderController.clear(); + _otherGender = null; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: _gender == value + ? appTheme.colorScheme.primary + : Colors.black26, + width: 1.0)), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + lbl, + style: appTextTheme.subtitle2, + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + _onGenderTyped(String other) { + setState(() { + _gender = UserGender.stated; + _otherGender = other; + }); + } + + /// BIRTHDAY + Widget _getBirthdayPicker() { + return UnStyledTxtInput( + onTap: () { + _showBDatePickerDialog(); + }, + controller: _bDayController, + hint: birthDayHint, + keyboardType: TextInputType.none, + label: birthDay, + ); + } + + DateTime? _selectedBDay; + void _showBDatePickerDialog() async { + if (_isDialogOpen) return; + setState(() { + _isDialogOpen = true; + }); + DateTime eighteenYearsAgo = today.subtract(Duration(days: eighteenYears)); + DateTime hundredTwentyYearsBefore = + today.subtract(Duration(days: hundredTwentyYears)); + DateTime twentyFourYearsAgo = + today.subtract(Duration(days: twentyFourYears)); + + _selectedBDay = await showDatePicker( + context: context, + initialDate: twentyFourYearsAgo, + firstDate: hundredTwentyYearsBefore, + lastDate: eighteenYearsAgo); + + if (mounted) { + setState(() { + _isDialogOpen = false; + if (_selectedBDay != null) { + _bDayController.text = + "${_selectedBDay?.year} / ${_selectedBDay?.month} / ${_selectedBDay?.day} "; + } + }); + } + } + + /// EVENTS + /// PROFILE PHOTO EVENTS + bool _onGoingProcessBlockPhotoUpload() => + (_isUpdatingUserProfile || _isUploadingPhoto); + void _onNewProfileImage( + File? file, String? filename, BuildContext blocContext) { + if (_onGoingProcessBlockPhotoUpload()) return; + if (file == null || filename == null) return; + setState(() { + _selectedLocalPhotoFile = file; + }); + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(ChangeProfilePhotoEvent(file, filename)); + } + + void _onSaveProfile(BuildContext blocContext) { + if (_onGoingProcessBlockPhotoUpload()) return; + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(UpdateUserProfileEvent( + profile: widget.profile, + name: _nameController.text, + profilePhotoUrl: _profilePhoto ?? widget.profile.profilePhotoUrl, + genderOther: _otherGender, + gender: _gender, + bio: _bioController.text, + title: _titleController.text, + birthDay: _selectedBDay ?? widget.profile.birthDay, + registerCountry: _selectedCountry)); + } + + /// SNACK INFO + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + @override + void dispose() { + _eventDispatcher?.close(); + _nameController.dispose(); + _titleController.dispose(); + _bioController.dispose(); + _otherGenderController.dispose(); + _bDayController.dispose(); + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/pages/set_new_dating_info_screen.dart b/lib/features/profile/presentation/pages/set_new_dating_info_screen.dart new file mode 100644 index 0000000..77c1460 --- /dev/null +++ b/lib/features/profile/presentation/pages/set_new_dating_info_screen.dart @@ -0,0 +1,319 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/core/components/snack/snack.dart'; +import 'package:redting/core/components/text/app_name_std_style.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; +import 'package:redting/features/profile/presentation/components/age_preference_slider.dart'; +import 'package:redting/features/profile/presentation/components/dating_pics.dart'; +import 'package:redting/features/profile/presentation/components/gender_preferences.dart'; +import 'package:redting/features/profile/presentation/components/sexual_preferences.dart'; +import 'package:redting/features/profile/presentation/state/user_profile_bloc.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class SetNewDatingInfoScreen extends StatefulWidget { + const SetNewDatingInfoScreen({Key? key}) : super(key: key); + + @override + State createState() => _SetNewDatingInfoScreenState(); +} + +class _SetNewDatingInfoScreenState extends State { + late UserProfile userProfile; + bool _isSavingProfile = false; + List _datingPics = []; + int _minAge = 18, _maxAge = 60; + UserGender? _myGenderPreference; + List _mySexualOrientationPreferences = [ + SexualOrientation.straight + ]; + bool _makeMyOrientationPublic = false; + bool _showMeMyOrientationOnly = false; + UserProfileBloc? _eventDispatcher; + + @override + Widget build(BuildContext context) { + RouteSettings? settings = ModalRoute.of(context)?.settings; + if (settings != null && settings.arguments is UserProfile) { + userProfile = settings.arguments as UserProfile; + _makeMyOrientationPublic = userProfile.makeMyOrientationPublic; + _showMeMyOrientationOnly = userProfile.onlyShowMeOthersOfSameOrientation; + _myGenderPreference = userProfile.getGender() == UserGender.male + ? UserGender.female + : (userProfile.getGender() == UserGender.female) + ? UserGender.male + : null; + } + + var screenHeight = MediaQuery.of(context).size.height; + var screenWidth = MediaQuery.of(context).size.width; + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is UserProfileInitialState) { + _onInitState(blocContext); + } + + return ScaffoldWrapper( + child: Scaffold( + extendBodyBehindAppBar: false, + appBar: AppBar( + toolbarHeight: appBarHeight, + backgroundColor: appTheme.colorScheme.primary, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: StdAppName( + firstPartTxtColor: Colors.white, + secondPartTxtColor: + appTheme.colorScheme.inversePrimary, + )), + const SizedBox( + width: paddingStd, + ), + IconButton( + iconSize: 32, + onPressed: () { + _onSaveProfile(blocContext); + }, + icon: const Icon( + Icons.check_circle, + size: 32, + color: Colors.white, + )) + ]), + ), + body: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Container( + decoration: BoxDecoration( + gradient: threeColorOpaqueGradientTB), + constraints: BoxConstraints( + minWidth: screenWidth, minHeight: screenHeight), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: paddingMd, horizontal: paddingStd), + child: ListBody( + children: [ + Text(datingPicsHint, + style: appTextTheme.bodyText1?.copyWith( + color: appTheme.colorScheme.primary)), + DatingPicsWidget( + onError: (String errMsg) { + _showSnack(errMsg); + }, + onRemoveFile: _onRemovePhoto, + onChange: _onAddPhoto, + datingPics: _datingPics, + disableClick: _isSavingProfile, + ), + const SizedBox( + height: paddingMd, + ), + AgePreferenceSlider( + fromAge: _minAge.toDouble(), + toAge: _maxAge.toDouble(), + onAgeRangeSet: (int from, int to) { + if (mounted) { + setState(() { + _minAge = from; + _maxAge = to; + }); + } + }, + ), + const SizedBox( + height: paddingMd, + ), + GenderPreferences( + onChangeGender: (UserGender? value) { + if (mounted) { + setState(() { + _myGenderPreference = value; + }); + } + }, + initialGender: _myGenderPreference, + ), + const SizedBox( + height: paddingMd, + ), + SexualPreferences( + orientationPreferences: + _mySexualOrientationPreferences, + onUpdatedPreferences: (List + orientationPreferences) { + if (mounted) { + setState(() { + _mySexualOrientationPreferences = + orientationPreferences; + }); + } + }, + onUpdateVisibility: + (bool isSexOrientationPublic) { + if (mounted) { + setState(() { + _makeMyOrientationPublic = + isSexOrientationPublic; + }); + } + }, + onUpdateRestrictions: + (bool onlySimilarSexOrientation) { + if (mounted) { + setState(() { + _showMeMyOrientationOnly = + onlySimilarSexOrientation; + }); + } + }, + makeMyOrientationPublic: + _makeMyOrientationPublic, + showMeMyOrientationOnly: + _showMeMyOrientationOnly, + ), + const SizedBox( + height: paddingMd, + ), + Center( + child: Container( + constraints: + const BoxConstraints(maxWidth: 200), + child: MainElevatedBtn( + onClick: () { + _onSaveProfile(blocContext); + }, + primaryBg: true, + showLoading: _isSavingProfile, + lbl: createDatingProfileBtn, + loadingLbl: + creatingDatingProfilePleaseWait, + ), + ), + ) + ], + ), + ), + ), + ))); + }))); + } + + void _listenToStateChange(BuildContext context, UserProfileState state) { + if (state is SettingDatingInfoState) { + if (mounted) { + setState(() { + _isSavingProfile = true; + }); + } + } + + if (state is SetDatingInfoState) { + if (mounted) { + setState(() { + _isSavingProfile = false; + }); + } + Navigator.pushReplacementNamed(context, splashRoute); + } + + if (state is SettingDatingInfoFailedState) { + if (mounted) { + setState(() { + _isSavingProfile = false; + }); + _showSnack(state.errMsg); + } + } + } + + void _onInitState(BuildContext blocContext) {} + + /// EVENTS + void _onSaveProfile(BuildContext blocContext) { + if (_isSavingProfile) return; + _eventDispatcher ??= BlocProvider.of(blocContext); + _eventDispatcher?.add(SetDatingInfoEvent( + userProfile, + _datingPics, + _minAge, + _maxAge, + _myGenderPreference, + _mySexualOrientationPreferences, + _makeMyOrientationPublic, + _showMeMyOrientationOnly, + )); + } + + _onAddPhoto(File newFile, String filename, int photoNum) { + if (photoNum < maxDatingProfilePhotos) { + if (_datingPics.length > photoNum) { + _datingPics[photoNum].fileName = filename; + _datingPics[photoNum].file = newFile; + } else { + _datingPics.add( + DatingPic(position: photoNum, file: newFile, fileName: filename)); + } + } + if (mounted) { + setState(() { + _datingPics = _datingPics; + }); + } + } + + _onRemovePhoto(int photoNum) { + if (photoNum < maxDatingProfilePhotos) { + if (_datingPics.length > photoNum) { + _datingPics[photoNum].fileName = null; + _datingPics[photoNum].file = null; + } else { + _datingPics.add(DatingPic( + position: photoNum, + )); + } + } + if (mounted) { + setState(() { + _datingPics = _datingPics; + }); + } + } + + /// SNACK + void _showSnack(String err, {bool isError = true}) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(Snack( + content: err, + isError: isError, + ).create(context)); + } + } + + @override + void dispose() { + _eventDispatcher?.close(); + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/pages/view_profile_destination.dart b/lib/features/profile/presentation/pages/view_profile_destination.dart new file mode 100644 index 0000000..eff7f1d --- /dev/null +++ b/lib/features/profile/presentation/pages/view_profile_destination.dart @@ -0,0 +1,231 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/buttons/main_elevated_btn.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/core/utils/consts.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/presentation/components/view_only/profile_photo_uneditable.dart'; +import 'package:redting/features/profile/presentation/pages/edit_dating_info_screen.dart'; +import 'package:redting/features/profile/presentation/pages/edit_profile_screen.dart'; +import 'package:redting/features/profile/presentation/state/user_profile_bloc.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class ViewProfileScreen extends StatefulWidget { + final UserProfile profile; + const ViewProfileScreen({Key? key, required this.profile}) : super(key: key); + + @override + State createState() => _ViewProfileScreenState(); +} + +class _ViewProfileScreenState extends State + with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + + UserProfileBloc? _eventDispatcher; + + final double thumbnailHeight = datingProfilePhotoSizeHeight / 4; + final double thumbnailWidth = datingProfilePhotoSizeWidth / 2; + + @override + Widget build(BuildContext context) { + super.build(context); + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: _listenToStateChange, + child: BlocBuilder( + builder: (blocContext, state) { + if (state is UserProfileInitialState) { + _onInitState(blocContext); + } + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUserProfileSections()), + ); + }))); + } + + void _listenToStateChange(BuildContext context, UserProfileState state) {} + + void _onInitState(BuildContext blocContext) { + _eventDispatcher ??= BlocProvider.of(blocContext); + } + + List _buildUserProfileSections() { + return [ + Center( + child: Container( + constraints: const BoxConstraints( + maxHeight: avatarRadiusLg * 2.5, maxWidth: avatarRadiusLg * 2.5), + child: UneditableProfilePhoto( + profilePhoto: widget.profile.profilePhotoUrl), + ), + ), + Text( + "${widget.profile.name} ${widget.profile.age}", + style: appTextTheme.headline6 + ?.copyWith(color: appTheme.colorScheme.primary), + textAlign: TextAlign.center, + ), + Text( + widget.profile.title, + style: appTextTheme.subtitle1, + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + Text( + widget.profile.bio, + style: appTextTheme.bodyText1, + textAlign: TextAlign.center, + ), + const SizedBox( + height: paddingStd, + ), + _genderPill(), + const SizedBox( + height: paddingStd, + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + children: widget.profile.datingPhotos + .map((e) => _getDatingPicWidget( + e, const EdgeInsets.only(right: paddingStd))) + .toList(growable: false), + ), + ), + const SizedBox( + height: paddingMd, + ), + Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 200), + child: MainElevatedBtn( + suffixIcon: Icons.edit, + onClick: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditProfileScreen( + profile: widget.profile, + ), + ), + ); + }, + showLoading: false, + lbl: editProfile), + ), + ), + const SizedBox( + height: paddingMd, + ), + Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 300), + child: MainElevatedBtn( + suffixIcon: Icons.settings, + onClick: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditDatingInfoScreen( + userProfile: widget.profile, + ), + ), + ); + }, + showLoading: false, + lbl: editDatingPreferences), + ), + ), + const SizedBox( + height: paddingMd, + ), + ]; + } + + Widget _genderPill() { + UserGender userGender = widget.profile.getGender(); + String userGenderStr = ""; + switch (userGender) { + case UserGender.male: + userGenderStr = maleGender; + break; + case UserGender.female: + if (widget.profile.genderOther != null) { + userGenderStr = femaleGender; + } + break; + case UserGender.stated: + if (widget.profile.genderOther != null) { + userGenderStr = widget.profile.genderOther!; + } + break; + } + if (userGenderStr.isEmpty) return const SizedBox.shrink(); + + return Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 100), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: appTheme.colorScheme.primary.withOpacity(0.5))), + child: Padding( + padding: const EdgeInsets.all(paddingSm), + child: Text( + userGenderStr, + style: appTextTheme.bodyText2?.copyWith(color: Colors.black), + ), + ), + ), + ); + } + + Widget _getDatingPicWidget(String datingPicUrl, EdgeInsets margins) { + return Container( + width: thumbnailWidth, + height: thumbnailHeight, + margin: margins, + decoration: BoxDecoration( + color: appTheme.colorScheme.inversePrimary.withOpacity(0.2), + borderRadius: BorderRadius.circular(12)), + child: AspectRatio( + aspectRatio: thumbnailWidth / thumbnailHeight, + child: CachedNetworkImage( + imageUrl: datingPicUrl, + fit: BoxFit.cover, + errorWidget: (_, __, ___) { + return const SizedBox.shrink(); + }, + placeholder: (_, __) { + return const Center( + child: CircularProgress(), + ); + }, + ), + ), + ); + } + + @override + void dispose() { + _eventDispatcher?.close(); + super.dispose(); + } +} diff --git a/lib/features/profile/presentation/state/user_profile_bloc.dart b/lib/features/profile/presentation/state/user_profile_bloc.dart new file mode 100644 index 0000000..a20eea6 --- /dev/null +++ b/lib/features/profile/presentation/state/user_profile_bloc.dart @@ -0,0 +1,190 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/models/user_verification_video.dart'; +import 'package:redting/features/profile/domain/use_cases/profile_usecases.dart'; +import 'package:redting/features/profile/domain/utils/dating_pic.dart'; +import 'package:redting/res/strings.dart'; + +part 'user_profile_event.dart'; +part 'user_profile_state.dart'; + +class UserProfileBloc extends Bloc { + final ProfileUseCases profileUseCases; + + UserProfileBloc({required this.profileUseCases}) + : super(UserProfileInitialState()) { + on(_onLoadUserProfileFromRemoteEvent); + on(_onChangeProfilePhotoEvent); + on(_onChangeVerificationVideoEvent); + on(_onGetVerificationVideoCodeEvent); + on(_onDeleteVerificationVideoEvent); + on(_onCreateUserProfileEvent); + on(_onLoadCachedProfileFromRemoteEvent); + on(_onSetDatingInfoEvent); + on(_onUpdatingUserProfileEvent); + } + + FutureOr _onLoadUserProfileFromRemoteEvent( + LoadUserProfileFromRemoteEvent event, + Emitter emit) async { + emit(LoadingUserProfileState()); + ServiceResult result = + await profileUseCases.getProfileFromRemoteUseCase.execute(); + if (result.errorOccurred) { + emit(ErrorLoadingUserProfileState( + errMsg: result.errorMessage ?? getProfileError)); + } + + if (result.data is UserProfile) { + emit(LoadedUserProfileState(profile: result.data as UserProfile)); + } + } + + FutureOr _onLoadCachedProfileFromRemoteEvent( + LoadCachedProfileEvent event, Emitter emit) async { + emit(LoadingUserProfileState()); + UserProfile? profile = + await profileUseCases.getCachedProfileUseCase.execute(); + if (profile == null) { + emit(ErrorLoadingUserProfileState(errMsg: getProfileError)); + } else { + emit(LoadedUserProfileState(profile: profile)); + } + } + + FutureOr _onChangeProfilePhotoEvent( + ChangeProfilePhotoEvent event, Emitter emit) async { + emit(UpdatingProfilePhotoState(event.photoFile)); + ServiceResult result = await profileUseCases.uploadProfilePhotoUseCase + .execute(file: event.photoFile, filename: event.filename); + + if (result.errorOccurred) { + emit(UpdatingProfilePhotoFailedState( + result.errorMessage ?? uploadingPhotoErr)); + } else { + emit(UpdatedProfilePhotoState(result.data as String)); + } + } + + FutureOr _onGetVerificationVideoCodeEvent( + GetVerificationVideoCodeEvent event, + Emitter emit) async { + emit(LoadingVerificationVideoCodeState()); + + ServiceResult result = + await profileUseCases.generateVideoVerificationCodeUseCase.execute(); + + if (result.errorOccurred) { + emit(LoadingVerificationVideoCodeFailedState(result.errorMessage ?? "")); + } else { + emit(LoadedVerificationVideoCodeState(result.data)); + } + } + + FutureOr _onChangeVerificationVideoEvent( + ChangeVerificationVideoEvent event, + Emitter emit) async { + emit(UpdatingVerificationVideoState(event.videoFile)); + + ServiceResult result = await profileUseCases.uploadVerificationVideoUseCase + .execute( + file: event.videoFile, verificationCode: event.verificationCode); + if (result.errorOccurred) { + emit(UpdatingVerificationVideoFailedState( + result.errorMessage ?? errorUploadingVerificationVideo)); + } else { + emit(UpdatedVerificationVideoState(result.data as UserVerificationVideo)); + } + } + + FutureOr _onDeleteVerificationVideoEvent( + DeleteVerificationVideoEvent event, + Emitter emit) async { + emit(DeletingVerificationVideoState()); + ServiceResult result = + await profileUseCases.deleteVerificationVideoUseCase.execute(); + + if (result.errorOccurred) { + emit(DeletingVerificationVideoFailedState( + result.errorMessage ?? deletingVerificationVideoFailed)); + } else { + emit(DeletedVerificationVideoState()); + } + } + + FutureOr _onCreateUserProfileEvent( + CreateUserProfileEvent event, Emitter emit) async { + emit(CreatingUserProfileState()); + + ServiceResult result = await profileUseCases.createProfileUseCase.execute( + name: event.name, + userId: event.userId, + profilePhotoUrl: event.profilePhotoUrl, + gender: event.gender, + genderOther: event.genderOther, + bio: event.bio, + title: event.title, + birthDay: event.birthDay, + registerCountry: event.registerCountry, + verificationVideo: event.verificationVideo); + + if (result.errorOccurred) { + emit(ErrorCreatingUserProfileState(errMsg: result.errorMessage)); + } else { + emit(CreatedUserProfileState(profile: result.data as UserProfile)); + } + } + + FutureOr _onSetDatingInfoEvent( + SetDatingInfoEvent event, Emitter emit) async { + emit(SettingDatingInfoState()); + + ServiceResult result = await profileUseCases.setDatingInfoUseCase.execute( + event.profile, + event.datingPics, + event.minAgePreference, + event.maxAgePreference, + event.genderPreference, + event.userOrientation, + event.makeMyOrientationPublic, + event.onlyShowMeOthersOfSameOrientation); + + if (result.data is! UserProfile) { + emit(SettingDatingInfoFailedState( + result.errorMessage ?? setDatingInfoErr)); + } else { + emit(SetDatingInfoState(result.data as UserProfile)); + } + } + + FutureOr _onUpdatingUserProfileEvent( + UpdateUserProfileEvent event, Emitter emit) async { + emit(UpdatingUserProfileState()); + + ServiceResult result = await profileUseCases.updateUserProfileUseCase + .execute( + profile: event.profile, + name: event.name, + profilePhotoUrl: event.profilePhotoUrl, + genderOther: event.genderOther, + gender: event.gender, + bio: event.bio, + title: event.title, + birthDay: event.birthDay, + registerCountry: event.registerCountry); + + if (result.errorOccurred || result.data is! UserProfile) { + emit(ErrorUpdatingUserProfileState( + result.errorMessage ?? updateProfileError)); + } else { + emit(UpdatedUserProfileState(result.data as UserProfile)); + } + } +} diff --git a/lib/features/profile/presentation/state/user_profile_event.dart b/lib/features/profile/presentation/state/user_profile_event.dart new file mode 100644 index 0000000..f4553f7 --- /dev/null +++ b/lib/features/profile/presentation/state/user_profile_event.dart @@ -0,0 +1,100 @@ +part of 'user_profile_bloc.dart'; + +@immutable +abstract class UserProfileEvent {} + +/// when a user might have a profile for instance new phone install but already registered +class LoadUserProfileFromRemoteEvent extends UserProfileEvent {} + +// when a user profile should exist in cache e.g. view profile screen +class LoadCachedProfileEvent extends UserProfileEvent {} + +/// verification profile +class ChangeProfilePhotoEvent extends UserProfileEvent { + final File photoFile; + final String filename; + ChangeProfilePhotoEvent(this.photoFile, this.filename); +} + +/// verification video +class ChangeVerificationVideoEvent extends UserProfileEvent { + final File videoFile; + final String verificationCode; + ChangeVerificationVideoEvent(this.videoFile, this.verificationCode); +} + +class DeleteVerificationVideoEvent extends UserProfileEvent {} + +class GetVerificationVideoCodeEvent extends UserProfileEvent {} + +//creating user profile +class CreateUserProfileEvent extends UserProfileEvent { + final String name; + final String userId; + final String profilePhotoUrl; + final String? genderOther; + final UserGender gender; + final String bio; + final String title; + final DateTime? birthDay; + final String registerCountry; + final UserVerificationVideo? verificationVideo; + + CreateUserProfileEvent({ + required this.registerCountry, + required this.name, + required this.userId, + required this.profilePhotoUrl, + required this.genderOther, + required this.gender, + required this.bio, + required this.title, + required this.birthDay, + required this.verificationVideo, + }); +} + +//adding dating info +class SetDatingInfoEvent extends UserProfileEvent { + final UserProfile profile; + final List datingPics; + final int minAgePreference; + final int maxAgePreference; + final UserGender? genderPreference; + final List userOrientation; + final bool makeMyOrientationPublic; + final bool onlyShowMeOthersOfSameOrientation; + SetDatingInfoEvent( + this.profile, + this.datingPics, + this.minAgePreference, + this.maxAgePreference, + this.genderPreference, + this.userOrientation, + this.makeMyOrientationPublic, + this.onlyShowMeOthersOfSameOrientation, + ); +} + +class UpdateUserProfileEvent extends UserProfileEvent { + final UserProfile profile; + final String name; + final String profilePhotoUrl; + final String? genderOther; + final UserGender gender; + final String bio; + final String title; + final DateTime birthDay; + final String registerCountry; + + UpdateUserProfileEvent( + {required this.profile, + required this.name, + required this.profilePhotoUrl, + required this.genderOther, + required this.gender, + required this.bio, + required this.title, + required this.birthDay, + required this.registerCountry}); +} diff --git a/lib/features/profile/presentation/state/user_profile_state.dart b/lib/features/profile/presentation/state/user_profile_state.dart new file mode 100644 index 0000000..6e2dfd4 --- /dev/null +++ b/lib/features/profile/presentation/state/user_profile_state.dart @@ -0,0 +1,115 @@ +part of 'user_profile_bloc.dart'; + +@immutable +abstract class UserProfileState {} + +class UserProfileInitialState extends UserProfileState {} + +/// USER PROFILE GET +class LoadingUserProfileState extends UserProfileState {} + +class LoadedUserProfileState extends UserProfileState { + final UserProfile profile; + LoadedUserProfileState({required this.profile}); +} + +class ErrorLoadingUserProfileState extends UserProfileState { + final String errMsg; + ErrorLoadingUserProfileState({required this.errMsg}); +} + +class NoAuthUserFoundState extends UserProfileState {} + +/// USER PROFILE CREATING +class CreatingUserProfileState extends UserProfileState {} + +class CreatedUserProfileState extends UserProfileState { + final UserProfile profile; + CreatedUserProfileState({required this.profile}); +} + +class ErrorCreatingUserProfileState extends UserProfileState { + final String? errMsg; + ErrorCreatingUserProfileState({required this.errMsg}); +} + +/// PROFILE PHOTO +class UpdatingProfilePhotoState extends UserProfileState { + final File photoBeingUploaded; + UpdatingProfilePhotoState(this.photoBeingUploaded); +} + +class UpdatedProfilePhotoState extends UserProfileState { + final String photoUrl; + UpdatedProfilePhotoState(this.photoUrl); +} + +class UpdatingProfilePhotoFailedState extends UserProfileState { + final String errMsg; + UpdatingProfilePhotoFailedState(this.errMsg); +} + +/// VERIFICATION VIDEO +class UpdatingVerificationVideoState extends UserProfileState { + final File videoBeingUploaded; + UpdatingVerificationVideoState(this.videoBeingUploaded); +} + +class UpdatedVerificationVideoState extends UserProfileState { + final UserVerificationVideo userVerificationVideo; + UpdatedVerificationVideoState(this.userVerificationVideo); +} + +class UpdatingVerificationVideoFailedState extends UserProfileState { + final String errMsg; + UpdatingVerificationVideoFailedState(this.errMsg); +} + +//deleting the video +class DeletingVerificationVideoState extends UserProfileState {} + +class DeletingVerificationVideoFailedState extends UserProfileState { + final String errMsg; + DeletingVerificationVideoFailedState(this.errMsg); +} + +class DeletedVerificationVideoState extends UserProfileState {} + +/// VERIFICATION CODE +class LoadingVerificationVideoCodeState extends UserProfileState {} + +class LoadingVerificationVideoCodeFailedState extends UserProfileState { + final String errMsg; + LoadingVerificationVideoCodeFailedState(this.errMsg); +} + +class LoadedVerificationVideoCodeState extends UserProfileState { + final String verificationVideoCode; + LoadedVerificationVideoCodeState(this.verificationVideoCode); +} + +/// DATING INFO +class SettingDatingInfoState extends UserProfileState {} + +class SettingDatingInfoFailedState extends UserProfileState { + final String errMsg; + SettingDatingInfoFailedState(this.errMsg); +} + +class SetDatingInfoState extends UserProfileState { + final UserProfile profile; + SetDatingInfoState(this.profile); +} + +/// USER PROFILE UPDATING +class UpdatingUserProfileState extends UserProfileState {} + +class UpdatedUserProfileState extends UserProfileState { + final UserProfile newProfile; + UpdatedUserProfileState(this.newProfile); +} + +class ErrorUpdatingUserProfileState extends UserProfileState { + final String errMsg; + ErrorUpdatingUserProfileState(this.errMsg); +} diff --git a/lib/features/splash/di/splash_di.dart b/lib/features/splash/di/splash_di.dart new file mode 100644 index 0000000..85345ec --- /dev/null +++ b/lib/features/splash/di/splash_di.dart @@ -0,0 +1,18 @@ +import 'package:get_it/get_it.dart'; +import 'package:redting/features/splash/domain/splash_repository.dart'; +import 'package:redting/features/splash/domain/usecases/fetch_current_user.dart'; +import 'package:redting/features/splash/presentation/state/current_user_bloc.dart'; + +void init() { + final GetIt diInstance = GetIt.instance; + //auth bloc + diInstance + .registerFactory(() => CurrentUserBloc(diInstance())); + + //useCases + diInstance.registerLazySingleton( + () => FetchCurrentUserUseCase(diInstance())); + + diInstance.registerLazySingleton( + () => SplashRepository(diInstance(), diInstance())); +} diff --git a/lib/features/splash/domain/current_user_status_util.dart b/lib/features/splash/domain/current_user_status_util.dart new file mode 100644 index 0000000..f4f8a14 --- /dev/null +++ b/lib/features/splash/domain/current_user_status_util.dart @@ -0,0 +1,10 @@ +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; + +class CurrentUserStatus { + UserProfile? profile; + AuthUser? authUser; + bool errorFetchingStatus; + CurrentUserStatus( + {this.profile, this.authUser, this.errorFetchingStatus = false}); +} diff --git a/lib/features/splash/domain/splash_repository.dart b/lib/features/splash/domain/splash_repository.dart new file mode 100644 index 0000000..5c84373 --- /dev/null +++ b/lib/features/splash/domain/splash_repository.dart @@ -0,0 +1,42 @@ +import 'package:redting/core/utils/service_result.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/auth/domain/repositories/auth_repository.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/profile/domain/repositories/profile_repository.dart'; +import 'package:redting/features/splash/domain/current_user_status_util.dart'; + +class SplashRepository { + final AuthRepository authRepository; + final ProfileRepository profileRepository; + SplashRepository( + this.authRepository, + this.profileRepository, + ); + + Future fetchCurrentUserStatus() async { + ServiceResult result = await authRepository.getCachedAuthUser(); + if (result.errorOccurred) { + //an error occurred + return CurrentUserStatus(errorFetchingStatus: true); + } + + if (result.data is! AuthUser) return CurrentUserStatus(); + + AuthUser authUser = result.data as AuthUser; + ServiceResult profileResult = + await profileRepository.loadUserProfileFromRemoteIfExists(); + if (profileResult.errorOccurred) { + //an error occurred + return CurrentUserStatus(errorFetchingStatus: true); + } + + if (profileResult.data is! UserProfile) { + return CurrentUserStatus(authUser: authUser); + } + + return CurrentUserStatus( + authUser: authUser, + profile: profileResult.data as UserProfile, + ); + } +} diff --git a/lib/features/splash/domain/usecases/fetch_current_user.dart b/lib/features/splash/domain/usecases/fetch_current_user.dart new file mode 100644 index 0000000..daa9247 --- /dev/null +++ b/lib/features/splash/domain/usecases/fetch_current_user.dart @@ -0,0 +1,11 @@ +import 'package:redting/features/splash/domain/current_user_status_util.dart'; +import 'package:redting/features/splash/domain/splash_repository.dart'; + +class FetchCurrentUserUseCase { + final SplashRepository splashRepository; + FetchCurrentUserUseCase(this.splashRepository); + + Future execute() async { + return await splashRepository.fetchCurrentUserStatus(); + } +} diff --git a/lib/features/splash/presentation/pages/splash_screen.dart b/lib/features/splash/presentation/pages/splash_screen.dart new file mode 100644 index 0000000..c71b7c4 --- /dev/null +++ b/lib/features/splash/presentation/pages/splash_screen.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:redting/core/components/gradients/primary_gradients.dart'; +import 'package:redting/core/components/progress/circular_progress.dart'; +import 'package:redting/core/components/screens/scaffold_wrapper.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/splash/presentation/state/current_user_bloc.dart'; +import 'package:redting/res/assets_paths.dart'; +import 'package:redting/res/dimens.dart'; +import 'package:redting/res/fonts.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({Key? key}) : super(key: key); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + final screenWidth = size.width; + final screenHeight = size.height; + return BlocProvider( + lazy: false, + create: (BuildContext blocProviderContext) => + GetIt.instance(), + child: BlocListener( + listener: (context, state) { + if (state is LoadedCurrentUserState) { + bool shouldLogin = state.authUser == null; + + bool shouldCreateProfile = + state.authUser != null && state.userProfile == null; + + bool shouldCreateDatingProfile = state.authUser != null && + state.userProfile != null && + state.userProfile?.datingPhotos.isEmpty == true; + + bool shouldGoHome = state.authUser != null && + state.userProfile != null && + state.userProfile?.datingPhotos.isNotEmpty == true; + + if (shouldLogin) { + _goToLogin(); + } + + if (shouldCreateProfile) { + _goToProfileScreen(authUser: state.authUser!); + } + + if (shouldCreateDatingProfile) { + _goToDatingProfile(profile: state.userProfile!); + } + + if (shouldGoHome) { + _goToHome(profile: state.userProfile!); + } + } + }, + child: ScaffoldWrapper( + child: Scaffold( + extendBodyBehindAppBar: true, + body: Container( + decoration: BoxDecoration(gradient: twoColorOpaquePrimaryGradient), + constraints: + BoxConstraints(minWidth: screenWidth, minHeight: screenHeight), + child: BlocBuilder( + builder: (blocContext, state) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.only(bottom: paddingMd), + child: Image.asset( + redLogoPath, + height: 56, + fit: BoxFit.fitHeight, + ), + ), + if (state is InitialState) _initialize(blocContext), + if (state is LoadingCurrentUserState) + _getLoadingIndicator(), + if (state is ErrorLoadingCurrentUserState) _errorWidget(), + ]); + }), + ), + )), + ), + ); + } + + Widget _initialize(BuildContext blocContext) { + BlocProvider.of(blocContext).add(LoadCurrentUserEvent()); + return Center( + child: Text( + loadingAuthUser, + style: appTextTheme.bodyText1 + ?.copyWith(color: appTheme.colorScheme.primary), + ), + ); + } + + Widget _getLoadingIndicator() { + return const Center( + child: CircularProgress(), + ); + } + + Widget _errorWidget() { + return Center( + child: Text( + loadingAuthUserErr, + style: appTextTheme.bodyText1?.copyWith( + color: appTheme.colorScheme.error, + ), + textAlign: TextAlign.center, + ), + ); + } + + //navigation + void _goToLogin() { + Navigator.pushReplacementNamed(context, loginRoute); + } + + void _goToProfileScreen({required AuthUser authUser}) { + Navigator.pushReplacementNamed(context, createProfileRoute, + arguments: authUser); + } + + void _goToDatingProfile({required UserProfile profile}) { + Navigator.pushReplacementNamed(context, addDatingProfileRoute, + arguments: profile); + } + + void _goToHome({required UserProfile profile}) { + Navigator.pushReplacementNamed(context, homeRoute, arguments: profile); + } +} diff --git a/lib/features/splash/presentation/state/current_user_bloc.dart b/lib/features/splash/presentation/state/current_user_bloc.dart new file mode 100644 index 0000000..98f5a6e --- /dev/null +++ b/lib/features/splash/presentation/state/current_user_bloc.dart @@ -0,0 +1,30 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:redting/features/auth/domain/models/auth_user.dart'; +import 'package:redting/features/profile/domain/models/user_profile.dart'; +import 'package:redting/features/splash/domain/current_user_status_util.dart'; +import 'package:redting/features/splash/domain/usecases/fetch_current_user.dart'; +import 'package:redting/res/strings.dart'; + +part 'current_user_event.dart'; +part 'current_user_state.dart'; + +class CurrentUserBloc extends Bloc { + final FetchCurrentUserUseCase fetchCurrentUserUseCase; + CurrentUserBloc(this.fetchCurrentUserUseCase) : super(InitialState()) { + on(_onLoadCurrentUserEvent); + } + + FutureOr _onLoadCurrentUserEvent( + LoadCurrentUserEvent event, Emitter emit) async { + emit(LoadingCurrentUserState()); + CurrentUserStatus status = await fetchCurrentUserUseCase.execute(); + if (status.errorFetchingStatus) { + emit(ErrorLoadingCurrentUserState(loadingAuthUserErr)); + } else { + emit(LoadedCurrentUserState(status.authUser, status.profile)); + } + } +} diff --git a/lib/features/splash/presentation/state/current_user_event.dart b/lib/features/splash/presentation/state/current_user_event.dart new file mode 100644 index 0000000..275b92f --- /dev/null +++ b/lib/features/splash/presentation/state/current_user_event.dart @@ -0,0 +1,6 @@ +part of 'current_user_bloc.dart'; + +@immutable +abstract class CurrentUserEvent {} + +class LoadCurrentUserEvent extends CurrentUserEvent {} diff --git a/lib/features/splash/presentation/state/current_user_state.dart b/lib/features/splash/presentation/state/current_user_state.dart new file mode 100644 index 0000000..9141fdc --- /dev/null +++ b/lib/features/splash/presentation/state/current_user_state.dart @@ -0,0 +1,19 @@ +part of 'current_user_bloc.dart'; + +@immutable +abstract class CurrentUserState {} + +class InitialState extends CurrentUserState {} + +class LoadingCurrentUserState extends CurrentUserState {} + +class LoadedCurrentUserState extends CurrentUserState { + final AuthUser? authUser; + final UserProfile? userProfile; + LoadedCurrentUserState(this.authUser, this.userProfile); +} + +class ErrorLoadingCurrentUserState extends CurrentUserState { + final String errMsg; + ErrorLoadingCurrentUserState(this.errMsg); +} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..cb0ee14 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,70 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyC5Fs1bokNE96VvZoLSKIhfa88lcm9-w8A', + appId: '1:309040849360:android:e67558df37e022f4bc99d5', + messagingSenderId: '309040849360', + projectId: 'redting-d5504', + storageBucket: 'redting-d5504.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyAC5o9UXSTCyCMeMNYblbawbOTe_6MYmx8', + appId: '1:309040849360:ios:9ce023fc88ca3d7abc99d5', + messagingSenderId: '309040849360', + projectId: 'redting-d5504', + storageBucket: 'redting-d5504.appspot.com', + androidClientId: '309040849360-gdiqkfnqtqd1cc5r0jbjglmofhp1je3h.apps.googleusercontent.com', + iosClientId: '309040849360-ajflptqec7341i58rg3np5755gsi6ksc.apps.googleusercontent.com', + iosBundleId: 'com.redting.developers.redting', + ); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..3cb92c9 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:redting/core/data/local_storage.dart'; +import 'package:redting/main_init.dart'; +import 'package:redting/res/routes.dart'; +import 'package:redting/res/strings.dart'; +import 'package:redting/res/theme.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + bool isInitProperly = await MainAppInit.initApp(); + runApp(MyApp(isInit: isInitProperly)); //todo - app check before release +} + +class MyApp extends StatefulWidget { + final bool isInit; + const MyApp({Key? key, required this.isInit}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void dispose() { + LocalStorage.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + FocusManager.instance.primaryFocus?.unfocus(); + } + }, + child: MaterialApp( + title: appName, + darkTheme: darkTheme, + theme: lightTheme, + routes: appRoutes, + ), + ); + } +} diff --git a/lib/main_init.dart b/lib/main_init.dart new file mode 100644 index 0000000..dd0f02c --- /dev/null +++ b/lib/main_init.dart @@ -0,0 +1,60 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:redting/core/data/local_storage.dart'; +import 'package:redting/core/di/core_dependencies.dart' as core_di; +import 'package:redting/features/auth/di/auth_di.dart' as auth_di; +import 'package:redting/features/blind_date_setup/di/blind_date_di.dart' + as blind_date_di; +import 'package:redting/features/chat/di/chat_di.dart' as chat_di; +import 'package:redting/features/matching/di/matching_di.dart' as matching_di; +import 'package:redting/features/profile/di/profile_di.dart' as profile_di; +import 'package:redting/features/splash/di/splash_di.dart' as splash_di; + +import 'firebase_options.dart'; + +class MainAppInit { + static Future initApp() async { + try { + await _initFirebase(); + bool success = await _initLocalStorage(); + if (success) { + _initDependencies(); + } + return success; + } catch (e) { + return false; + } + } + + static Future _initFirebase() async { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + } + + static Future _initLocalStorage() async { + try { + await LocalStorage.init(); + return true; + } catch (e) { + return false; + } + } + + static void _initDependencies() { + try { + core_di.init(); + auth_di.init(); + profile_di.init(); + matching_di.init(); + chat_di.init(); + splash_di.init(); + blind_date_di.init(); + } catch (e) { + print("===================== $e ============= "); + } + } + + static void dispose() { + LocalStorage.dispose(); + } +} diff --git a/lib/res/assets_paths.dart b/lib/res/assets_paths.dart new file mode 100644 index 0000000..1ba7e9a --- /dev/null +++ b/lib/res/assets_paths.dart @@ -0,0 +1,3 @@ +const blackLogoPath = "assets/images/redting_black.png"; +const redLogoPath = "assets/images/redting_red.png"; +const whiteLogoPath = "assets/images/redting_white.png"; diff --git a/lib/res/colors.dart b/lib/res/colors.dart new file mode 100644 index 0000000..473ddf8 --- /dev/null +++ b/lib/res/colors.dart @@ -0,0 +1,3 @@ +import 'package:flutter/material.dart'; + +var seedColor = const Color(0xffA7171A); diff --git a/lib/res/countries.dart b/lib/res/countries.dart new file mode 100644 index 0000000..fb442c2 --- /dev/null +++ b/lib/res/countries.dart @@ -0,0 +1,244 @@ +Map countryToPhoneCodeMap = { + "Afghanistan": "+93", + "Åland Islands": "+358", + "Albania": "+355", + "Algeria": "+213", + "American Samoa": "+1684", + "Andorra": "+376", + "Angola": "+244", + "Anguilla": "+1264", + "Antarctica": "+672", + "Antigua & Barbuda": "+1268", + "Argentina": "+54", + "Armenia": "+374", + "Aruba": "+297", + "Australia": "+61", + "Austria": "+43", + "Azerbaijan": "+994", + "Bahamas": "+1242", + "Bahrain": "+973", + "Bangladesh": "+880", + "Barbados": "+1246", + "Belarus": "+375", + "Belgium": "+32", + "Belize": "+501", + "Benin": "+229", + "Bermuda": "+1441", + "Bhutan": "+975", + "Bolivia": "+591", + "Bosnia & Herzegovina": "+387", + "Botswana": "+267", + "Brazil": "+55", + "British Indian Ocean Territory": "+246", + "British Virgin Islands": "+1284", + "Brunei Darussalam": "+673", + "Bulgaria": "+359", + "Burkina Faso": "+226", + "Burundi": "+257", + "Cambodia": "+855", + "Cameroon": "+237", + "Canada": "+1", + "Cape Verde": "+238", + "Cayman Islands": "+1345", + "Central African Republic": "+236", + "Chad": "+235", + "Chile": "+56", + "China": "+86", + "Christmas Island": "+61", + "Cocos (keeling) Islands": "+61", + "Colombia": "+57", + "Comoros": "+269", + "Congo Republic": "+242", + "Congo DRC": "+243", + "Cook Islands": "+682", + "Costa Rica": "+506", + "Croatia": "+385", + "Cuba": "+53", + "Cyprus": "+357", + "Czech Republic": "+420", + "Denmark": "+45", + "Djibouti": "+253", + "Dominica": "+1767", + "Dominican Republic": "+1849", + "Timor-leste": "+670", + "Ecuador": "+593", + "Egypt": "+20", + "El Salvador": "+503", + "Equatorial Guinea": "+240", + "Eritrea": "+291", + "Estonia": "+372", + "Ethiopia": "+251", + "Falkland Islands (malvinas)": "+500", + "Faroe Islands": "+298", + "Fiji": "+679", + "Finland": "+358", + "France": "+33", + "French Guiana": "+594", + "French Polynesia": "+689", + "Gabon": "+241", + "Gambia": "+220", + "Georgia": "+995", + "Germany": "+49", + "Ghana": "+233", + "Gibraltar": "+350", + "Greece": "+30", + "Greenland": "+299", + "Grenada": "+1473", + "Guadeloupe": "+590", + "Guam": "+1671", + "Guatemala": "+502", + "Guernsey": "+44", + "Guinea": "+224", + "Guinea-bissau": "+245", + "Guyana": "+592", + "Haiti": "+509", + "Honduras": "+504", + "Hong Kong": "+852", + "Hungary": "+36", + "Iceland": "+354", + "India": "+91", + "Indonesia": "+62", + "Iran": "+98", + "Iraq": "+964", + "Ireland": "+353", + "Isle Of Man": "+44", + "Israel": "+972", + "Italy": "+39", + "Côte D\'ivoire": "+225", + "Jamaica": "+1876", + "Japan": "+81", + "Jersey": "+44", + "Jordan": "+962", + "Kazakhstan": "+7", + "Kenya": "+254", + "Kiribati": "+686", + "Kosovo": "+383", + "Kuwait": "+965", + "Kyrgyzstan": "+996", + "Lao People\'s Democratic Republic": "+856", + "Latvia": "+371", + "Lebanon": "+961", + "Lesotho": "+266", + "Liberia": "+231", + "Libya": "+218", + "Liechtenstein": "+423", + "Lithuania": "+370", + "Luxembourg": "+352", + "Macao": "+853", + "Macedonia": "+389", + "Madagascar": "+261", + "Malawi": "+265", + "Malaysia": "+60", + "Maldives": "+960", + "Mali": "+223", + "Malta": "+356", + "Marshall Islands": "+692", + "Martinique": "+596", + "Mauritania": "+222", + "Mauritius": "+230", + "Mayotte": "+262", + "Mexico": "+52", + "Micronesia": "+691", + "Moldova": "+373", + "Monaco": "+377", + "Mongolia": "+976", + "Montserrat": "+1664", + "Montenegro": "+382", + "Morocco": "+212", + "Mozambique": "+258", + "Myanmar": "+95", + "Namibia": "+264", + "Nauru": "+674", + "Nepal": "+977", + "Netherlands": "+31", + "New Caledonia": "+687", + "New Zealand": "+64", + "Nicaragua": "+505", + "Niger": "+227", + "Nigeria": "+234", + "Niue": "+683", + "North Korea": "+672", + "Norfolk Island": "+672", + "Northern Mariana Islands": "+1670", + "Norway": "+47", + "Oman": "+968", + "Pakistan": "+92", + "Palau": "+680", + "Palestinian Territory, Occupied": "+970", + "Panama": "+507", + "Papua New Guinea": "+675", + "Paraguay": "+595", + "Peru": "+51", + "Philippines": "+63", + "Pitcairn": "+64", + "Poland": "+48", + "Portugal": "+351", + "Puerto Rico": "+1939", + "Qatar": "+974", + "Réunion": "+262", + "Romania": "+40", + "Russian Federation": "+7", + "Rwanda": "+250", + "Saint Barthélemy": "+590", + "Saint Kitts & Nevis": "+1869", + "Saint Lucia": "+1758", + "Saint Martin": "+590", + "Saint Vincent & The Grenadines": "+1784", + "Samoa": "+685", + "San Marino": "+378", + "Sao Tome & Principe": "+239", + "Saudi Arabia": "+966", + "Senegal": "+221", + "Serbia": "+381", + "Seychelles": "+248", + "Sierra Leone": "+232", + "Singapore": "+65", + "Sint Maarten": "+1", + "Slovakia": "+421", + "Slovenia": "+386", + "Solomon Islands": "+677", + "Somalia": "+252", + "South Africa": "+27", + "South Korea": "+82", + "South Georgia & the South Sandwich Islands": "+500", + "Spain": "+34", + "Sri Lanka": "+94", + "Saint Helena": "+290", + "Saint Pierre & Miquelon": "+508", + "South Sudan": "+211", + "Sudan": "+249", + "Suriname": "+597", + "Swaziland": "+268", + "Sweden": "+46", + "Switzerland": "+41", + "Syrian Arab Republic": "+963", + "Taiwan": "+886", + "Tajikistan": "+992", + "Tanzania": "+255", + "Thailand": "+66", + "Togo": "+228", + "Tokelau": "+690", + "Tonga": "+676", + "Trinidad & Tobago": "+1868", + "Tunisia": "+216", + "Turkey": "+90", + "Turkmenistan": "+993", + "Turks & Caicos Islands": "+1649", + "Tuvalu": "+688", + "United Arab Emirates": "+971", + "Uganda": "+256", + "United Kingdom": "+44", + "Ukraine": "+380", + "Uruguay": "+598", + "United States": "+1", + "US Virgin Islands": "+1340", + "Uzbekistan": "+998", + "Vanuatu": "+678", + "Holy See (vatican City State)": "+379", + "Venezuela": "+58", + "Vietnam": "+84", + "Wallis & Futuna": "+681", + "Yemen": "+967", + "Zambia": "+260", + "Zimbabwe": "+263" +}; diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart new file mode 100644 index 0000000..120ae6a --- /dev/null +++ b/lib/res/dimens.dart @@ -0,0 +1,9 @@ +const paddingSm = 8.0; +const paddingStd = 12.0; +const paddingMd = 16.0; +const paddingLg = 20.0; + +const appBarHeight = 62.0; +const avatarRadiusLg = 60.0; +const avatarRadiusSmall = 40.0; +const avatarRadiusSmallest = 24.0; diff --git a/lib/res/fonts.dart b/lib/res/fonts.dart new file mode 100644 index 0000000..f104a4c --- /dev/null +++ b/lib/res/fonts.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +var appTextTheme = const TextTheme( + headline1: + TextStyle(fontFamily: 'Righteous', fontSize: 97, letterSpacing: -1.5), + headline2: + TextStyle(fontFamily: 'Righteous', fontSize: 61, letterSpacing: -0.5), + headline3: TextStyle( + fontFamily: 'Righteous', + fontSize: 48, + ), + headline4: + TextStyle(fontFamily: 'Righteous', fontSize: 34, letterSpacing: 0.25), + headline5: TextStyle( + fontFamily: 'Righteous', + fontSize: 24, + ), + headline6: + TextStyle(fontFamily: 'Righteous', fontSize: 20, letterSpacing: 0.15), + subtitle1: + TextStyle(fontFamily: 'Righteous', fontSize: 16, letterSpacing: 0.15), + subtitle2: + TextStyle(fontFamily: 'Righteous', fontSize: 14, letterSpacing: 0.1), + bodyText1: TextStyle(fontFamily: 'Rubik', fontSize: 16, letterSpacing: 0.5), + bodyText2: TextStyle(fontFamily: 'Rubik', fontSize: 14, letterSpacing: 0.25), + button: TextStyle(fontFamily: 'Rubik', fontSize: 14, letterSpacing: 1.25), + caption: TextStyle( + fontFamily: 'Rubik', + fontSize: 12, + fontWeight: FontWeight.w300, + letterSpacing: 0.4), + overline: TextStyle( + fontFamily: 'Rubik', + fontSize: 10, + fontWeight: FontWeight.w300, + letterSpacing: 1.5), +); diff --git a/lib/res/routes.dart b/lib/res/routes.dart new file mode 100644 index 0000000..2f70622 --- /dev/null +++ b/lib/res/routes.dart @@ -0,0 +1,19 @@ +import 'package:redting/features/auth/presentation/pages/login_screen.dart'; +import 'package:redting/features/home/presentation/pages/home_screen.dart'; +import 'package:redting/features/profile/presentation/pages/create_profile_screen.dart'; +import 'package:redting/features/profile/presentation/pages/set_new_dating_info_screen.dart'; +import 'package:redting/features/splash/presentation/pages/splash_screen.dart'; + +const splashRoute = '/'; +const loginRoute = '/login'; +const createProfileRoute = '/createProfile'; +const cameraPreviewOnProfile = '/cameraPreview'; +const homeRoute = '/home'; +const addDatingProfileRoute = '/addDatingProfile'; +var appRoutes = { + splashRoute: (context) => const SplashScreen(), + loginRoute: (context) => const LoginScreen(), + createProfileRoute: (context) => const CreateProfileScreen(), + homeRoute: (context) => const HomeScreen(), + addDatingProfileRoute: (context) => const SetNewDatingInfoScreen(), +}; diff --git a/lib/res/string_arrays.dart b/lib/res/string_arrays.dart new file mode 100644 index 0000000..c961ff3 --- /dev/null +++ b/lib/res/string_arrays.dart @@ -0,0 +1,24 @@ +final List vowels = ['A', 'E', 'I', 'O', 'U']; +final List consonants = [ + 'B', + 'C', + 'D', + 'F', + 'G', + 'H', + 'J', + 'K', + 'L', + 'M', + 'N', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'V', + 'X', + 'Z', + 'W', + 'Y' +]; diff --git a/lib/res/string_maps.dart b/lib/res/string_maps.dart new file mode 100644 index 0000000..f8b4162 --- /dev/null +++ b/lib/res/string_maps.dart @@ -0,0 +1,19 @@ +import 'package:redting/features/profile/domain/models/sexual_orientation.dart'; +import 'package:redting/features/profile/domain/models/user_gender.dart'; +import 'package:redting/res/strings.dart'; + +const genderToStrMap = { + UserGender.male: maleGender, + UserGender.female: femaleGender, +}; + +const sexualOrientationToStrMap = { + SexualOrientation.straight: straightSexualOrientationLbl, + SexualOrientation.questioning: questioningSexualOrientationLbl, + SexualOrientation.queer: queerSexualOrientationLbl, + SexualOrientation.panSexual: panSexualSexualOrientationLbl, + SexualOrientation.demiSexual: demiSexualSexualOrientationLbl, + SexualOrientation.bisexual: biSexualSexualOrientationLbl, + SexualOrientation.asexual: asexualSexualOrientationLbl, + SexualOrientation.gay: gaySexualOrientationLbl, +}; diff --git a/lib/res/strings.dart b/lib/res/strings.dart new file mode 100644 index 0000000..90d58a6 --- /dev/null +++ b/lib/res/strings.dart @@ -0,0 +1,193 @@ +const appName = 'REDTING'; +const appNameFirstWord = 'RED'; +const appNameLastWord = 'TING'; +const defaultIceBreaker = "Hi there. I’m vanilla! … Get it?"; +const initAppErr = "Something went wrong! Please restart app"; +const beAuthenticAlways = "Always Be your Authenticate self."; +//splash screen +const loadingAuthUser = 'Authenticating ...'; +const loadingAuthUserErr = + 'Checking your auth status failed! Please restart the app'; + +//login screen +const loginTitle = "Login"; +const yourPhoneNumberLbl = "Your mobile number"; +const phoneNumberErr = "Invalid mobile number"; +const unknownCodeSendingErr = "Failed to send verification code!"; +const loginBtn = 'Continue'; +const phoneVerificationHint = + "When you tap CONTINUE, RedTing will send a verification code via sms to your mobile number. Standard sms & data rates may apply. After verification you can then login. *You may be redirected to a browser, after completion, close browser to return here"; +const enterCodeSentTo = 'Enter code sent to '; +const resendTxt = 'RESEND'; +const changePhoneTxt = 'CHANGE PHONE NUMBER'; +const signInTerms = + "Depending on your location, it might take a minute for the code to reach your sms inbox. By tapping CONTINUE, you are agreeing to RedTing's terms and conditions. Click this message to review them first."; +const invalidVerificationCode = + "The code you entered is invalid. Please try again"; +const failedToVerifyUnknown = + "Timeout or connection error occurred! Please resend code & verify again"; +const failedToCacheAuthUser = "Failed to save your credentials! Please retry"; + +//profile screen +const createProfileError = + "failed to create your profile! Please check your connection & retry"; +const deleteProfileError = + "failed to delete your profile! Please check your connection & retry"; +const updateProfileError = + "failed to update your profile! ! Please check your connection & retry"; +const getProfileError = + "failed to fetch your profile! Please check your connection & retry"; +const nameHint = "Your name"; +const titleLbl = "Title"; +const titleHint = "art lover"; +const bioLbl = "Bio"; +const bioHint = "my friends say I am ..."; +const gender = "Gender"; +const maleGender = "Male"; +const femaleGender = "Female"; +const otherGenderHint = "I am ..."; +const birthDay = "Birthday"; +const birthDayHint = "Date of birth"; + +//profile photo +const uploadingPhotoErr = "Failed to update your profile photo"; +const uploadingPhotoSuccess = "Profile photo updated"; +const errPickingPhotoGallery = + "Failed to pick profile photo.\nHave you provided REDTING with access to the gallery in settings?"; + +//CAMERA +const returnedVideoWasNullErr = "No video was received!"; +const verificationVideoTitle = "REDTING Verification"; +const verificationVideoInstructions = + "Record a short clip (6 SECONDS MAX) of yourself saying the fun word below!"; +const verificationVideoHint = + "Only the first 6 seconds of the video will be taken."; +const verificationVideoInstructionsGotIt = "I'M READY"; +const verificationVideoKeep = "KEEP"; +const verificationVideoDelete = "RE TAKE"; +const deletingVerificationVideoFailed = + "failed to delete your verification video!"; +const errorUploadingVerificationVideo = + "Failed to upload your verification video! check your connection & retry"; +const successUploadingVerificationVideo = + "Your verification video has been saved"; + +//create profile +const createProfileBtn = "GET IN"; +const createProfileOnGoing = "Creating profile..."; +const createProfileSuccess = "Awesome! Your profile was created."; +const createProfileFail = + "Failed to create your profile. Please check your connection & retry"; +const userIdMissingDuringProfileCreateErr = + "OOps! Looks like you are logged out! Please restart the app"; +const emptyProfilePhotoErr = "A profile photo is required"; +const noVerificationVideo = "A verification video is required"; +const noGenderSpecified = "Please state your gender"; +const bioIsEmptyErr = "Mmmh! That bio is too short it seems."; +const titleIsEmptyErr = "Please provide your title"; +const titleIsTooLongErr = "Mmmh! Your title is too long. Please abbreviate it"; +const bDayNotSet = "Your birthday is required. You must be at least 18 to join"; +const nameMissingErr = "Please provide your name"; + +//dating profile +const uploadingDatingProfilePhotoErr = + "Failed to upload your photo! Please try again."; +const deletingDatingProfilePhotoErr = + "Failed to delete your RedTing profile photo! Please retry"; +const setDatingInfoErr = + "Failed to save your dating preferences! Please try again."; +const deleteDatingProfileErr = + "Failed to delete your RedTing profile! Please retry"; +const getDatingProfileErr = "Failed to get your RedTing profile! Please retry"; +const updateDatingProfileErr = + "Failed to update your RedTing profile! Please retry"; +const ageRangeLbl = "AGE RANGE"; +const mySexualityLbl = "I AM"; +const showMeGendersAndPreferencesLbl = "SHOW ME"; +const menLbl = "MEN"; +const womenLbl = "WOMEN"; +const allGendersLbl = "EVERYONE"; +const mySexualOrientation = "MY SEXUAL ORIENTATION"; +const makeMyOrientationPublicLbl = "Show my orientation on my profile"; +const showMeMyOrientationOnly = + "Only show me others of same sexual orientation"; +const straightSexualOrientationLbl = "straight"; +const questioningSexualOrientationLbl = "questioning"; +const queerSexualOrientationLbl = "queer"; +const panSexualSexualOrientationLbl = "pansexual"; +const demiSexualSexualOrientationLbl = "demisexual"; +const biSexualSexualOrientationLbl = "bisexual"; +const asexualSexualOrientationLbl = "asexual"; +const gaySexualOrientationLbl = "gay"; +const createDatingProfileBtn = "ALL SET"; +const datingProfilePicsMissingErr = 'Please add at least 3 photos'; +const creatingDatingProfilePleaseWait = "just a bit ..."; +const datingPicsHint = + "*Show your face in some of these. Others will be able to see your verification video."; + +//matching +const likingUserFailed = + "OOps! Failed to like the user! Please check your internet connection"; +const dislikingUserFailedErr = "Please check your internet connection"; +const fetchingProfilesToMatchFailed = + "REDTING couldn't load profiles! Please check your internet connection"; +const likeStamp = "FIT"; +const passStamp = "PASS"; +const noMoreProfiles = + "That's all the people we have currently.\nEnjoy chatting with your current matches.\nIn the meantime, please share your experience at RedTing today."; +const feedbackHint = "I like ... but I wish ..."; +const submitFeedbackBtn = "submit"; +const feedbackReceived = "Thank You! Your feedback helps RedTing get better."; +const feedbackNotSentErr = "Oops! Sorry, we could not get that! Please retry"; +const verificationVideoWord = "VERIFICATION CODE: "; + +//home +const homeDestinationLbl = "RedTing"; +const myProfileDestinationLbl = "My Profile"; +const myMatchesDestinationLbl = "Matched"; + +//chat +const errorSendingImageMessage = "Failed to send image"; +const errorSendingTxtMessage = "Failed to send your message"; + +//editing profile +const editDatingPreferences = "Matching Preferences"; +const editProfile = "Edit Profile"; +const updateProfileSuccess = "Your changes have been saved"; +const updateProfileFail = "Failed to save your changes"; +const updateProfileBtn = "Update Profile"; +const selectFromCountryLbl = "From Country"; + +//settings +const editDatingPreferencesSemanticLbl = "edit dating preferences"; +const blindDateSetupSemanticLbl = "click to setup a blind date"; + +//blind date +const blindDatesScreenNavTitle = "Blind Dates"; +const blindDateInstructionsStep1 = "Enter their phone numbers below."; +const blindDateInstructionsStep2 = "Choose an ice-breaker"; +const blindDateInstructionsStep3 = "They will automatically be matched!"; +const friend1PhoneNumberLbl = "friend 1 phone number"; +const friend2PhoneNumberLbl = "friend 2 phone number"; +const blindDateHint = + "*You can only use this feature twice (i.e. connect 4 people at most). Blind Redting is anonymous, and the pair won't be notified that you connected them."; +const blindDateSetupSubtitle = "Know a perfect match that should be redting?"; +const blindDateSetupTitle = "Blind Redting"; +const blindDateIceBreakerLbl = "a fun ice breaker"; +const blindDateIceBreakerHint = "suggest something fun, respectful, & playful"; +const blindDateSetupBtn = "Match Make"; +const blindDateSetupNotSignedIn = + "This user has not joined RedTing yet. Invite them to install the app"; +const blindDateSetupUnknownErr = + "Something went wrong! check your internet & retry"; +const blindDateSetupSuccess = "Awesome! You have setup a blind date."; +const cannotSetupBlindDateWithSelfErr = "" + "You cannot match make yourself!"; +const phoneNumbersMissingCountryCodeErr = + "Please include a country code on each phone number"; +const funIceBreakerMissingErr = + "Please include a fun ice breaker. It's okay to be cheesy!"; +const phoneNumbersInvalid = "Please enter valid phone numbers"; +const cannotSetupBlindDateMaxReached = + "Looks like you have already matched 4 people!"; +const blindDateOtherUserName = "anon"; diff --git a/lib/res/theme.dart b/lib/res/theme.dart new file mode 100644 index 0000000..0f659ff --- /dev/null +++ b/lib/res/theme.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:redting/res/colors.dart'; +import 'package:redting/res/fonts.dart'; + +var appTheme = ThemeData( + useMaterial3: true, colorSchemeSeed: seedColor, textTheme: appTextTheme); + +var darkTheme = appTheme.copyWith(brightness: Brightness.dark); + +var lightTheme = appTheme.copyWith(brightness: Brightness.light); diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..16ec06f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,98 @@ +name: redting +description: IT's A REAL WORLD OUTSIDE - Dating App + +# 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 used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.17.5 <3.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 + google_fonts: ^3.0.1 + get_it: ^7.2.0 + connectivity_plus: ^2.3.5 + hive: ^2.2.3 + firebase_core: ^1.19.2 + firebase_auth: ^3.4.2 + cloud_firestore: ^3.3.0 + firebase_storage: ^10.3.2 + hive_flutter: ^1.1.0 + json_annotation: ^4.6.0 + infinite_scroll_pagination: ^3.1.0 + path: ^1.8.0 + path_provider: ^2.0.8 + hive_generator: ^1.1.2 + meta: ^1.7.0 + bloc: ^8.0.3 + flutter_bloc: ^8.0.1 + image_picker: ^0.8.5+3 + cached_network_image: ^3.2.1 + video_player: ^2.4.5 + video_compress: ^3.1.1 + flutter_image_compress: ^1.1.0 + encrypt: ^5.0.1 + +dev_dependencies: + change_app_package_name: ^1.1.0 + json_serializable: ^6.1.4 + build_runner: ^2.1.7 + 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 + assets: + - assets/images/ + + fonts: + - family: Righteous + fonts: + - asset: assets/fonts/Righteous-Regular.ttf + - family: Rubik + fonts: + - asset: assets/fonts/Rubik-Regular.ttf + - asset: assets/fonts/Rubik-Light.ttf + weight: 300 + diff --git a/redting.iml b/redting.iml new file mode 100644 index 0000000..3844432 --- /dev/null +++ b/redting.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file