├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ └── Info.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift └── .gitignore ├── scripts ├── autoroute.sh ├── clean.sh ├── lang.sh └── build.sh ├── functions ├── tsconfig.dev.json ├── .gitignore ├── tsconfig.json ├── .eslintrc.js └── package.json ├── .firebaserc ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── google │ │ │ │ │ └── recommender_app │ │ │ │ │ └── solution_challenge_2023_recommender_app │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle └── settings.gradle ├── preview ├── dark │ ├── auth.png │ ├── pdf.png │ ├── post.png │ ├── image.png │ ├── profile.png │ ├── search.png │ ├── splash.png │ ├── video.png │ ├── category.png │ ├── onboard1.png │ ├── onboard2.png │ ├── onboard3.png │ ├── onboard4.png │ ├── onboard5.png │ ├── register.png │ ├── settings.png │ ├── leaderboard_problem.png │ └── leaderboard_solution.png └── light │ ├── pdf.png │ ├── auth.png │ ├── image.png │ ├── post.png │ ├── search.png │ ├── splash.png │ ├── video.png │ ├── category.png │ ├── onboard1.png │ ├── onboard2.png │ ├── onboard3.png │ ├── onboard4.png │ ├── onboard5.png │ ├── profile.png │ ├── register.png │ ├── settings.png │ ├── leaderboard_problem.png │ └── leaderboard_solution.png ├── assets └── image │ ├── apple.png │ ├── google.png │ └── launcher.png ├── lib ├── feature │ ├── App │ │ ├── presentation │ │ │ ├── pages │ │ │ │ ├── index.dart │ │ │ │ ├── Leaderboard │ │ │ │ │ └── mixin │ │ │ │ │ │ ├── leaderboard_mixin.dart │ │ │ │ │ │ ├── leaderboard_suggest_mixin.dart │ │ │ │ │ │ └── leaderboard_problem_mixin.dart │ │ │ │ ├── Home │ │ │ │ │ └── mixin │ │ │ │ │ │ └── home_page_mixin.dart │ │ │ │ ├── Category │ │ │ │ │ ├── mixin │ │ │ │ │ │ ├── category_mixin.dart │ │ │ │ │ │ └── category_body_mixin.dart │ │ │ │ │ ├── category_list_page_body.dart │ │ │ │ │ └── category_list_page.dart │ │ │ │ ├── Profile │ │ │ │ │ ├── mixin │ │ │ │ │ │ └── profile_mixin.dart │ │ │ │ │ └── profile_page.dart │ │ │ │ ├── Post │ │ │ │ │ └── post_page.dart │ │ │ │ ├── Suggest │ │ │ │ │ └── comment_suggest_page.dart │ │ │ │ ├── Settings │ │ │ │ │ └── settings_language_options.dart │ │ │ │ ├── Problem │ │ │ │ │ ├── mixin │ │ │ │ │ │ └── comment_problem_page_mixin.dart │ │ │ │ │ └── comment_problem_suggest.dart │ │ │ │ └── Search │ │ │ │ │ └── mixin │ │ │ │ │ └── search_mixin.dart │ │ │ ├── main │ │ │ │ ├── widget │ │ │ │ │ ├── bottom_nav_bar_destinations.dart │ │ │ │ │ └── bottom_nav_bar.dart │ │ │ │ └── main_wrapper.dart │ │ │ ├── bloc │ │ │ │ ├── bloc_search │ │ │ │ │ ├── search_event.dart │ │ │ │ │ └── search_state.dart │ │ │ │ ├── solutionCardCubit │ │ │ │ │ ├── solution_card_state.dart │ │ │ │ │ └── solution_card_cubit.dart │ │ │ │ ├── cubit_profile │ │ │ │ │ ├── profile_state.dart │ │ │ │ │ └── profile_cubit.dart │ │ │ │ ├── cubit_category │ │ │ │ │ ├── category_state.dart │ │ │ │ │ └── category_cubit.dart │ │ │ │ ├── cubit_home_lastSent │ │ │ │ │ ├── home_last_sent_state.dart │ │ │ │ │ └── home_last_sent_cubit.dart │ │ │ │ ├── problemCardCubit │ │ │ │ │ ├── problem_card_state.dart │ │ │ │ │ └── problem_card_cubit.dart │ │ │ │ ├── cubit_comment_problem │ │ │ │ │ └── comment_problem_state.dart │ │ │ │ ├── cubit_home_specialForYou │ │ │ │ │ ├── home_special_for_you_state.dart │ │ │ │ │ └── home_special_for_you_cubit.dart │ │ │ │ ├── cubit_leaderboard_problem │ │ │ │ │ ├── leaderboard_problem_state.dart │ │ │ │ │ └── leaderboard_problem_cubit.dart │ │ │ │ ├── cubit_leaderboard_suggest │ │ │ │ │ ├── leaderboard_suggest_state.dart │ │ │ │ │ └── leaderboard_suggest_cubit.dart │ │ │ │ └── cubit_profile_entity │ │ │ │ │ └── profile_entity_cubit.dart │ │ │ └── widget │ │ │ │ ├── floatingActionButton.dart │ │ │ │ ├── network_image_hero.dart │ │ │ │ ├── sliver_appbar.dart │ │ │ │ ├── category_card.dart │ │ │ │ └── frame_widget.dart │ │ └── data │ │ │ └── models │ │ │ └── category_card_model.dart │ ├── Splash │ │ ├── cubit │ │ │ └── splash_finished_cubit.dart │ │ └── presentation │ │ │ ├── mixin │ │ │ └── splash_mixin.dart │ │ │ └── pages │ │ │ └── splash_page.dart │ ├── Auth │ │ ├── domain │ │ │ ├── usecases │ │ │ │ ├── sign_out.dart │ │ │ │ ├── is_sign_in.dart │ │ │ │ ├── user_usecase.dart │ │ │ │ ├── sign_in_with_google_usecase.dart │ │ │ │ ├── sign_in_with_auth_credential.dart │ │ │ │ ├── sign_in_with_email_usecase.dart │ │ │ │ ├── sign_up_with_email_usecase.dart │ │ │ │ └── get_current_uid_usecase.dart │ │ │ └── repository │ │ │ │ └── auth_repository.dart │ │ ├── presentation │ │ │ ├── bloc │ │ │ │ ├── auth_firebase_bloc │ │ │ │ │ ├── auth_firebase_event.dart │ │ │ │ │ └── auth_firebase_state.dart │ │ │ │ ├── auth_page_bloc │ │ │ │ │ ├── auth_page_state.dart │ │ │ │ │ ├── auth_page_event.dart │ │ │ │ │ └── auth_page_bloc.dart │ │ │ │ └── auth_register_bloc │ │ │ │ │ ├── auth_register_state.dart │ │ │ │ │ ├── auth_register_event.dart │ │ │ │ │ └── auth_register_bloc.dart │ │ │ └── widget │ │ │ │ ├── custom_button.dart │ │ │ │ └── square_box.dart │ │ └── data │ │ │ └── repository │ │ │ └── auth_repository_impl.dart │ ├── Services │ │ └── domain │ │ │ ├── usecases │ │ │ ├── delete_model_usecase.dart │ │ │ ├── translate_text_usecase.dart │ │ │ ├── is_model_downloaded_usecase.dart │ │ │ ├── download_model_usecase.dart │ │ │ ├── get_geo_fire_point_usecase.dart │ │ │ └── get_searched_comments.dart │ │ │ └── repository │ │ │ └── services_repository.dart │ ├── Onboard │ │ ├── onboard_injection_container.dart │ │ └── presentation │ │ │ ├── pages │ │ │ └── mixin │ │ │ │ └── onboard_page_mixin.dart │ │ │ ├── cubit │ │ │ ├── onboard_cubit.dart │ │ │ └── onboard_state.dart │ │ │ └── widgets │ │ │ └── page_content_list.dart │ └── Firestorage │ │ ├── domain │ │ ├── usecases │ │ │ ├── select_files_usecase.dart │ │ │ ├── get_profile_usecase.dart │ │ │ ├── get_comment_problem_usecase.dart │ │ │ ├── delete_profile_usecase.dart │ │ │ ├── get_comment_suggestion_usecase.dart │ │ │ ├── delete_comment_problem_usecase.dart │ │ │ ├── delete_comment_suggestion_usecase.dart │ │ │ ├── profile_last_looked_contents_usecase.dart │ │ │ ├── comment_problem_like_usecase.dart │ │ │ ├── comment_solution_like_usecase.dart │ │ │ ├── upload_files_usecase.dart │ │ │ ├── create_report_usecase.dart │ │ │ ├── update_profile_usecase.dart │ │ │ ├── create_profile_usecase.dart │ │ │ ├── create_comment_problem_usecase.dart │ │ │ ├── update_comment_problem_usecase.dart │ │ │ ├── create_comment_suggestion_usecase.dart │ │ │ ├── update_comment_suggestion_usecase.dart │ │ │ ├── get_comment_problem_list_searched_usecase.dart │ │ │ ├── get_comment_problem_list_last_usecase.dart │ │ │ ├── get_comment_problem_list_according_to_likecount_usecase.dart │ │ │ ├── get_comment_suggest_list_according_to_comment_id_usecase.dart │ │ │ ├── get_comment_problem_list_according_to_tags_usecase.dart │ │ │ ├── get_comment_suggest_list_according_to_likecount_usecase.dart │ │ │ ├── get_comment_problem_list_according_to_category_usecase.dart │ │ │ ├── get_comment_problem_list_according_to_profileID.dart │ │ │ └── index.dart │ │ └── entities │ │ │ ├── report_entities.dart │ │ │ └── comments_suggestions_entities.dart │ │ └── data │ │ └── models │ │ └── report_model.dart ├── core │ ├── init │ │ ├── theme │ │ │ ├── custom_theme.dart │ │ │ ├── dark │ │ │ │ ├── dark_theme.dart │ │ │ │ └── dark_schema.dart │ │ │ ├── light │ │ │ │ ├── light_theme.dart │ │ │ │ └── light_schema.dart │ │ │ └── cubit │ │ │ │ ├── app_theme_state.dart │ │ │ │ └── app_theme_cubit.dart │ │ ├── lang │ │ │ └── language.dart │ │ └── cache │ │ │ └── hive_cache_manager.dart │ ├── utility │ │ ├── helper_class.dart │ │ ├── mixin │ │ │ ├── mounted_mixin.dart │ │ │ └── loading_mixin.dart │ │ └── custom_scroll.dart │ ├── constants │ │ ├── lottie │ │ │ └── lottie_constants.dart │ │ ├── settings │ │ │ └── settings_constants.dart │ │ ├── image │ │ │ └── image_constants.dart │ │ ├── extension │ │ │ ├── list_extension.dart │ │ │ ├── string_extension.dart │ │ │ ├── lang_extension.dart │ │ │ ├── time_extension.dart │ │ │ └── padding.dart │ │ ├── enums │ │ │ ├── language.dart │ │ │ └── firestore_constants.dart │ │ ├── navigation │ │ │ └── navigation_constants.dart │ │ ├── material3 │ │ │ ├── text_style_constant.dart │ │ │ └── material3_desing_constant.dart │ │ └── firestore │ │ │ └── firestore_constants.dart │ ├── logger │ │ └── app_logger.dart │ └── errors │ │ └── exception │ │ └── exception.dart └── injection.dart ├── firebase.json ├── .metadata ├── test └── widget_test.dart ├── .gitignore ├── .github └── workflows │ └── dart.yml └── analysis_options.yaml /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /scripts/autoroute.sh: -------------------------------------------------------------------------------- 1 | flutter packages pub run build_runner build -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /functions/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | ".eslintrc.js" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "recommenderappfikiratlas" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | flutter clean; cd ios; pod deintegrate; pod install; cd..; flutter pub get -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /preview/dark/auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/auth.png -------------------------------------------------------------------------------- /preview/dark/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/pdf.png -------------------------------------------------------------------------------- /preview/dark/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/post.png -------------------------------------------------------------------------------- /preview/light/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/pdf.png -------------------------------------------------------------------------------- /scripts/lang.sh: -------------------------------------------------------------------------------- 1 | flutter pub run easy_localization:generate -O lib/core/init/lang -f keys -S assets/translations -o locale_keys.g.dart -------------------------------------------------------------------------------- /assets/image/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/assets/image/apple.png -------------------------------------------------------------------------------- /assets/image/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/assets/image/google.png -------------------------------------------------------------------------------- /preview/dark/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/image.png -------------------------------------------------------------------------------- /preview/dark/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/profile.png -------------------------------------------------------------------------------- /preview/dark/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/search.png -------------------------------------------------------------------------------- /preview/dark/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/splash.png -------------------------------------------------------------------------------- /preview/dark/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/video.png -------------------------------------------------------------------------------- /preview/light/auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/auth.png -------------------------------------------------------------------------------- /preview/light/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/image.png -------------------------------------------------------------------------------- /preview/light/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/post.png -------------------------------------------------------------------------------- /preview/light/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/search.png -------------------------------------------------------------------------------- /preview/light/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/splash.png -------------------------------------------------------------------------------- /preview/light/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/video.png -------------------------------------------------------------------------------- /assets/image/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/assets/image/launcher.png -------------------------------------------------------------------------------- /preview/dark/category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/category.png -------------------------------------------------------------------------------- /preview/dark/onboard1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/onboard1.png -------------------------------------------------------------------------------- /preview/dark/onboard2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/onboard2.png -------------------------------------------------------------------------------- /preview/dark/onboard3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/onboard3.png -------------------------------------------------------------------------------- /preview/dark/onboard4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/onboard4.png -------------------------------------------------------------------------------- /preview/dark/onboard5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/onboard5.png -------------------------------------------------------------------------------- /preview/dark/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/register.png -------------------------------------------------------------------------------- /preview/dark/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/settings.png -------------------------------------------------------------------------------- /preview/light/category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/category.png -------------------------------------------------------------------------------- /preview/light/onboard1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/onboard1.png -------------------------------------------------------------------------------- /preview/light/onboard2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/onboard2.png -------------------------------------------------------------------------------- /preview/light/onboard3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/onboard3.png -------------------------------------------------------------------------------- /preview/light/onboard4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/onboard4.png -------------------------------------------------------------------------------- /preview/light/onboard5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/onboard5.png -------------------------------------------------------------------------------- /preview/light/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/profile.png -------------------------------------------------------------------------------- /preview/light/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/register.png -------------------------------------------------------------------------------- /preview/light/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/settings.png -------------------------------------------------------------------------------- /preview/dark/leaderboard_problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/leaderboard_problem.png -------------------------------------------------------------------------------- /preview/dark/leaderboard_solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/dark/leaderboard_solution.png -------------------------------------------------------------------------------- /preview/light/leaderboard_problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/leaderboard_problem.png -------------------------------------------------------------------------------- /preview/light/leaderboard_solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/preview/light/leaderboard_solution.png -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" = "force" ] 4 | then 5 | dart run build_runner build --delete-conflicting-outputs 6 | else 7 | dart run build_runner build 8 | fi -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HasancanCakicioglu/Solution_Challenge_2023_FikirAtlasi/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled JavaScript files 2 | lib/**/*.js 3 | lib/**/*.js.map 4 | 5 | # TypeScript v1 declaration files 6 | typings/ 7 | 8 | # Node.js dependency directory 9 | node_modules/ 10 | 11 | # Dotenv 12 | .env 13 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/index.dart: -------------------------------------------------------------------------------- 1 | export 'Category/category_page.dart'; 2 | export 'Home/home_page.dart'; 3 | export 'Leaderboard/leaderboard_page.dart'; 4 | export 'Post/post_page.dart'; 5 | export 'Search/search_page.dart'; 6 | 7 | -------------------------------------------------------------------------------- /lib/core/init/theme/custom_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Custom Theme 4 | abstract class CustomTheme { 5 | ThemeData get themeData; 6 | 7 | FloatingActionButtonThemeData get floatingActionButtonThemeData; 8 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 6 | -------------------------------------------------------------------------------- /lib/core/utility/helper_class.dart: -------------------------------------------------------------------------------- 1 | import 'package:uuid/uuid.dart'; 2 | 3 | /// Helper class 4 | class HelperClass { 5 | static const Uuid _uuid = Uuid(); 6 | 7 | /// Get a unique identifier 8 | static String getUuid() { 9 | return _uuid.v4(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/core/constants/lottie/lottie_constants.dart: -------------------------------------------------------------------------------- 1 | 2 | /// Lottie animation constants 3 | abstract final class LottieConstants { 4 | static String get splashScreen => toLottie('splash_screen_building'); 5 | 6 | static String toLottie(String name) => 'assets/lottie/$name.json'; 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/google/recommender_app/solution_challenge_2023_recommender_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.google.recommender_app.solution_challenge_2023_recommender_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /lib/core/constants/settings/settings_constants.dart: -------------------------------------------------------------------------------- 1 | 2 | /// [AppSettingsConstants] is a class that contains all the constants related to the app settings. 3 | class AppSettingsConstants { 4 | static const String cacheSettingsBoxName = "App_Settings"; 5 | static const String themeMode = "isLight"; 6 | 7 | } -------------------------------------------------------------------------------- /lib/feature/Splash/cubit/splash_finished_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | 3 | class SplashFinishedControllerCubit extends Cubit { 4 | SplashFinishedControllerCubit() : super(false); 5 | 6 | void splashFinishedAtLeastOnce() { 7 | emit(true); 8 | } 9 | } 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/core/constants/image/image_constants.dart: -------------------------------------------------------------------------------- 1 | /// Image Constants 2 | abstract final class ImageConstants { 3 | ImageConstants._init(); 4 | 5 | static String get google => toPng('google'); 6 | static String get apple => toPng('apple'); 7 | 8 | static String toPng(String name) => 'assets/image/$name.png'; 9 | } 10 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/core/constants/extension/list_extension.dart: -------------------------------------------------------------------------------- 1 | 2 | /// List extension to add all elements if not contains 3 | extension ListExtension on List { 4 | void addAllIfNotContains(List elements) { 5 | for (var element in elements) { 6 | if (!contains(element)) { 7 | add(element); 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitReturns": true, 5 | "noUnusedLocals": true, 6 | "outDir": "lib", 7 | "sourceMap": true, 8 | "strict": true, 9 | "target": "es2017", 10 | }, 11 | "compileOnSave": true, 12 | "include": [ 13 | "src" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/sign_out.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 4 | 5 | class SignOutUsecase{ 6 | final AuthRepository repository; 7 | 8 | SignOutUsecase(this.repository); 9 | 10 | Future call()async{ 11 | return await repository.signOut(); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/is_sign_in.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 4 | 5 | class IsSignInUsecase{ 6 | final AuthRepository repository; 7 | 8 | IsSignInUsecase(this.repository); 9 | 10 | Future call()async{ 11 | return await repository.isSignIn(); 12 | } 13 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 3 | 4 | class UserUsecase{ 5 | final AuthRepository repository; 6 | 7 | UserUsecase(this.repository); 8 | 9 | Stream call(){ 10 | return repository.user; 11 | } 12 | } -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/sign_in_with_google_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 4 | 5 | class SignInWithGoogleUsecase{ 6 | final AuthRepository repository; 7 | 8 | SignInWithGoogleUsecase(this.repository); 9 | 10 | Future call()async{ 11 | return await repository.signInWithGoogle(); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/core/utility/mixin/mounted_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Stateful widget mixin to handle loading state 5 | mixin MountedMixin on State { 6 | /// manage your mounted state 7 | Future safeOperation(AsyncCallback callback) async { 8 | if (!mounted) return; 9 | await callback.call(); 10 | } 11 | } -------------------------------------------------------------------------------- /lib/feature/Services/domain/usecases/delete_model_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/repository/services_repository.dart'; 2 | 3 | class DeleteModelUsecase { 4 | final ServicesRepository repository; 5 | 6 | DeleteModelUsecase(this.repository); 7 | 8 | Future call(String model) async { 9 | return await repository.deleteModel(model); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/feature/Services/domain/usecases/translate_text_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/repository/services_repository.dart'; 2 | 3 | class TranslateTextUsecase { 4 | final ServicesRepository repository; 5 | 6 | TranslateTextUsecase(this.repository); 7 | 8 | Future call(String text) async { 9 | return await repository.translateText(text); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/feature/Services/domain/usecases/is_model_downloaded_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/repository/services_repository.dart'; 2 | 3 | class IsModelDownloadedUsecase { 4 | final ServicesRepository repository; 5 | 6 | IsModelDownloadedUsecase(this.repository); 7 | 8 | Future call(String model) async { 9 | return await repository.isModelDownloaded(model); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/feature/Onboard/onboard_injection_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Onboard/presentation/cubit/onboard_cubit.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/injection.dart'; 3 | 4 | /// Function to set up dependency injection for the OnBoard feature 5 | void onBoardInjectionContainer() { 6 | // Cubit or Bloc 7 | sl.registerFactory( 8 | () => OnBoardCubit()); 9 | } 10 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": [ 3 | { 4 | "source": "functions", 5 | "location": "europe-west3", 6 | "codebase": "default", 7 | "ignore": [ 8 | "node_modules", 9 | ".git", 10 | "firebase-debug.log", 11 | "firebase-debug.*.log" 12 | ], 13 | "predeploy": [ 14 | "npm --prefix \"$RESOURCE_DIR\" run lint", 15 | "npm --prefix \"$RESOURCE_DIR\" run build" 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_firebase_bloc/auth_firebase_event.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_firebase_bloc.dart'; 2 | 3 | /// Represents the events for user authentication. 4 | sealed class AuthFirebaseEvent extends Equatable { 5 | const AuthFirebaseEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | /// Event for logging in the user. 12 | class AuthStateChanged extends AuthFirebaseEvent { 13 | const AuthStateChanged(this.user); 14 | 15 | final User? user; 16 | } 17 | -------------------------------------------------------------------------------- /lib/feature/Services/domain/usecases/download_model_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/repository/services_repository.dart'; 2 | 3 | class DownloadModelUsecase { 4 | final ServicesRepository repository; 5 | 6 | DownloadModelUsecase(this.repository); 7 | 8 | Future call(String model, {bool isWifiRequired = true}) async { 9 | return await repository.downloadModel(model, 10 | isWifiRequired: isWifiRequired); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/select_files_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:file_picker/file_picker.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 4 | 5 | class SelectFilesUsecase{ 6 | final FirestoreRepository repository; 7 | 8 | SelectFilesUsecase(this.repository); 9 | 10 | Future?> call(FileType fileType) async { 11 | return await repository.selectFiles(fileType); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/main/widget/bottom_nav_bar_destinations.dart: -------------------------------------------------------------------------------- 1 | part of 'bottom_nav_bar.dart'; 2 | 3 | /// Represents the destinations for the bottom navigation bar. 4 | List _navigationBarDestinations = const [ 5 | NavigationDestination(icon: Icon(Icons.home), label: ""), 6 | NavigationDestination(icon: Icon(Icons.search), label: ""), 7 | NavigationDestination(icon: Icon(Icons.category), label: ""), 8 | NavigationDestination(icon: Icon(Icons.leaderboard), label: ""), 9 | ]; 10 | -------------------------------------------------------------------------------- /lib/feature/Services/domain/usecases/get_geo_fire_point_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:geoflutterfire2/geoflutterfire2.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/repository/services_repository.dart'; 3 | 4 | class GetGeoFirePointUsecase { 5 | final ServicesRepository repository; 6 | 7 | GetGeoFirePointUsecase(this.repository); 8 | 9 | GeoFirePoint call(double latitude, double longitude) { 10 | return repository.getGeoFirePoint(latitude, longitude); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/sign_in_with_auth_credential.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 3 | 4 | class SignInWithAuthCredentialUsecase{ 5 | final AuthRepository repository; 6 | 7 | SignInWithAuthCredentialUsecase(this.repository); 8 | 9 | Future call(AuthCredential credential)async{ 10 | return await repository.signInWithAuthCredential(credential); 11 | } 12 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/sign_in_with_email_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 3 | 4 | class SignInWithEmailAndPasswordUsecase{ 5 | final AuthRepository repository; 6 | 7 | SignInWithEmailAndPasswordUsecase(this.repository); 8 | 9 | Future call(String email,String password)async{ 10 | return await repository.signInWithEmailAndPassword(email, password); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/sign_up_with_email_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 3 | 4 | class SignUpWithEmailAndPasswordUsecase{ 5 | final AuthRepository repository; 6 | 7 | SignUpWithEmailAndPasswordUsecase(this.repository); 8 | 9 | Future call(String email,String password)async{ 10 | return await repository.signUpWithEmailAndPassword(email, password); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/entities/report_entities.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// The model representing the report entity for the Idea Atlas application. 4 | /// 5 | /// The [uid] and [date] parameters are required. 6 | class ReportEntity extends Equatable { 7 | final String? uid; 8 | final DateTime? date; 9 | 10 | /// Creates an instance of [ReportEntity]. 11 | const ReportEntity({ 12 | this.uid, 13 | this.date, 14 | }); 15 | 16 | @override 17 | List get props => [uid]; 18 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_profile_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/profile_entites.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 3 | 4 | class GetProfileUsecase{ 5 | final FirestoreRepository repository; 6 | 7 | GetProfileUsecase(this.repository); 8 | 9 | Future call(String uid)async { 10 | return await repository.getProfile(uid); 11 | } 12 | } -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import GoogleMaps 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | GMSServices.provideAPIKey("your key") 12 | GeneratedPluginRegistrant.register(with: self) 13 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/core/init/theme/dark/dark_theme.dart: -------------------------------------------------------------------------------- 1 | part of "dark_schema.dart"; 2 | 3 | 4 | /// Custom light theme for project design 5 | final class CustomDarkTheme implements CustomTheme { 6 | @override 7 | ThemeData get themeData => ThemeData( 8 | useMaterial3: true, 9 | colorScheme: _darkColorScheme, 10 | floatingActionButtonTheme: floatingActionButtonThemeData, 11 | ); 12 | 13 | @override 14 | final FloatingActionButtonThemeData floatingActionButtonThemeData = 15 | const FloatingActionButtonThemeData(); 16 | } -------------------------------------------------------------------------------- /lib/core/constants/extension/string_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | 3 | /// The `StringLocalization` extension provides a mechanism for converting a [String] object to a localized string. 4 | extension StringLocalization on String { 5 | String get locale => this.tr(); 6 | 7 | } 8 | 9 | /// The `ImagePathExtension` extension provides a mechanism for converting a [String] object to an image path. 10 | extension ImagePathExtension on String { 11 | String get toSVG => 'asset/svg/$this.svg'; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /lib/core/init/theme/light/light_theme.dart: -------------------------------------------------------------------------------- 1 | part of "light_schema.dart"; 2 | 3 | 4 | /// Custom light theme for project design 5 | final class CustomLightTheme implements CustomTheme { 6 | @override 7 | ThemeData get themeData => ThemeData( 8 | useMaterial3: true, 9 | colorScheme: _lightColorScheme, 10 | floatingActionButtonTheme: floatingActionButtonThemeData, 11 | ); 12 | 13 | @override 14 | final FloatingActionButtonThemeData floatingActionButtonThemeData = 15 | const FloatingActionButtonThemeData(); 16 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 3 | 4 | class GetCommentProblemUsecase{ 5 | final FirestoreRepository repository; 6 | 7 | GetCommentProblemUsecase(this.repository); 8 | 9 | Future call(String uid)async { 10 | return await repository.getCommentProblem(uid); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/delete_profile_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 4 | 5 | class DeleteProfileUsecase{ 6 | final FirestoreRepository repository; 7 | 8 | DeleteProfileUsecase(this.repository); 9 | 10 | Future> call(String uid)async { 11 | return await repository.deleteProfile(uid); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/bloc_search/search_event.dart: -------------------------------------------------------------------------------- 1 | part of 'search_bloc.dart'; 2 | 3 | sealed class SearchEvent extends Equatable { 4 | const SearchEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class SearchTextChanged extends SearchEvent { 11 | final String text; 12 | 13 | const SearchTextChanged({required this.text}); 14 | 15 | @override 16 | List get props => [text]; 17 | } 18 | 19 | class SearchSubmitted extends SearchEvent { 20 | const SearchSubmitted(); 21 | 22 | @override 23 | List get props => []; 24 | } 25 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_suggestion_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 3 | 4 | class GetCommentSuggestionUsecase{ 5 | final FirestoreRepository repository; 6 | 7 | GetCommentSuggestionUsecase(this.repository); 8 | 9 | Future> call(String uid)async { 10 | return await repository.getCommentSuggestion(uid); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/feature/Services/domain/usecases/get_searched_comments.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/repository/services_repository.dart'; 4 | 5 | class GetSearchedCommentsUsecase { 6 | final ServicesRepository repository; 7 | 8 | GetSearchedCommentsUsecase(this.repository); 9 | 10 | Future>> call(String text) async { 11 | return await repository.getSearchedComments(text); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/delete_comment_problem_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 4 | 5 | class DeleteCommentProblemUsecase{ 6 | final FirestoreRepository repository; 7 | 8 | DeleteCommentProblemUsecase(this.repository); 9 | 10 | Future> call(String uid)async { 11 | return await repository.deleteCommentProblem(uid); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/delete_comment_suggestion_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 4 | 5 | class DeleteCommentSuggestionUsecase{ 6 | final FirestoreRepository repository; 7 | 8 | DeleteCommentSuggestionUsecase(this.repository); 9 | 10 | Future> call(String uid)async { 11 | return await repository.deleteCommentSuggestion(uid); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/core/constants/extension/lang_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// The `LocaleToString` extension provides a mechanism for converting a [Locale] object to a string. 4 | extension LocaleToString on Locale { 5 | String countryName() { 6 | switch (languageCode) { 7 | case 'tr': 8 | return 'turkish'; 9 | case 'de': 10 | return 'germany'; 11 | case 'en': 12 | return 'english'; 13 | case 'fr': 14 | return 'french'; 15 | case 'ar': 16 | return 'Arapça'; 17 | default: 18 | return 'Unknown Country'; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /lib/core/constants/enums/language.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Enum to represent supported locales in the app. 4 | enum Locales { 5 | tr(Locale('tr', 'TR')), 6 | en(Locale('en', 'US')), 7 | de(Locale('de', 'DE')), 8 | fr(Locale('fr', 'FR')); 9 | 10 | /// The `Locale` associated with each enum value. 11 | final Locale locale; 12 | const Locales(this.locale); 13 | 14 | /// A list of supported locales in the app. 15 | static const List supportedLocales = [ 16 | Locale('tr', 'TR'), 17 | Locale('en', 'US'), 18 | Locale('de', 'DE'), 19 | Locale('fr', 'FR'), 20 | ]; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/profile_last_looked_contents_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 3 | 4 | class ProfileLastLookedContentsUsecase{ 5 | final FirestoreRepository repository; 6 | 7 | ProfileLastLookedContentsUsecase(this.repository); 8 | 9 | Future call(CommentProblemEntity commentProblemEntity) async { 10 | return await repository.profileLastLookedContents(commentProblemEntity); 11 | } 12 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/comment_problem_like_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 4 | 5 | class CommentProblemLikeUsecase { 6 | final FirestoreRepository repository; 7 | 8 | CommentProblemLikeUsecase(this.repository); 9 | 10 | Future> call( 11 | String commentID, bool isLike) async { 12 | return await repository.commentProblemLike(commentID, isLike); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/comment_solution_like_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 4 | 5 | class CommentSolutionLikeUsecase { 6 | final FirestoreRepository repository; 7 | 8 | CommentSolutionLikeUsecase(this.repository); 9 | 10 | Future> call( 11 | String solutionID, bool isLike) async { 12 | return await repository.commentSolutionLike(solutionID, isLike); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | tasks.register("clean", Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /lib/core/init/theme/cubit/app_theme_state.dart: -------------------------------------------------------------------------------- 1 | part of 'app_theme_cubit.dart'; 2 | 3 | /// Theme State 4 | final class ThemeState extends Equatable { 5 | const ThemeState({this.themeMode = ThemeMode.system}); 6 | 7 | /// Create a [ThemeState] from a map 8 | factory ThemeState.fromMap(Map map) { 9 | return ThemeState(themeMode: ThemeMode.values[map['themeMode'] as int]); 10 | } 11 | 12 | /// The theme mode 13 | final ThemeMode themeMode; 14 | 15 | /// Convert the [ThemeState] to a map 16 | Map toMap() { 17 | return {'themeMode': themeMode.index}; 18 | } 19 | 20 | @override 21 | List get props => [themeMode]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/upload_files_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:solution_challenge_2023_recommender_app/core/constants/enums/firestore_constants.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class UploadFilesUsecase { 7 | final FirestoreRepository repository; 8 | 9 | UploadFilesUsecase(this.repository); 10 | 11 | Future>> call( 12 | FirestoreAllowedFileTypes firestoreAllowedFileTypes, 13 | List files) async { 14 | return await repository.uploadFiles(firestoreAllowedFileTypes, files); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/solutionCardCubit/solution_card_state.dart: -------------------------------------------------------------------------------- 1 | part of 'solution_card_cubit.dart'; 2 | 3 | 4 | final class SolutionCardState extends Equatable { 5 | const SolutionCardState({ 6 | this.liked = true, 7 | this.translationText,}); 8 | 9 | final bool liked; 10 | final String? translationText; 11 | 12 | 13 | copyWith({ 14 | bool? liked, 15 | String? translationText, 16 | String? titleTranslation, 17 | }) { 18 | return SolutionCardState( 19 | liked: liked ?? this.liked, 20 | translationText: translationText, 21 | ); 22 | } 23 | 24 | @override 25 | List get props => [liked,translationText]; 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/core/constants/enums/firestore_constants.dart: -------------------------------------------------------------------------------- 1 | /// This file contains the enums used in the firestore database 2 | enum CategoriesEnum { 3 | education("Education"), 4 | transport("Transport"), 5 | economy("Economy"), 6 | health("Health"), 7 | technology("Technology"), 8 | artsAndCulture("Arts and Culture"), 9 | sport("Sport"), 10 | entertainment("Entertainment"), 11 | environment("Environment"), 12 | policy("Policy"), 13 | security("Security"), 14 | other("Other"); 15 | 16 | final String value; 17 | const CategoriesEnum(this.value); 18 | } 19 | 20 | /// This file contains the enums used in the firestore database 21 | enum FirestoreAllowedFileTypes { image, video, audio, pdf, unknown } 22 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Leaderboard/mixin/leaderboard_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Leaderboard/leaderboard_page.dart'; 3 | 4 | /// This mixin is used to handle the scroll events of the [LeaderBoard] page. 5 | mixin LeaderBoardMixin on State { 6 | late ScrollController scrollControllerNested; 7 | 8 | @override 9 | void initState() { 10 | super.initState(); 11 | scrollControllerNested = ScrollController(); 12 | } 13 | 14 | @override 15 | void dispose() { 16 | scrollControllerNested.dispose(); 17 | super.dispose(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/create_report_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/report_entities.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class CreateReportUsecase{ 7 | final FirestoreRepository repository; 8 | 9 | CreateReportUsecase(this.repository); 10 | 11 | Future> call(ReportEntity reportEntity)async { 12 | return await repository.createReport(reportEntity); 13 | } 14 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/update_profile_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/profile_entites.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class UpdateProfileUsecase{ 7 | final FirestoreRepository repository; 8 | 9 | UpdateProfileUsecase(this.repository); 10 | 11 | Future> call(ProfileEntity profileEntity)async { 12 | return await repository.updateProfile(profileEntity); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_profile/profile_state.dart: -------------------------------------------------------------------------------- 1 | part of 'profile_cubit.dart'; 2 | 3 | sealed class ProfileState extends Equatable { 4 | const ProfileState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class ProfileInitial extends ProfileState {} 11 | 12 | final class ProfileLoading extends ProfileState {} 13 | 14 | final class ProfileLoaded extends ProfileState { 15 | final List comments; 16 | final bool isLoadingNewData; 17 | final bool isError; 18 | 19 | const ProfileLoaded(this.comments, 20 | {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData]; 24 | } 25 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_category/category_state.dart: -------------------------------------------------------------------------------- 1 | part of 'category_cubit.dart'; 2 | 3 | 4 | sealed class CategoryState extends Equatable { 5 | const CategoryState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class CategoryInitial extends CategoryState {} 12 | 13 | final class CategoryLoading extends CategoryState {} 14 | 15 | final class CategoryLoaded extends CategoryState { 16 | final List comments; 17 | final bool isLoadingNewData; 18 | final bool isError; 19 | 20 | const CategoryLoaded(this.comments, {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData,isError]; 24 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Home/mixin/home_page_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Home/home_page.dart'; 3 | 4 | 5 | /// Mixin for HomePageView 6 | /// 7 | /// This mixin contains the scroll controller for the nested scroll view in the home page. 8 | mixin HomePageMixin on State { 9 | late ScrollController scrollControllerNested; 10 | 11 | @override 12 | void initState() { 13 | super.initState(); 14 | scrollControllerNested = ScrollController(); 15 | } 16 | 17 | 18 | @override 19 | void dispose() { 20 | scrollControllerNested.dispose(); 21 | super.dispose(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/create_profile_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/profile_entites.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class CreateProfileUsecase { 7 | final FirestoreRepository repository; 8 | 9 | CreateProfileUsecase(this.repository); 10 | 11 | Future> call( 12 | ProfileEntity profileEntity) async { 13 | return await repository.createProfile(profileEntity); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/core/utility/mixin/loading_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Stateful widget mixin to handle loading state 4 | mixin LoadingMixin on State { 5 | final ValueNotifier _isLoadingNotifier = ValueNotifier(false); 6 | 7 | /// Get loading state 8 | bool get isLoading => _isLoadingNotifier.value; 9 | 10 | /// Get loading state notifier 11 | ValueNotifier get isLoadingNotifier => _isLoadingNotifier; 12 | 13 | /// Change loading state 14 | /// If user want to force, give a bool value 15 | void changeLoading({bool? isLoading}) { 16 | if (isLoading != null) _isLoadingNotifier.value = isLoading; 17 | 18 | _isLoadingNotifier.value = !_isLoadingNotifier.value; 19 | } 20 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Category/mixin/category_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Category/category_list_page.dart'; 3 | 4 | /// Category mixin for [CategoryListPageView] 5 | /// 6 | /// This mixin contains the scroll controller for the nested scroll view 7 | mixin CategoryMixin on State { 8 | late ScrollController scrollControllerNested; 9 | 10 | @override 11 | void initState() { 12 | super.initState(); 13 | scrollControllerNested = ScrollController(); 14 | } 15 | 16 | @override 17 | void dispose() { 18 | scrollControllerNested.dispose(); 19 | super.dispose(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/widget/floatingActionButton.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | 3 | import 'package:auto_route/auto_route.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/constants/navigation/navigation_constants.dart'; 6 | 7 | class FloatingActionButtonWidget extends StatelessWidget { 8 | const FloatingActionButtonWidget({super.key,required this.heroTag}); 9 | final String heroTag; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return FloatingActionButton( 14 | heroTag: heroTag, 15 | child: const Icon(Icons.edit), 16 | onPressed: () async { 17 | context.router.pushNamed(NavigationConstants.Post); 18 | }, 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/create_comment_problem_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class CreateCommentProblemUsecase{ 7 | final FirestoreRepository repository; 8 | 9 | CreateCommentProblemUsecase(this.repository); 10 | 11 | Future> call(CommentProblemEntity commentProblemEntity)async { 12 | return await repository.createCommentProblem(commentProblemEntity); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/update_comment_problem_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class UpdateCommentProblemUsecase{ 7 | final FirestoreRepository repository; 8 | 9 | UpdateCommentProblemUsecase(this.repository); 10 | 11 | Future> call(CommentProblemEntity commentProblemEntity)async { 12 | return await repository.updateCommentProblem(commentProblemEntity); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/feature/Services/domain/repository/services_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:geoflutterfire2/geoflutterfire2.dart'; 4 | 5 | abstract class ServicesRepository { 6 | // Google ml-kit translation 7 | Future isModelDownloaded(String model); 8 | Future deleteModel(String model); 9 | Future translateText(String text); 10 | Future downloadModel(String model, {bool isWifiRequired = true}); 11 | 12 | // Google cloud function 13 | Future>> getSearchedComments( 14 | String text); 15 | 16 | // GeoFlutterFire 17 | GeoFirePoint getGeoFirePoint(double latitude, double longitude); 18 | } 19 | -------------------------------------------------------------------------------- /functions/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:import/errors", 10 | "plugin:import/warnings", 11 | "plugin:import/typescript", 12 | "google", 13 | "plugin:@typescript-eslint/recommended", 14 | ], 15 | parser: "@typescript-eslint/parser", 16 | parserOptions: { 17 | project: ["tsconfig.json", "tsconfig.dev.json"], 18 | sourceType: "module", 19 | }, 20 | ignorePatterns: [ 21 | "/lib/**/*", // Ignore built files. 22 | ], 23 | plugins: [ 24 | "@typescript-eslint", 25 | "import", 26 | ], 27 | rules: { 28 | "quotes": ["error", "double"], 29 | "import/no-unresolved": 0, 30 | "indent": ["error", 2], 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/create_comment_suggestion_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class CreateCommentSuggestionUsecase{ 7 | final FirestoreRepository repository; 8 | 9 | CreateCommentSuggestionUsecase(this.repository); 10 | 11 | Future> call(CommentSuggestionEntity commentSuggestionEntity)async { 12 | return await repository.createCommentSuggestion(commentSuggestionEntity); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/update_comment_suggestion_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class UpdateCommentSuggestionUsecase{ 7 | final FirestoreRepository repository; 8 | 9 | UpdateCommentSuggestionUsecase(this.repository); 10 | 11 | Future> call(CommentSuggestionEntity commentSuggestionEntity)async { 12 | return await repository.updateCommentSuggestion(commentSuggestionEntity); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_home_lastSent/home_last_sent_state.dart: -------------------------------------------------------------------------------- 1 | part of 'home_last_sent_cubit.dart'; 2 | 3 | 4 | sealed class HomeLastSentState extends Equatable { 5 | const HomeLastSentState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class HomeLastSentInitial extends HomeLastSentState {} 12 | 13 | final class HomeLastSentLoading extends HomeLastSentState {} 14 | 15 | final class HomeLastSentLoaded extends HomeLastSentState { 16 | final List comments; 17 | final bool isLoadingNewData; 18 | final bool isError; 19 | 20 | const HomeLastSentLoaded(this.comments, {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData]; 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/problemCardCubit/problem_card_state.dart: -------------------------------------------------------------------------------- 1 | part of 'problem_card_cubit.dart'; 2 | 3 | 4 | final class ProblemCardState extends Equatable { 5 | const ProblemCardState({ 6 | this.liked = true, 7 | this.translation, 8 | this.titleTranslation}); 9 | 10 | final bool liked; 11 | final String? translation; 12 | final String? titleTranslation; 13 | 14 | copyWith({ 15 | bool? liked, 16 | String? translation, 17 | String? titleTranslation, 18 | }) { 19 | return ProblemCardState( 20 | liked: liked ?? this.liked, 21 | translation: translation ?? this.translation, 22 | titleTranslation: titleTranslation ?? this.titleTranslation, 23 | ); 24 | } 25 | 26 | @override 27 | List get props => [liked,translation,titleTranslation]; 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_comment_problem/comment_problem_state.dart: -------------------------------------------------------------------------------- 1 | part of 'comment_problem_cubit.dart'; 2 | 3 | 4 | sealed class CommentProblemState extends Equatable { 5 | const CommentProblemState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class CommentProblemInitial extends CommentProblemState {} 12 | 13 | final class CommentProblemLoading extends CommentProblemState {} 14 | 15 | final class CommentProblemLoaded extends CommentProblemState { 16 | final List comments; 17 | final bool isLoadingNewData; 18 | final bool isError; 19 | 20 | const CommentProblemLoaded(this.comments, {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData]; 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_home_specialForYou/home_special_for_you_state.dart: -------------------------------------------------------------------------------- 1 | part of 'home_special_for_you_cubit.dart'; 2 | 3 | 4 | sealed class HomeSpecialForYouState extends Equatable { 5 | const HomeSpecialForYouState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class HomeSpecialForYouInitial extends HomeSpecialForYouState {} 12 | 13 | final class HomeSpecialForYouLoading extends HomeSpecialForYouState {} 14 | 15 | final class HomeSpecialForYouLoaded extends HomeSpecialForYouState { 16 | final List comments; 17 | final bool isLoadingNewData; 18 | final bool isError; 19 | 20 | const HomeSpecialForYouLoaded(this.comments, {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData]; 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_page_bloc/auth_page_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_page_bloc.dart'; 2 | 3 | /// Represents the state for user authentication. 4 | class AuthPageBlocState extends Equatable { 5 | const AuthPageBlocState({ 6 | this.email = "", 7 | this.password = "", 8 | this.passwordIsObscure = true, 9 | }); 10 | final String email; 11 | final String password; 12 | final bool passwordIsObscure; 13 | 14 | AuthPageBlocState copyWith({ 15 | String? email, 16 | String? password, 17 | bool? passwordIsObscure, 18 | }) { 19 | return AuthPageBlocState( 20 | email: email ?? this.email, 21 | password: password ?? this.password, 22 | passwordIsObscure: passwordIsObscure ?? this.passwordIsObscure, 23 | ); 24 | } 25 | 26 | @override 27 | List get props => [email, password, passwordIsObscure]; 28 | } 29 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_leaderboard_problem/leaderboard_problem_state.dart: -------------------------------------------------------------------------------- 1 | part of 'leaderboard_problem_cubit.dart'; 2 | 3 | 4 | sealed class LeaderboardProblemState extends Equatable { 5 | const LeaderboardProblemState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class LeaderboardProblemInitial extends LeaderboardProblemState {} 12 | 13 | final class LeaderboardProblemLoading extends LeaderboardProblemState {} 14 | 15 | final class LeaderboardProblemLoaded extends LeaderboardProblemState { 16 | final List comments; 17 | final bool isLoadingNewData; 18 | final bool isError; 19 | 20 | const LeaderboardProblemLoaded(this.comments, {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData]; 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_leaderboard_suggest/leaderboard_suggest_state.dart: -------------------------------------------------------------------------------- 1 | part of 'leaderboard_suggest_cubit.dart'; 2 | 3 | 4 | sealed class LeaderboardSuggestState extends Equatable { 5 | const LeaderboardSuggestState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class LeaderboardSuggestInitial extends LeaderboardSuggestState {} 12 | 13 | final class LeaderboardSuggestLoading extends LeaderboardSuggestState {} 14 | 15 | final class LeaderboardSuggestLoaded extends LeaderboardSuggestState { 16 | final List comments; 17 | final bool isLoadingNewData; 18 | final bool isError; 19 | 20 | const LeaderboardSuggestLoaded(this.comments, {this.isLoadingNewData = false, this.isError = false}); 21 | 22 | @override 23 | List get props => [comments, isLoadingNewData]; 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/core/logger/app_logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | 3 | /// The `AppLogger` class provides a centralized logging mechanism for the Flutter app. 4 | final class AppLogger extends Logger { 5 | /// Constructor to initialize `AppLogger`. 6 | AppLogger() 7 | : super( 8 | printer: _CustomPrinter(), 9 | ); 10 | } 11 | 12 | final class _CustomPrinter extends PrettyPrinter { 13 | _CustomPrinter({ 14 | int methodCount = 2, 15 | int errorMethodCount = 8, 16 | int lineLength = 120, 17 | bool colors = true, 18 | bool printEmojis = true, 19 | bool printTime = true, 20 | }) : super( 21 | methodCount: methodCount, 22 | errorMethodCount: errorMethodCount, 23 | lineLength: lineLength, 24 | colors: colors, 25 | printEmojis: printEmojis, 26 | printTime: printTime, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_list_searched_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 5 | 6 | class GetCommentProblemListSearchedUsecase { 7 | final FirestoreRepository repository; 8 | 9 | GetCommentProblemListSearchedUsecase(this.repository); 10 | 11 | Future?>> call( 12 | List text, 13 | {gettingData = 20}) async { 14 | return await repository.getCommentProblemListSearched(text, 15 | gettingData: gettingData); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/feature/Auth/domain/usecases/get_current_uid_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 2 | 3 | /// `GetCurrentUIdUsecase` is a use case class responsible for retrieving the UID (User ID) 4 | /// of the currently authenticated user. 5 | /// 6 | /// This class encapsulates the business logic for fetching the UID and interacts with the 7 | /// provided [AuthRepository] for authentication-related operations. 8 | class GetCurrentUIdUsecase { 9 | final AuthRepository repository; 10 | 11 | /// Constructs a `GetCurrentUIdUsecase` with the provided [repository]. 12 | GetCurrentUIdUsecase(this.repository); 13 | 14 | /// Invokes the use case and returns a [Future] containing the UID as a [String], 15 | /// or `null` if no user is authenticated. 16 | Future call() async{ 17 | return await repository.getCurrentUId(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/constants/navigation/navigation_constants.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | /// This class is used to store all the navigation constants used in the app. 4 | class NavigationConstants { 5 | static const Splash = '/Splash'; 6 | static const Auth = '/Auth'; 7 | static const AuthState = '/AuthState'; 8 | 9 | static const Home = '/Home'; 10 | static const mainWrapper = '/MainWrapper'; 11 | static const OnBoarding = '/OnBoarding'; 12 | static const CategoryList = '/CategoryList'; 13 | static const Profile = '/Profile'; 14 | static const Settings = '/Settings'; 15 | static const SettingsLanguageOptions = '/SettingsLanguageOptions'; 16 | static const Post = '/Post'; 17 | static const GoogleMaps = '/GoogleMaps'; 18 | static const CommentsPage = '/CommentsPage'; 19 | static const SuggestionsPage = '/SuggestionsPage'; 20 | static const RegisterPage = '/RegisterPage'; 21 | 22 | 23 | } -------------------------------------------------------------------------------- /lib/feature/Onboard/presentation/pages/mixin/onboard_page_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/constants/extension/context_extension.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Onboard/presentation/pages/onboard_page.dart'; 4 | 5 | /// [OnBoardPageMixin] is a mixin that provides common functionality for managing 6 | /// the state and page controller in an onboarding page. 7 | mixin OnBoardPageMixin on State { 8 | late PageController pageController; 9 | late bool isDark; 10 | 11 | 12 | @override 13 | void initState() { 14 | pageController = PageController(initialPage: 0); 15 | super.initState(); 16 | isDark = context.isDarkMode; 17 | } 18 | 19 | @override 20 | void dispose() { 21 | pageController.dispose(); 22 | super.dispose(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version "7.3.0" apply false 27 | } 28 | 29 | include ":app" 30 | -------------------------------------------------------------------------------- /lib/feature/App/data/models/category_card_model.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:solution_challenge_2023_recommender_app/core/constants/enums/firestore_constants.dart'; 3 | 4 | //Category card model 5 | final class CategoryCardModel{ 6 | 7 | final CategoriesEnum categoriesEnum; 8 | final String image; 9 | 10 | CategoryCardModel({required this.categoriesEnum, required this.image}); 11 | 12 | @override 13 | //to string method 14 | String toString() { 15 | return 'CategoryCardModel{title: $categoriesEnum, image: $image}'; 16 | } 17 | 18 | //equals method 19 | @override 20 | bool operator ==(Object other) => 21 | identical(this, other) || 22 | other is CategoryCardModel && 23 | runtimeType == other.runtimeType && 24 | categoriesEnum == other.categoriesEnum && 25 | image == other.image; 26 | 27 | //hash code method 28 | @override 29 | int get hashCode => categoriesEnum.hashCode ^ image.hashCode; 30 | 31 | } -------------------------------------------------------------------------------- /lib/core/init/theme/cubit/app_theme_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:hydrated_bloc/hydrated_bloc.dart'; 4 | 5 | part 'app_theme_state.dart'; 6 | 7 | /// Theme Cubit 8 | class ThemeCubit extends HydratedCubit { 9 | ThemeCubit() : super(const ThemeState()); 10 | 11 | /// Takes a [Brightness] object and changes the theme 12 | /// 13 | /// Give the brightness of the theme you want to change to. 14 | void changeTheme({required Brightness brightness}) { 15 | brightness == Brightness.dark 16 | ? emit(const ThemeState(themeMode: ThemeMode.dark)) 17 | : emit(const ThemeState(themeMode: ThemeMode.light)); 18 | } 19 | 20 | @override 21 | ThemeState? fromJson(Map json) { 22 | return ThemeState.fromMap(json); 23 | } 24 | 25 | @override 26 | Map? toJson(ThemeState state) { 27 | return state.toMap(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Category/mixin/category_body_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_category/category_cubit.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Category/category_list_page_body.dart'; 5 | 6 | /// [CategoryBodyMixin] is a mixin class for [CategoryListPageBody] to use in [State] class. 7 | /// 8 | /// This mixin class contains the lifecycle methods of the [State] class. 9 | mixin CategoryBodyMixin 10 | on State { 11 | @override 12 | void initState() { 13 | super.initState(); 14 | BlocProvider.of(context) 15 | .getCommentProblemListLastRefresh(widget.categoryEnum); 16 | } 17 | 18 | @override 19 | void dispose() { 20 | super.dispose(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/bloc_search/search_state.dart: -------------------------------------------------------------------------------- 1 | part of 'search_bloc.dart'; 2 | 3 | final class SearchState extends Equatable { 4 | const SearchState({ 5 | this.comments = const [], 6 | this.text = "", 7 | this.isLoadingNewData = false, 8 | this.isError = false, 9 | }); 10 | 11 | final List comments; 12 | final String text; 13 | final bool isLoadingNewData; 14 | final bool isError; 15 | 16 | 17 | SearchState copyWith({ 18 | List? comments, 19 | String? text, 20 | bool? isLoadingNewData, 21 | bool? isError, 22 | }) { 23 | return SearchState( 24 | comments: comments ?? this.comments, 25 | text: text ?? this.text, 26 | isLoadingNewData: isLoadingNewData ?? this.isLoadingNewData, 27 | isError: isError ?? this.isError, 28 | ); 29 | } 30 | 31 | @override 32 | List get props => [comments, text, isLoadingNewData, isError]; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_list_last_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 7 | 8 | 9 | 10 | class GetCommentProblemListLastUsecase{ 11 | final FirestoreRepository repository; 12 | 13 | GetCommentProblemListLastUsecase(this.repository); 14 | 15 | Future,QueryDocumentSnapshot?>>> call(QueryDocumentSnapshot? startAfter,{gettingData = 20}) async { 16 | return await repository.getCommentProblemListLast(startAfter,gettingData: gettingData); 17 | } 18 | } -------------------------------------------------------------------------------- /lib/feature/Onboard/presentation/cubit/onboard_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | part "onboard_state.dart"; 5 | 6 | /// [OnBoardCubit] is a Cubit responsible for managing the state 7 | /// of whether the onboarding process has been finished. 8 | class OnBoardCubit extends Cubit { 9 | /// Initializes the cubit with the initial state of onboarding completion set to `false`. 10 | OnBoardCubit() : super(const OnBoardState()); 11 | 12 | /// Changes the state to indicate whether the onboarding process has been finished. 13 | void onBoardFinishedChangeState(bool isFinished) { 14 | emit(state.copyWith(finished: isFinished)); 15 | } 16 | 17 | /// Changes the state to update the current index and percentage of completion. 18 | void onBoardIndexChangeState(int currentIndex) { 19 | emit(state.copyWith( 20 | index: currentIndex, percentage: (currentIndex + 1) * 0.20)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/widget/custom_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// The `CustomButtonWidget` is a reusable widget for creating custom elevated buttons. 4 | /// It takes the `text` to be displayed on the button and an `onPressed` callback 5 | /// function to be executed when the button is pressed. 6 | class CustomButtonWidget extends StatelessWidget { 7 | /// The text to be displayed on the button. 8 | final String text; 9 | 10 | /// The callback function to be executed when the button is pressed. 11 | final VoidCallback onPressed; 12 | 13 | /// Creates a new instance of `CustomButtonWidget`. 14 | /// 15 | /// The `text` and `onPressed` parameters are required. 16 | const CustomButtonWidget({Key? key, required this.text, required this.onPressed}) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ElevatedButton( 21 | onPressed: onPressed, 22 | child: Text(text), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_firebase_bloc/auth_firebase_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_firebase_bloc.dart'; 2 | 3 | /// Represents the states for user authentication. 4 | sealed class AuthFirebaseState extends Equatable { 5 | const AuthFirebaseState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | /// Initial state for user authentication. 12 | final class AuthInitial extends AuthFirebaseState{} 13 | 14 | /// Loading state for user authentication. 15 | final class AuthLoading extends AuthFirebaseState{} 16 | 17 | /// State for when the user is logged in. 18 | final class AuthLoggedIn extends AuthFirebaseState { 19 | final User user; 20 | 21 | const AuthLoggedIn(this.user); 22 | } 23 | 24 | /// State for when the user is logged out. 25 | final class AuthLoggedOut extends AuthFirebaseState {} 26 | 27 | /// State for when there is an error during authentication. 28 | class AuthError extends AuthFirebaseState { 29 | final String error; 30 | 31 | const AuthError(this.error); 32 | } 33 | -------------------------------------------------------------------------------- /lib/core/constants/material3/text_style_constant.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// The `AppTextStyle` class provides a centralized mechanism for managing App text styles. 6 | class AppTextStyle { 7 | static const MINI_BOLD_DESCRIPTION_TEXT = TextStyle( 8 | fontSize: 13, 9 | 10 | letterSpacing: 3, 11 | fontWeight: FontWeight.w600, 12 | ); 13 | static const MIDDLE_BUTTON_TEXT = TextStyle( 14 | fontSize: 20, 15 | letterSpacing: 5, 16 | 17 | fontWeight: FontWeight.w300); 18 | 19 | static const MINI_DESCRIPTION_TEXT = 20 | TextStyle(fontSize: 13, letterSpacing: 3); 21 | 22 | static const MINI_DEFAULT_DESCRIPTION_TEXT = 23 | TextStyle(fontSize: 13); 24 | 25 | static const MINI_DESCRIPTION_BOLD = 26 | TextStyle(fontSize: 13 ,fontWeight: FontWeight.w600); 27 | 28 | 29 | static const MIDDLE_DESCRIPTION_TEXT = 30 | TextStyle(fontSize: 16, letterSpacing: 1,fontWeight: FontWeight.w600); 31 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/data/models/report_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/report_entities.dart'; 2 | 3 | /// A concrete implementation of [ReportEntity] representing a report model. 4 | class ReportModel extends ReportEntity{ 5 | 6 | const ReportModel({ 7 | String? uid, 8 | DateTime? date, 9 | }) : super( 10 | uid: uid, 11 | date: date, 12 | ); 13 | 14 | /// Constructs a [ReportModel] instance from a JSON map. 15 | factory ReportModel.fromJson(Map json) => ReportModel( 16 | uid: json["uid"], 17 | date: json["date"], 18 | ); 19 | 20 | /// Converts a [ReportModel] instance to a JSON map. 21 | factory ReportModel.fromEntity(ReportEntity reportEntity) => ReportModel( 22 | uid: reportEntity.uid, 23 | date: reportEntity.date, 24 | ); 25 | 26 | /// Converts a [ReportModel] instance to a JSON map. 27 | Map toJson() => { 28 | "uid": uid, 29 | "date": date, 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/widget/network_image_hero.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// NetworkImageHero widget is used to display an image from a network source 4 | class NetworkImageHero extends StatelessWidget { 5 | const NetworkImageHero({super.key, required this.image,required this.tag}); 6 | final String image; 7 | final String tag; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | body: Center(child: Hero( 13 | tag: image, 14 | child: ClipRRect( 15 | borderRadius: BorderRadius.circular(12.0), 16 | child: Image.network( 17 | image, 18 | fit: BoxFit.cover, 19 | filterQuality: FilterQuality.low, 20 | loadingBuilder: (context, widget, imageChunk) { 21 | return imageChunk == null 22 | ? widget 23 | : const CircularProgressIndicator(); 24 | }, 25 | ), 26 | ), 27 | ),) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_likecount_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 7 | 8 | 9 | class GetCommentProblemListAccordingToLikeCountUseCase{ 10 | final FirestoreRepository repository; 11 | 12 | GetCommentProblemListAccordingToLikeCountUseCase(this.repository); 13 | 14 | Future,QueryDocumentSnapshot?>>> call(QueryDocumentSnapshot? startAfter,{gettingData = 20}) async { 15 | return await repository.getCommentProblemListAccordingToLikeCount(startAfter,gettingData: gettingData); 16 | } 17 | } -------------------------------------------------------------------------------- /lib/core/constants/firestore/firestore_constants.dart: -------------------------------------------------------------------------------- 1 | /// Firestore Constants 2 | /// 3 | abstract final class FirestoreConstants { 4 | // Collections 5 | static const String collectionCommentsProblems = 'Comments'; 6 | static const String collectionProfiles = 'Profiles'; 7 | static const String collectionReports = 'Reports'; 8 | static const String collectionCommentsSuggestions = 'Suggestions'; 9 | 10 | // Comments Categories 11 | static const String categoryEducation = 'Education'; 12 | static const String categoryTransport = 'Transport'; 13 | static const String categoryEconomy = 'Economy'; 14 | static const String categoryHealth = 'Health'; 15 | static const String categoryTechnology = 'Technology'; 16 | static const String categoryArtsAndCulture = 'Arts_and_Culture'; 17 | static const String categorySport = 'Sport'; 18 | static const String categoryEntertainment = 'Entertainment'; 19 | static const String categoryEnvironment = 'Environment'; 20 | static const String categoryPolicy = 'Policy'; 21 | static const String categoryOther = "Security"; 22 | } 23 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 17 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 18 | - platform: android 19 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 20 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_suggest_list_according_to_comment_id_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 6 | 7 | 8 | 9 | class GetCommentSuggestListAccordingToCommentIDUsecase{ 10 | final FirestoreRepository repository; 11 | 12 | GetCommentSuggestListAccordingToCommentIDUsecase(this.repository); 13 | 14 | Future,QueryDocumentSnapshot?>>> call(String commentID,QueryDocumentSnapshot? startAfter,{gettingData = 20})async{ 15 | return await repository.getCommentSuggestListAccordingToCommentID(commentID, startAfter,gettingData: gettingData); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_tags_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 6 | 7 | class GetCommentProblemListAccordingToTagsUsecase { 8 | final FirestoreRepository repository; 9 | 10 | GetCommentProblemListAccordingToTagsUsecase(this.repository); 11 | 12 | Future< 13 | Either< 14 | FirebaseUnknowFailure, 15 | Tuple2, 16 | QueryDocumentSnapshot?>>> call( 17 | QueryDocumentSnapshot? startAfter, 18 | {gettingData = 20}) async { 19 | return await repository.getCommentProblemListAccordingToTags(startAfter, 20 | gettingData: gettingData); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_register_bloc/auth_register_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_register_bloc.dart'; 2 | 3 | /// Represents the state for user registration/authentication. 4 | final class AuthRegisterState extends Equatable { 5 | const AuthRegisterState({ 6 | this.email = "", 7 | this.passwordFirst = "", 8 | this.passwordSecond = "", 9 | this.passwordIsObscure = true, 10 | }); 11 | final String email; 12 | final String passwordFirst; 13 | final String passwordSecond; 14 | final bool passwordIsObscure; 15 | 16 | AuthRegisterState copyWith({ 17 | String? email, 18 | String? passwordFirst, 19 | String? passwordSecond, 20 | bool? passwordIsObscure, 21 | }) { 22 | return AuthRegisterState( 23 | email: email ?? this.email, 24 | passwordFirst: passwordFirst ?? this.passwordFirst, 25 | passwordSecond: passwordSecond ?? this.passwordSecond, 26 | passwordIsObscure: passwordIsObscure ?? this.passwordIsObscure, 27 | ); 28 | } 29 | 30 | @override 31 | List get props => [email, passwordFirst,passwordSecond, passwordIsObscure]; 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "scripts": { 4 | "lint": "eslint --ext .js,.ts .", 5 | "build": "tsc", 6 | "build:watch": "tsc --watch", 7 | "serve": "npm run build && firebase emulators:start --only functions", 8 | "shell": "npm run build && firebase functions:shell", 9 | "start": "npm run shell", 10 | "deploy": "firebase deploy --only functions", 11 | "logs": "firebase functions:log" 12 | }, 13 | "engines": { 14 | "node": "18" 15 | }, 16 | "main": "lib/index.js", 17 | "dependencies": { 18 | "@pinecone-database/pinecone": "^2.0.1", 19 | "dotenv": "^16.3.2", 20 | "firebase-admin": "^11.11.1", 21 | "firebase-functions": "^4.6.0", 22 | "geofire": "^6.0.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node-fetch": "^2.6.11", 26 | "@typescript-eslint/eslint-plugin": "^5.12.0", 27 | "@typescript-eslint/parser": "^5.12.0", 28 | "eslint": "^8.9.0", 29 | "eslint-config-google": "^0.14.0", 30 | "eslint-plugin-import": "^2.25.4", 31 | "firebase-functions-test": "^3.1.0", 32 | "typescript": "^4.9.0" 33 | }, 34 | "private": true 35 | } 36 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_suggest_list_according_to_likecount_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 6 | 7 | class GetCommentSuggestionListAccordingToLikeCountUseCase { 8 | final FirestoreRepository repository; 9 | 10 | GetCommentSuggestionListAccordingToLikeCountUseCase(this.repository); 11 | 12 | Future< 13 | Either< 14 | FirebaseUnknowFailure, 15 | Tuple2, 16 | QueryDocumentSnapshot?>>> call( 17 | QueryDocumentSnapshot? startAfter, 18 | {gettingData = 20}) async { 19 | return await repository.getCommentSuggestListAccordingToLikeCount( 20 | startAfter, 21 | gettingData: gettingData); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Profile/mixin/profile_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/utility/custom_scroll.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_profile/profile_cubit.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Profile/profile_body.dart'; 6 | 7 | mixin ProfileMixin on State { 8 | late CustomScrollController scrollControllerProfilePage; 9 | 10 | @override 11 | void initState() { 12 | super.initState(); 13 | 14 | scrollControllerProfilePage = CustomScrollController(); 15 | scrollControllerProfilePage.addListener( 16 | () { 17 | if (scrollControllerProfilePage.isMaxExtent()) { 18 | context.read().getCommentProblemListLast(widget.profileID); 19 | } 20 | }, 21 | ); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | scrollControllerProfilePage.dispose(); 27 | super.dispose(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_category_usecase.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/constants/enums/firestore_constants.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 8 | 9 | class GetCommentProblemListAccordingToCategoryUsecase{ 10 | final FirestoreRepository repository; 11 | 12 | GetCommentProblemListAccordingToCategoryUsecase(this.repository); 13 | 14 | Future,QueryDocumentSnapshot?>>> call(CategoriesEnum categoriesEnum,QueryDocumentSnapshot? startAfter,{gettingData = 20}) async { 15 | return await repository.getCommentProblemListAccordingToCategory(categoriesEnum, startAfter,gettingData: gettingData); 16 | } 17 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Post/post_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/bloc_post/post_bloc.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Post/post_body.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/injection.dart'; 7 | 8 | /// This page is the post page of the application. 9 | @RoutePage() 10 | class PostPageView extends StatefulWidget { 11 | const PostPageView({Key? key,this.isProblem = true,this.commentID}) : super(key: key); 12 | final bool isProblem; 13 | final String? commentID; 14 | 15 | @override 16 | State createState() => _PostPageViewState(); 17 | } 18 | 19 | class _PostPageViewState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return BlocProvider( 23 | create: (context) => sl.get(), 24 | 25 | child: PostBody(isProblem: widget.isProblem,commendID: widget.commentID,) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_profileID.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: file_names 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/errors/failure/failure.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/repository/firestore_repository.dart'; 8 | 9 | class GetCommentProblemListAccordingToProfileIDUsecase { 10 | final FirestoreRepository repository; 11 | 12 | GetCommentProblemListAccordingToProfileIDUsecase(this.repository); 13 | 14 | Future< 15 | Either< 16 | FirebaseUnknowFailure, 17 | Tuple2, 18 | QueryDocumentSnapshot?>>> call( 19 | String profileID, QueryDocumentSnapshot? startAfter, 20 | {gettingData = 20}) async { 21 | return await repository.getCommentProblemListAccordingToProfileID( 22 | profileID, startAfter, 23 | gettingData: gettingData); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:solution_challenge_2023_recommender_app/app.dart'; 11 | 12 | 13 | 14 | void main() { 15 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 16 | // Build our app and trigger a frame. 17 | await tester.pumpWidget(const MyApp()); 18 | 19 | // Verify that our counter starts at 0. 20 | expect(find.text('0'), findsOneWidget); 21 | expect(find.text('1'), findsNothing); 22 | 23 | // Tap the '+' icon and trigger a frame. 24 | await tester.tap(find.byIcon(Icons.add)); 25 | await tester.pump(); 26 | 27 | // Verify that our counter has incremented. 28 | expect(find.text('0'), findsNothing); 29 | expect(find.text('1'), findsOneWidget); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # Code generations 14 | *.g.dart 15 | 16 | # IntelliJ related 17 | *.iml 18 | *.ipr 19 | *.iws 20 | .idea/ 21 | 22 | # The .vscode folder contains launch configuration and tasks you configure in 23 | # VS Code which you may wish to be included in version control, so this line 24 | # is commented out by default. 25 | #.vscode/ 26 | 27 | # Flutter/Dart/Pub related 28 | **/doc/api/ 29 | **/ios/Flutter/.last_build_id 30 | .dart_tool/ 31 | .flutter-plugins 32 | .flutter-plugins-dependencies 33 | .pub-cache/ 34 | .pub/ 35 | /build/ 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | 49 | # Firebase configs 50 | /android/app/google-services.json 51 | /ios/Runner/GoogleService-Info.plist 52 | /ios/firebase_app_id_file.json 53 | /lib/firebase_options.dart 54 | 55 | 56 | # Dotenv 57 | *.env 58 | 59 | 60 | # GoogleSecrets 61 | /ios/Runner/AppDelegate.swift 62 | /android/app/src/main/AndroidManifest.xml -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Suggest/comment_suggest_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/widget/comments_suggestion_card.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 5 | 6 | @RoutePage() 7 | class CommentSuggestionPageView extends StatefulWidget { 8 | const CommentSuggestionPageView( 9 | {super.key, required this.commentSuggestionEntity}); 10 | final CommentSuggestionEntity commentSuggestionEntity; 11 | 12 | @override 13 | State createState() => 14 | _CommentSuggestionPageViewState(); 15 | } 16 | 17 | class _CommentSuggestionPageViewState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | centerTitle: true, 23 | ), 24 | body: SingleChildScrollView( 25 | child: CommentsSuggestionCard( 26 | commentSolutionEntity: widget.commentSuggestionEntity, 27 | canGo: false, 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/injection.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/init/navigation/app_router.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/logger/app_logger.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/app_injection_container.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/auth_injection_container.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/firestorage_injection_container.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/feature/Onboard/onboard_injection_container.dart'; 8 | import 'package:solution_challenge_2023_recommender_app/feature/Services/services_injection_container.dart'; 9 | 10 | final sl = GetIt.instance; 11 | 12 | abstract final class LocatorGetIt { 13 | /// Responsible for registering all the dependencies 14 | static Future setup() async { 15 | // Blocs 16 | sl 17 | ..registerFactory(() => AppLogger()) 18 | ..registerSingleton(AppRouter()); 19 | 20 | authInjectionContainer(); 21 | onBoardInjectionContainer(); 22 | appInjectionContainer(); 23 | firestoreInjectionContainer(); 24 | servicesInjectionContainer(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_profile_entity/profile_entity_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/profile_entites.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/create_profile_usecase.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_profile_usecase.dart'; 5 | 6 | class ProfileEntityCubit extends Cubit { 7 | ProfileEntityCubit(this.createProfileUsecase, this.getProfileUsecase) 8 | : super(const ProfileEntity()); 9 | CreateProfileUsecase createProfileUsecase; 10 | GetProfileUsecase getProfileUsecase; 11 | 12 | // Profil bilgilerini güncellemek için kullanılan metod 13 | void updateProfile(ProfileEntity profileEntity) { 14 | emit(profileEntity); 15 | } 16 | 17 | Future getOrSetProfile() async { 18 | await createProfileUsecase.call(const ProfileEntity()).then((value) async{ 19 | value.fold((l) async{ 20 | }, (r) async{ 21 | if (r != null){ 22 | final profileEntityUser = await getProfileUsecase.call(r); 23 | updateProfile(profileEntityUser); 24 | } 25 | }); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Profile/profile_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_profile/profile_cubit.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Profile/profile_body.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/injection.dart'; 8 | 9 | @RoutePage() 10 | class ProfilePageView extends StatefulWidget { 11 | const ProfilePageView({super.key, required this.profileID}); 12 | final String profileID; 13 | 14 | @override 15 | State createState() => _ProfilePageViewState(); 16 | } 17 | 18 | class _ProfilePageViewState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return BlocProvider( 22 | create: (context) => sl.get() 23 | ..getCommentProblemListLastRefresh(widget.profileID), 24 | child: Scaffold( 25 | appBar: AppBar( 26 | title: const Text("Profile").tr(), 27 | ), 28 | body: ProfileBody(profileID: widget.profileID,), 29 | )); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/core/constants/material3/material3_desing_constant.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// The `Material3Design` class provides a centralized mechanism for managing Material3 design constants. 4 | class Material3Design { 5 | // Page Paddings 6 | static const double smallPagePadding = 8.0; 7 | static const double mediumPagePadding = 16.0; 8 | static const double largePagePadding = 24.0; 9 | 10 | // Text Paddings 11 | static const EdgeInsets smallTextPadding = EdgeInsets.all(4.0); 12 | static const EdgeInsets mediumTextPadding = EdgeInsets.all(8.0); 13 | static const EdgeInsets largeTextPadding = EdgeInsets.all(16.0); 14 | 15 | // Button Paddings 16 | static const EdgeInsets smallButtonPadding = EdgeInsets.all(4.0); 17 | static const EdgeInsets mediumButtonPadding = EdgeInsets.all(8.0); 18 | static const EdgeInsets largeButtonPadding = EdgeInsets.all(16.0); 19 | 20 | 21 | // Other Paddings 22 | static const double smallPadding = 4.0; 23 | static const double mediumPadding = 8.0; 24 | static const double largePadding = 16.0; 25 | 26 | 27 | // Text Styles 28 | static const TextStyle smallText = TextStyle(fontSize: 12.0); 29 | static const TextStyle mediumText = TextStyle(fontSize: 16.0); 30 | static const TextStyle largeText = TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold); 31 | 32 | 33 | 34 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/solutionCardCubit/solution_card_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/comment_solution_like_usecase.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/usecases/translate_text_usecase.dart'; 5 | 6 | part 'solution_card_state.dart'; 7 | 8 | class SolutionCardCubit extends Cubit { 9 | CommentSolutionLikeUsecase commentProblemLikeUsecase; 10 | TranslateTextUsecase translateTextUsecase; 11 | SolutionCardCubit( 12 | this.commentProblemLikeUsecase, 13 | this.translateTextUsecase 14 | ) : super(const SolutionCardState()); 15 | 16 | void setLike(bool liked) => emit(state.copyWith(liked: liked)); 17 | 18 | void likeComment(String commentID, bool isLike) async { 19 | final failureOrVoid = await commentProblemLikeUsecase.call( 20 | commentID, 21 | isLike, 22 | ); 23 | failureOrVoid.fold( 24 | (failure) => emit(state.copyWith(liked: !isLike)), 25 | (r) => emit(state.copyWith(liked: isLike)), 26 | ); 27 | } 28 | 29 | void translateText(String text) async { 30 | final translateText = await translateTextUsecase.call(text); 31 | emit(state.copyWith(translationText: translateText)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Settings/settings_language_options.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/constants/extension/lang_extension.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/init/lang/language.dart'; 6 | 7 | 8 | @RoutePage() 9 | class SettingsLanguageOptionsPageView extends StatelessWidget { 10 | const SettingsLanguageOptionsPageView({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: const Text("selectLanguage").tr(), 17 | ), 18 | body: ListView.builder( 19 | itemCount:LanguageManager.supportedLocalesLanguages.length, 20 | itemBuilder: (context, index) { 21 | return InkWell( 22 | onTap: () { 23 | AutoRouter.of(context).pop(LanguageManager.supportedLocalesLanguages[index]); 24 | }, 25 | child: ListTile( 26 | title: Text(LanguageManager.supportedLocalesLanguages[index].countryName()).tr(), 27 | subtitle: Text(LanguageManager.supportedLocalesLanguages[index].toString()), 28 | trailing: const Icon(Icons.arrow_back_ios), 29 | ), 30 | ); 31 | },), 32 | ); 33 | } 34 | } -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/entities/comments_suggestions_entities.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:equatable/equatable.dart'; 3 | 4 | /// The model representing the comment suggestion entity for the Idea Atlas application. 5 | /// 6 | /// The [uid], [profileId], [commentProblemID], [text], [date], [likeCount], [pdf], [images], [videos], [profileName], and [profileImage] parameters are required. 7 | class CommentSuggestionEntity extends Equatable { 8 | final String? uid; 9 | final String? profileId; 10 | final String? commentProblemID; 11 | final String? text; 12 | final String? date; 13 | final int? likeCount; 14 | final List? pdf; 15 | final List? images; 16 | final List? videos; 17 | final String? profileName; 18 | final String? profileImage; 19 | 20 | const CommentSuggestionEntity({ 21 | this.uid, 22 | this.profileId, 23 | this.commentProblemID, 24 | this.text, 25 | this.date, 26 | this.likeCount, 27 | this.pdf, 28 | this.images, 29 | this.videos, 30 | this.profileName, 31 | this.profileImage, 32 | }); 33 | 34 | @override 35 | List get props => [ 36 | uid, 37 | profileId, 38 | commentProblemID, 39 | text, 40 | date, 41 | likeCount, 42 | pdf, 43 | images, 44 | videos, 45 | profileName, 46 | profileImage 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_page_bloc/auth_page_event.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_page_bloc.dart'; 2 | 3 | /// Represents the events for user authentication. 4 | sealed class AuthPageBlocEvent extends Equatable { 5 | const AuthPageBlocEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | /// Event for changing the email during authentication. 12 | final class AuthPageEmailChanged extends AuthPageBlocEvent { 13 | final String email; 14 | 15 | const AuthPageEmailChanged({required this.email}); 16 | 17 | @override 18 | List get props => [email]; 19 | } 20 | 21 | /// Event for changing the password during authentication. 22 | final class AuthPagePasswordChanged extends AuthPageBlocEvent { 23 | final String password; 24 | 25 | const AuthPagePasswordChanged({required this.password}); 26 | 27 | @override 28 | List get props => [password]; 29 | } 30 | 31 | /// Event for changing the obscuration status of the password during authentication. 32 | final class AuthPagePasswordObscureChanged extends AuthPageBlocEvent { 33 | final bool passwordIsObscure; 34 | const AuthPagePasswordObscureChanged({required this.passwordIsObscure}); 35 | 36 | @override 37 | List get props => [passwordIsObscure]; 38 | } 39 | 40 | /// Event for submitting the authentication form. 41 | final class AuthPageSubmitted extends AuthPageBlocEvent { 42 | const AuthPageSubmitted(); 43 | } 44 | -------------------------------------------------------------------------------- /lib/core/init/lang/language.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '../../constants/enums/language.dart'; 4 | 5 | 6 | /// `LanguageManager` is responsible for managing language-related configurations. 7 | final class LanguageManager extends EasyLocalization { 8 | 9 | /// Constructor to initialize `LanguageManager`. 10 | LanguageManager({ 11 | required super.child, 12 | super.key, 13 | }) : super( 14 | saveLocale: true, 15 | supportedLocales: supportedLocalesLanguages, 16 | path: _translationPath, 17 | useOnlyLangCode: true, 18 | ); 19 | 20 | /// The path where translation files are stored. 21 | static const String _translationPath = 'assets/translations'; 22 | 23 | /// A list of supported locales in the app. 24 | static List get supportedLocalesLanguages => [ 25 | Locales.tr.locale, 26 | Locales.en.locale, 27 | Locales.de.locale, 28 | Locales.fr.locale, 29 | ]; 30 | 31 | /// Method to update the language. 32 | static Future updateLanguage({ 33 | required BuildContext context, 34 | required Locale value, 35 | }) => 36 | context.setLocale(value); 37 | 38 | 39 | /// Method to reset the language. 40 | static Future setLocaleLanguage({ 41 | required BuildContext context, 42 | }) => 43 | context.resetLocale(); 44 | } 45 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/main/widget/bottom_nav_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/bottomNavBar/bottomNavBar_cubit.dart'; 4 | part 'bottom_nav_bar_destinations.dart'; 5 | 6 | /// Represents the bottom navigation bar. 7 | class MainWrapperNavigationBar extends StatelessWidget { 8 | const MainWrapperNavigationBar({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return BlocBuilder( 13 | builder: (BuildContext context, BottomNavBarState state) { 14 | return AnimatedContainer( 15 | duration: const Duration(milliseconds: 300), 16 | height: (state.bottomNavBarVisibleState.isVisible) 17 | ? kBottomNavigationBarHeight + 3 18 | : 0.0, 19 | child: Wrap( 20 | children: [ 21 | NavigationBar( 22 | selectedIndex: BlocProvider.of(context) 23 | .state 24 | .bottomNavBarPages 25 | .index, 26 | onDestinationSelected: (index) => 27 | BlocProvider.of(context) 28 | .onDestinationSelected(index), 29 | destinations: _navigationBarDestinations) 30 | ], 31 | ), 32 | ); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/problemCardCubit/problem_card_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/comment_problem_like_usecase.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Services/domain/usecases/translate_text_usecase.dart'; 5 | 6 | part 'problem_card_state.dart'; 7 | 8 | class ProblemCardCubit extends Cubit { 9 | TranslateTextUsecase translateTextUsecase; 10 | CommentProblemLikeUsecase commentProblemLikeUsecase; 11 | ProblemCardCubit( 12 | this.commentProblemLikeUsecase, 13 | this.translateTextUsecase, 14 | ) : super(const ProblemCardState()); 15 | 16 | void setLike(bool liked) => emit(state.copyWith(liked: liked)); 17 | 18 | void likeComment(String commentID, bool isLike) async { 19 | final failureOrVoid = await commentProblemLikeUsecase.call( 20 | commentID, 21 | isLike, 22 | ); 23 | failureOrVoid.fold( 24 | (failure) => emit(state.copyWith(liked: !isLike)), 25 | (r) => emit(state.copyWith(liked: isLike)), 26 | ); 27 | } 28 | 29 | void translateText(String text,String title) async { 30 | final translateText = await translateTextUsecase.call(text); 31 | final translateTitle = await translateTextUsecase.call(title); 32 | emit(state.copyWith(translation: translateText,titleTranslation: translateTitle)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Dart 7 | 8 | on: 9 | push: 10 | branches: [ "main" ] 11 | pull_request: 12 | branches: [ "main" ] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | # Note: This workflow uses the latest stable version of the Dart SDK. 22 | # You can specify other versions if desired, see documentation here: 23 | # https://github.com/dart-lang/setup-dart/blob/main/README.md 24 | # - uses: dart-lang/setup-dart@v1 25 | - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 26 | 27 | - name: Install dependencies 28 | run: dart pub get 29 | 30 | # Uncomment this step to verify the use of 'dart format' on each commit. 31 | # - name: Verify formatting 32 | # run: dart format --output=none --set-exit-if-changed . 33 | 34 | # Consider passing '--fatal-infos' for slightly stricter analysis. 35 | - name: Analyze project source 36 | run: dart analyze 37 | 38 | # Your project will need to have tests in test/ and a dependency on 39 | # package:test for this step to succeed. Note that Flutter projects will 40 | # want to change this to 'flutter test'. 41 | - name: Run tests 42 | run: dart test 43 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /lib/core/init/cache/hive_cache_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:hive_flutter/hive_flutter.dart'; 4 | 5 | /// A base class for all the cache clients in the app 6 | /// Any new cache client should extend this class by specifying the type of data it will be storing 7 | abstract class HiveCacheManager { 8 | String get boxName; 9 | Box? _box; 10 | 11 | Future init() async { 12 | registerAdapters(); 13 | if (!(_box?.isOpen ?? false)) { 14 | _box = await Hive.openBox(boxName); 15 | } 16 | } 17 | 18 | void registerAdapters(); 19 | 20 | Future get(String key) async { 21 | return _box?.get(key); 22 | } 23 | 24 | List? getAll() { 25 | return _box?.values.toList(); 26 | } 27 | 28 | Future add(T item) async { 29 | await _box?.add(item); 30 | } 31 | 32 | Future addList(List items) async { 33 | await _box?.addAll(items); 34 | } 35 | 36 | Future put(String key, T item) async { 37 | await _box?.put(key, item); 38 | } 39 | 40 | Future putMultiple(Map mapObject) async { 41 | await _box?.putAll(mapObject); 42 | } 43 | 44 | Future delete(String key) async { 45 | await _box?.delete(key); 46 | } 47 | 48 | Future deleteAt(int index) async { 49 | await _box?.deleteAt(index); 50 | } 51 | 52 | Future deleteMultiple(List keys) async { 53 | await _box?.deleteAll(keys); 54 | } 55 | 56 | Future deleteAll() async { 57 | await _box?.clear(); 58 | } 59 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Problem/mixin/comment_problem_page_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/utility/custom_scroll.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_comment_problem/comment_problem_cubit.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Problem/comment_problem_suggest.dart'; 6 | 7 | mixin CommentProblemPageMixin on State { 8 | late CustomScrollController scrollControllerCommentProblemPage; 9 | 10 | @override 11 | void initState() { 12 | super.initState(); 13 | context.read().getCommentProblemListLastRefresh(widget.commentProblemEntity.uid!); 14 | scrollControllerCommentProblemPage = CustomScrollController(); 15 | scrollControllerCommentProblemPage.addListener(() { 16 | if (scrollControllerCommentProblemPage.position.pixels == 17 | scrollControllerCommentProblemPage.position.maxScrollExtent) { 18 | context.read().getCommentProblemListLast(widget.commentProblemEntity.uid!); 19 | } 20 | }); 21 | context.read().profileLastLookedContents(widget.commentProblemEntity); 22 | 23 | } 24 | 25 | @override 26 | void dispose() { 27 | scrollControllerCommentProblemPage.dispose(); 28 | super.dispose(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/core/init/theme/light/light_schema.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/init/theme/custom_theme.dart'; 3 | part 'light_theme.dart'; 4 | 5 | /// Light Theme 6 | const _lightColorScheme = ColorScheme( 7 | brightness: Brightness.light, 8 | primary: Color(0xFF6750A4), 9 | onPrimary: Color(0xFFFFFFFF), 10 | primaryContainer: Color(0xFFEADDFF), 11 | onPrimaryContainer: Color(0xFF21005D), 12 | secondary: Color(0xFF625B71), 13 | onSecondary: Color(0xFFFFFFFF), 14 | secondaryContainer: Color(0xFFE8DEF8), 15 | onSecondaryContainer: Color(0xFF1D192B), 16 | tertiary: Color(0xFF7D5260), 17 | onTertiary: Color(0xFFFFFFFF), 18 | tertiaryContainer: Color(0xFFFFD8E4), 19 | onTertiaryContainer: Color(0xFF31111D), 20 | error: Color(0xFFB3261E), 21 | onError: Color(0xFFFFFFFF), 22 | errorContainer: Color(0xFFF9DEDC), 23 | onErrorContainer: Color(0xFF410E0B), 24 | outline: Color(0xFF79747E), 25 | background: Color.fromARGB(255, 255, 255, 255), 26 | onBackground: Color(0xFF1C1B1F), 27 | //surface: Color(0xFFFFFBFE), 28 | surface: Color.fromARGB(245, 255, 255, 255), 29 | onSurface: Color(0xFF1C1B1F), 30 | surfaceVariant: Color(0xFFE7E0EC), 31 | onSurfaceVariant: Color(0xFF49454F), 32 | inverseSurface: Color(0xFF313033), 33 | onInverseSurface: Color(0xFFF4EFF4), 34 | inversePrimary: Color(0xFFD0BCFF), 35 | shadow: Color(0xFF000000), 36 | surfaceTint: Color.fromARGB(255, 255, 255, 255), 37 | outlineVariant: Color(0xFFCAC4D0), 38 | scrim: Color(0xFF000000), 39 | ); -------------------------------------------------------------------------------- /lib/core/errors/exception/exception.dart: -------------------------------------------------------------------------------- 1 | /// A base exception class representing errors in the Dart language. 2 | /// Subclasses should be created to represent specific error scenarios. 3 | class DartLanguageException implements Exception { 4 | /// The title of the exception. 5 | final String title; 6 | 7 | /// The detailed message describing the exception. 8 | final String message; 9 | 10 | /// Constructs a [DartLanguageException] with optional [title] and [message]. 11 | DartLanguageException({this.title = 'Dart Language Error', this.message = 'An error occurred in the Dart language'}); 12 | 13 | @override 14 | String toString() { 15 | return '$title: $message'; 16 | } 17 | } 18 | 19 | /// An exception representing a null pointer error in the Dart language. 20 | class DartNullPointerException extends DartLanguageException { 21 | /// Constructs a [DartNullPointerException] with optional [title] and [message]. 22 | DartNullPointerException({String title = 'Null Pointer Error', String message = 'A null pointer error occurred in the Dart language'}) 23 | : super(title: title, message: message); 24 | } 25 | 26 | /// An exception representing an index out of range error in the Dart language. 27 | class DartIndexOutOfRangeException extends DartLanguageException { 28 | /// Constructs a [DartIndexOutOfRangeException] with optional [title] and [message]. 29 | DartIndexOutOfRangeException({String title = 'Index Out of Range Error', String message = 'An index out of range error occurred in the Dart language'}) 30 | : super(title: title, message: message); 31 | } 32 | -------------------------------------------------------------------------------- /lib/core/init/theme/dark/dark_schema.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:solution_challenge_2023_recommender_app/core/init/theme/custom_theme.dart'; 3 | part 'dark_theme.dart'; 4 | 5 | /// Dark Theme 6 | const _darkColorScheme = ColorScheme( 7 | brightness: Brightness.dark, 8 | primary: Color(0xFFD0BCFF), 9 | //primary: Color(0xFF22842), 10 | onPrimary: Color(0xFF381E72), 11 | primaryContainer: Color(0xFF4F378B), 12 | onPrimaryContainer: Color(0xFFEADDFF), 13 | secondary: Color(0xFFCCC2DC), 14 | onSecondary: Color(0xFF332D41), 15 | secondaryContainer: Color(0xFF4A4458), 16 | onSecondaryContainer: Color(0xFFE8DEF8), 17 | tertiary: Color(0xFFEFB8C8), 18 | onTertiary: Color(0xFF492532), 19 | tertiaryContainer: Color(0xFF633B48), 20 | onTertiaryContainer: Color(0xFFFFD8E4), 21 | error: Color(0xFFF2B8B5), 22 | onError: Color(0xFF601410), 23 | errorContainer: Color(0xFF8C1D18), 24 | onErrorContainer: Color(0xFFF9DEDC), 25 | outline: Color(0xFF938F99), 26 | //background: Color(0xFF1C1B1F), 27 | background: Color.fromARGB(255, 20, 20, 20), 28 | onBackground: Color(0xFFE6E1E5), 29 | surface: Color.fromARGB(255, 31, 31, 31), 30 | onSurface: Color(0xFFE6E1E5), 31 | surfaceVariant: Color(0xFF49454F), 32 | onSurfaceVariant: Color(0xFFCAC4D0), 33 | inverseSurface: Color(0xFFE6E1E5), 34 | onInverseSurface: Color(0xFF313033), 35 | inversePrimary: Color(0xFF6750A4), 36 | shadow: Color(0xFF000000), 37 | surfaceTint: Color.fromARGB(255, 0, 0, 0), 38 | outlineVariant: Color(0xFF49454F), 39 | scrim: Color(0xFF000000), 40 | 41 | ); 42 | -------------------------------------------------------------------------------- /lib/core/constants/extension/time_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | 3 | /// The `TimeAgoExtension` extension provides a mechanism for converting a [DateTime] object to a time ago string. 4 | extension TimeAgoExtension on DateTime { 5 | String timeAgo() { 6 | final now = DateTime.now(); 7 | final difference = now.difference(this); 8 | 9 | if (difference.inSeconds < 60) { 10 | return 'secondago'.tr(args: ['${difference.inSeconds}']); 11 | } else if (difference.inMinutes < 60) { 12 | return 'minuteago'.tr(args: ['${difference.inMinutes}']); 13 | } else if (difference.inHours < 24) { 14 | return 'hourago'.tr(args: ['${difference.inHours}']); 15 | } else if (difference.inDays < 7) { 16 | return 'dayago'.tr(args: ['${difference.inDays}']); 17 | } else if ((difference.inDays / 7).floor() == 1) { 18 | return 'weekago'.tr(args: ["1"]); 19 | } else if (difference.inDays < 30) { 20 | return 'weekago'.tr(args: ['${(difference.inDays / 7).floor()}']); 21 | } else if (difference.inDays < 365) { 22 | return 'monthago'.tr(args: ['${(difference.inDays / 30).floor()}']); 23 | } else { 24 | return 'yearago'.tr(args: ['${(difference.inDays / 365).floor()}']); 25 | } 26 | } 27 | } 28 | 29 | /// The `TimeAgoStringExtension` extension provides a mechanism for converting a [String] object to a time ago string. 30 | extension TimeAgoStringExtension on String { 31 | String timeAgoString() { 32 | final DateTime dateTime = DateTime.parse(this); 33 | return dateTime.timeAgo(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/core/utility/custom_scroll.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Custom Scroll Controller 4 | class CustomScrollController extends ScrollController { 5 | CustomScrollController() : super(); 6 | 7 | double get previousScrollOffset => _previousScrollOffset.first; 8 | final List _previousScrollOffset = [0.0]; 9 | 10 | /// Set previous scroll offset 11 | set previousScrollOffset(double value) { 12 | if (_previousScrollOffset.length > 1) { 13 | _previousScrollOffset.removeAt(0); 14 | } 15 | _previousScrollOffset.add(value); 16 | } 17 | 18 | /// Get distance of scroll 19 | double distanceOfScroll() { 20 | return position.pixels - previousScrollOffset; 21 | } 22 | 23 | /// Check if the scroll is at max extent 24 | bool isMaxExtent() { 25 | return position.pixels >= position.maxScrollExtent; 26 | } 27 | 28 | /// Check if the scroll is at min extent 29 | bool isMinExtent() { 30 | return position.pixels <= position.minScrollExtent; 31 | } 32 | 33 | /// New function to animate based on position 34 | void animateToClosestState() { 35 | final double threshold = position.maxScrollExtent / 2; // Adjust as needed 36 | if (position.pixels > threshold) { 37 | animateTo(position.maxScrollExtent, 38 | duration: const Duration(milliseconds: 100), curve: Curves.easeOut); 39 | } else { 40 | animateTo(0.0, 41 | duration: const Duration(milliseconds: 100), curve: Curves.easeOut); 42 | } 43 | } 44 | 45 | @override 46 | void notifyListeners() { 47 | previousScrollOffset = position.pixels; 48 | super.notifyListeners(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/core/constants/extension/padding.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension PaddingExtension on Widget { 4 | /// Adds padding to the widget on all sides. 5 | /// 6 | /// Example: 7 | /// Text('Hello').paddedAll(8.0); 8 | Padding paddedAll(double padding) { 9 | return Padding( 10 | padding: EdgeInsets.all(padding), 11 | child: this, 12 | ); 13 | } 14 | 15 | /// Adds horizontal padding to the widget. 16 | /// 17 | /// Example: 18 | /// Text('Hello').paddedHorizontal(16.0); 19 | Padding paddedHorizontal(double padding) { 20 | return Padding( 21 | padding: EdgeInsets.symmetric(horizontal: padding), 22 | child: this, 23 | ); 24 | } 25 | 26 | /// Adds vertical padding to the widget. 27 | /// 28 | /// Example: 29 | /// Text('Hello').paddedVertical(24.0); 30 | Padding paddedVertical(double padding) { 31 | return Padding( 32 | padding: EdgeInsets.symmetric(vertical: padding), 33 | child: this, 34 | ); 35 | } 36 | 37 | /// Adds symmetric padding to the widget. 38 | /// 39 | /// Example: 40 | /// Text('Hello').paddedSymmetric(horizontal: 8.0, vertical: 16.0); 41 | Padding paddedSymmetric({double horizontal = 0, double vertical = 0}) { 42 | return Padding( 43 | padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), 44 | child: this, 45 | ); 46 | } 47 | 48 | /// Adds custom padding to the widget. 49 | /// 50 | /// Example: 51 | /// Text('Hello').padded(EdgeInsets.only(left: 8.0, top: 16.0)); 52 | Padding padded(EdgeInsets padding) { 53 | return Padding( 54 | padding: padding, 55 | child: this, 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/widget/square_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// `SquareBox` is a custom widget representing a square-shaped box. 4 | /// It consists of an `InkWell` widget with a `Container` that contains 5 | /// an image. The box has a border with the specified color and a 6 | /// circular border radius. 7 | /// 8 | /// The `SquareBox` widget is designed to be clickable, and the `onTap` callback 9 | /// is triggered when the box is tapped. 10 | class SquareBox extends StatelessWidget { 11 | /// The file path of the image to be displayed in the box. 12 | final String imagePath; 13 | 14 | /// The color of the border surrounding the box. 15 | final Color color; 16 | 17 | /// The callback function to be executed when the box is tapped. 18 | final VoidCallback onTap; 19 | 20 | 21 | final Color? imageColor; 22 | 23 | /// Creates a new instance of `SquareBox`. 24 | /// 25 | /// The `imagePath`, `color`, and `onTap` parameters are required. 26 | const SquareBox({ 27 | Key? key, 28 | required this.imagePath, 29 | required this.color, 30 | required this.onTap, 31 | this.imageColor 32 | }) : super(key: key); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return InkWell( 37 | onTap: onTap, 38 | child: Container( 39 | padding: const EdgeInsets.all(10), 40 | decoration: BoxDecoration( 41 | border: Border.all(color: color), 42 | borderRadius: BorderRadius.circular(10), 43 | ), 44 | child: Image.asset( 45 | color: imageColor, 46 | imagePath, 47 | height: 32, 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/feature/Firestorage/domain/usecases/index.dart: -------------------------------------------------------------------------------- 1 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/create_comment_problem_usecase.dart'; 2 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/create_comment_suggestion_usecase.dart'; 3 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/create_profile_usecase.dart'; 4 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/create_report_usecase.dart'; 5 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/delete_comment_problem_usecase.dart'; 6 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/delete_comment_suggestion_usecase.dart'; 7 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/delete_profile_usecase.dart'; 8 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_problem_usecase.dart'; 9 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_suggestion_usecase.dart'; 10 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_profile_usecase.dart'; 11 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/update_comment_problem_usecase.dart'; 12 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/update_comment_suggestion_usecase.dart'; 13 | export 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/update_profile_usecase.dart'; 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Search/mixin/search_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/bottomNavBar/bottomNavBar_cubit.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Search/search_page.dart'; 6 | 7 | mixin SearchPageMixin on State { 8 | late ScrollController scrollControllerNested; 9 | late TextEditingController searchController; 10 | 11 | @override 12 | void initState() { 13 | super.initState(); 14 | scrollControllerNested = ScrollController(); 15 | searchController = TextEditingController(); 16 | scrollControllerNested.addListener(() { 17 | if (scrollControllerNested.position.userScrollDirection == 18 | ScrollDirection.reverse) { 19 | if (context 20 | .read() 21 | .state 22 | .bottomNavBarVisibleState 23 | .isVisible) { 24 | context.read().setIsVisible(false); 25 | } 26 | } 27 | if (scrollControllerNested.position.userScrollDirection == 28 | ScrollDirection.forward) { 29 | if (!context 30 | .read() 31 | .state 32 | .bottomNavBarVisibleState 33 | .isVisible) { 34 | context.read().setIsVisible(true); 35 | } 36 | } 37 | }); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | scrollControllerNested.dispose(); 43 | searchController.dispose(); 44 | super.dispose(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_register_bloc/auth_register_event.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_register_bloc.dart'; 2 | 3 | /// Represents the events for user registration/authentication. 4 | sealed class AuthRegisterEvent extends Equatable { 5 | const AuthRegisterEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | /// Event for changing the email during registration. 12 | class AuthRegisterEmailChanged extends AuthRegisterEvent { 13 | const AuthRegisterEmailChanged(this.email); 14 | 15 | final String email; 16 | 17 | @override 18 | List get props => [email]; 19 | } 20 | 21 | /// Event for changing the first password during registration. 22 | class AuthRegisterPasswordFirstChanged extends AuthRegisterEvent { 23 | const AuthRegisterPasswordFirstChanged(this.password); 24 | 25 | final String password; 26 | 27 | @override 28 | List get props => [password]; 29 | } 30 | 31 | /// Event for changing the second password during registration. 32 | class AuthRegisterPasswordSecondChanged extends AuthRegisterEvent { 33 | const AuthRegisterPasswordSecondChanged(this.password); 34 | 35 | final String password; 36 | 37 | @override 38 | List get props => [password]; 39 | } 40 | 41 | /// Event for changing the obscuration status of the password during registration. 42 | class AuthRegisterPasswordObscureChanged extends AuthRegisterEvent { 43 | const AuthRegisterPasswordObscureChanged(this.passwordIsObscure); 44 | 45 | final bool passwordIsObscure; 46 | 47 | @override 48 | List get props => [passwordIsObscure]; 49 | } 50 | 51 | /// Event for submitting the registration form. 52 | class AuthRegisterSubmitted extends AuthRegisterEvent { 53 | const AuthRegisterSubmitted(); 54 | } 55 | -------------------------------------------------------------------------------- /lib/feature/Onboard/presentation/widgets/page_content_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_localization/easy_localization.dart'; 2 | 3 | /// The model representing onboarding content for the Idea Atlas application. 4 | class OnBoardingContentModel { 5 | /// The title of the onboarding content. 6 | final String title; 7 | 8 | /// The description of the onboarding content. 9 | final String description; 10 | 11 | /// The image asset path for the onboarding content. 12 | final String image; 13 | 14 | /// Creates an instance of [OnBoardingContentModel]. 15 | /// 16 | /// The [title], [description], and [image] parameters are required. 17 | OnBoardingContentModel({ 18 | required this.title, 19 | required this.description, 20 | required this.image, 21 | }); 22 | } 23 | 24 | /// A list of predefined onboarding content for the Idea Atlas application. 25 | List onBoardingContentsList = [ 26 | OnBoardingContentModel( 27 | title: "onboard_title_one".tr(), 28 | description: "onboard_desc_one".tr(), 29 | image: "assets/svg/atlas.svg", 30 | ), 31 | OnBoardingContentModel( 32 | title: "onboard_title_two".tr(), 33 | description: "onboard_desc_two".tr(), 34 | image: "assets/svg/problem.svg", 35 | ), 36 | OnBoardingContentModel( 37 | title: "onboard_title_three".tr(), 38 | description: "onboard_desc_three".tr(), 39 | image: "assets/svg/idea.svg", 40 | ), 41 | OnBoardingContentModel( 42 | title: "onboard_title_four".tr(), 43 | description: "onboard_desc_four".tr(), 44 | image: "assets/svg/medias.svg", 45 | ), 46 | OnBoardingContentModel( 47 | title: "onboard_title_five".tr(), 48 | description: "onboard_desc_five".tr(), 49 | image: "assets/svg/start.svg", 50 | ), 51 | ]; 52 | -------------------------------------------------------------------------------- /lib/feature/Onboard/presentation/cubit/onboard_state.dart: -------------------------------------------------------------------------------- 1 | part of 'onboard_cubit.dart'; 2 | 3 | /// Represents the state of the onboarding process. 4 | /// 5 | /// The [OnBoardState] class contains information about the current progress of the onboarding process, 6 | /// including the percentage of completion, the current index, and whether the onboarding is finished or not. 7 | /// 8 | /// This class is immutable and can be copied with updated values using the [copyWith] method. 9 | /// 10 | /// The [percentage] property represents the percentage of completion of the onboarding process. 11 | /// The [index] property represents the current index of the onboarding process. 12 | /// The [finished] property indicates whether the onboarding process is finished or not. 13 | /// 14 | /// The [props] getter returns a list of properties that are used to determine equality between instances of [OnBoardState]. 15 | /// The [toString] method provides a string representation of the [OnBoardState] instance. 16 | class OnBoardState extends Equatable { 17 | final double percentage; 18 | final int index; 19 | final bool finished; 20 | 21 | const OnBoardState({ 22 | this.percentage = 0.25, 23 | this.index = 0, 24 | this.finished = false, 25 | }); 26 | 27 | OnBoardState copyWith({ 28 | double? percentage, 29 | int? index, 30 | bool? finished, 31 | }) { 32 | return OnBoardState( 33 | percentage: percentage ?? this.percentage, 34 | index: index ?? this.index, 35 | finished: finished ?? this.finished, 36 | ); 37 | } 38 | 39 | @override 40 | List get props => [percentage, index, finished]; 41 | 42 | @override 43 | String toString() { 44 | return 'OnBoardState(percentage: $percentage, index: $index, finished: $finished)'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/feature/Splash/presentation/mixin/splash_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/core/constants/svg/svg_constants.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Splash/cubit/splash_finished_cubit.dart'; 5 | 6 | /// A mixin providing animation control for a splash screen in a Flutter application. 7 | mixin SplashScreenAnimationController on State implements TickerProvider { 8 | /// Animation controller responsible for controlling the splash screen animation. 9 | late final AnimationController controller; 10 | 11 | @override 12 | void initState() { 13 | super.initState(); 14 | 15 | // Precache SVG images for improved performance during the splash screen display. 16 | SVGConstants.svgPrecacheImage(); 17 | 18 | // Initialize the animation controller. 19 | controller = AnimationController(vsync: this); 20 | 21 | // Add a listener to handle animation status changes. 22 | controller.addListener(() { 23 | if (controller.status == AnimationStatus.completed) { 24 | // Notify the SplashFinishedControllerCubit when the animation completes. 25 | context.read().splashFinishedAtLeastOnce(); 26 | // Repeat the animation. 27 | controller.repeat(); 28 | } else if (controller.status == AnimationStatus.dismissed) { 29 | // Forward the animation when it is dismissed. 30 | controller.forward(); 31 | } 32 | }); 33 | } 34 | 35 | @override 36 | void dispose() { 37 | // Dispose of the animation controller to avoid memory leaks. 38 | controller.dispose(); 39 | super.dispose(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/widget/sliver_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/init/navigation/app_router.dart'; 6 | 7 | /// SliverAppBar widget for the app. 8 | /// 9 | /// This widget is used to create a sliver app bar with a title and optional tab bar and actions. 10 | class SliverAppBarWidget extends StatelessWidget { 11 | const SliverAppBarWidget( 12 | {super.key, required this.text,this.tabbar, this.action,this.profileID}); 13 | final String text; 14 | final TabBar? tabbar; 15 | final List? action; 16 | final String? profileID; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return SliverAppBar( 21 | leading: profilePageRoute(context), 22 | actions: action != null 23 | ? [...action!, settingPageRoute(context)] 24 | : [settingPageRoute(context)], 25 | title: Text(text).tr(), 26 | centerTitle: true, 27 | floating: true, 28 | snap: true, 29 | pinned: false, 30 | bottom: tabbar); 31 | } 32 | 33 | IconButton profilePageRoute(BuildContext context){ 34 | 35 | return IconButton(onPressed: (){ 36 | AutoRouter.of(context).push(ProfilePageRoute(profileID: profileID ?? FirebaseAuth.instance.currentUser!.uid)); 37 | }, icon: const Icon(Icons.person)); 38 | } 39 | 40 | IconButton settingPageRoute(BuildContext context){ 41 | return IconButton(onPressed: (){ 42 | AutoRouter.of(context).push(const SettingsPageRoute()); 43 | }, icon: const Icon(Icons.settings)); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/widget/category_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_svg/flutter_svg.dart'; 5 | 6 | import 'package:solution_challenge_2023_recommender_app/core/init/navigation/app_router.dart'; 7 | 8 | import 'package:solution_challenge_2023_recommender_app/feature/App/data/models/category_card_model.dart'; 9 | 10 | /// Widget for displaying a category card. 11 | class CategoryCard extends StatelessWidget { 12 | final CategoryCardModel category; 13 | 14 | const CategoryCard({super.key, required this.category}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Card( 19 | elevation: 4.0, 20 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), 21 | child: InkWell( 22 | onTap: () { 23 | AutoRouter.of(context) 24 | .push(CategoryListPageRoute(categoryCardModel: category)); 25 | }, 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | svgAssets(context), 30 | const SizedBox(height: 8.0), 31 | Text( 32 | category.categoriesEnum.value, 33 | style: 34 | const TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), 35 | ).tr(), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | 42 | /// Builds SVG asset widget based on the category's image path. 43 | Widget svgAssets(BuildContext context) { 44 | return SvgPicture.asset( 45 | category.image, 46 | height: MediaQuery.of(context).size.width * 0.3, 47 | width: MediaQuery.of(context).size.width * 0.3, 48 | fit: BoxFit.cover, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/feature/Splash/presentation/pages/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:lottie/lottie.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/constants/extension/context_extension.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/constants/lottie/lottie_constants.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Splash/presentation/mixin/splash_mixin.dart'; 7 | 8 | /// [SplashView] is the splash screen of the application, utilizing animation and a dark/light theme. 9 | @RoutePage() 10 | class SplashView extends StatefulWidget { 11 | const SplashView({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _SplashViewState(); 15 | } 16 | 17 | class _SplashViewState extends State 18 | with TickerProviderStateMixin, SplashScreenAnimationController { 19 | @override 20 | Widget build(BuildContext context) { 21 | // Determine if the current theme is dark or light. 22 | final isDark = context.isDarkMode; 23 | return Scaffold( 24 | backgroundColor: isDark ? Colors.black : Colors.white, 25 | body: _buildBody(controller, isDark), 26 | ); 27 | } 28 | 29 | /// Builds the body of the splash screen. 30 | Widget _buildBody(AnimationController controller, bool isDark) { 31 | return Center( 32 | child: _buildLottieFile(controller), 33 | ); 34 | } 35 | 36 | /// Builds the Lottie animation widget with delayed animation start. 37 | Widget _buildLottieFile(AnimationController controller) { 38 | return Lottie.asset( 39 | LottieConstants.splashScreen, 40 | controller: controller, 41 | onLoaded: (composition) { 42 | controller 43 | ..duration = composition.duration 44 | ..forward(); 45 | 46 | }, 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/main/main_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/bottomNavBar/bottomNavBar_cubit.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_profile_entity/profile_entity_cubit.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/main/widget/bottom_nav_bar.dart'; 7 | import 'package:lazy_load_indexed_stack/lazy_load_indexed_stack.dart'; 8 | 9 | /// Represents the main wrapper view. 10 | /// 11 | /// This view is the main view of the application. It contains the bottom navigation bar and the main content of the application. 12 | @RoutePage() 13 | class MainWrapperView extends StatefulWidget { 14 | const MainWrapperView({super.key}); 15 | 16 | @override 17 | State createState() => _MainWrapperViewState(); 18 | } 19 | 20 | class _MainWrapperViewState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | createProfile(context); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | body: BlocBuilder( 31 | builder: (BuildContext context, BottomNavBarState state) { 32 | return LazyLoadIndexedStack( 33 | index: state.bottomNavBarPages.index, 34 | children: BlocProvider.of(context).pages); 35 | }, 36 | ), 37 | bottomNavigationBar: const MainWrapperNavigationBar(), 38 | ); 39 | } 40 | 41 | /// Creates the profile of the user on Firebase. 42 | void createProfile(BuildContext context) async { 43 | return await context.read().getOrSetProfile(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleLocalizations 6 | 7 | en 8 | nb 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleDisplayName 13 | Solution Challenge 2023 Recommender App 14 | CFBundleExecutable 15 | $(EXECUTABLE_NAME) 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | Fikir Atlası 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(FLUTTER_BUILD_NAME) 26 | CFBundleSignature 27 | ???? 28 | CFBundleVersion 29 | $(FLUTTER_BUILD_NUMBER) 30 | LSRequiresIPhoneOS 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | CADisableMinimumFrameDurationOnPhone 50 | 51 | UIApplicationSupportsIndirectInputEvents 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Category/category_list_page_body.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/core/constants/enums/firestore_constants.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_category/category_cubit.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Category/mixin/category_body_mixin.dart'; 8 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/widget/comments_problem_card.dart'; 9 | 10 | /// Category List Page Body Widget 11 | /// 12 | /// This widget is used to display the list of comments for a specific category 13 | class CategoryListPageBody extends StatefulWidget { 14 | const CategoryListPageBody({super.key,required this.scrollControllerNested,required this.categoryEnum}); 15 | 16 | final ScrollController scrollControllerNested; 17 | final CategoriesEnum categoryEnum; 18 | 19 | @override 20 | State createState() => _CategoryListPageBodyState(); 21 | } 22 | 23 | class _CategoryListPageBodyState extends State with CategoryBodyMixin{ 24 | @override 25 | Widget build(BuildContext context) { 26 | return BlocBuilder(builder: (context, state) { 27 | if (state is CategoryLoading) { 28 | return const Center( 29 | child: CircularProgressIndicator(), 30 | ); 31 | } else if (state is CategoryLoaded) { 32 | return ListView.builder( 33 | itemCount: state.comments.length, 34 | itemBuilder: (context, index) { 35 | return CommentsProblemCard( 36 | commentProblemEntity: state.comments[index]!, 37 | ); 38 | }, 39 | ); 40 | } else { 41 | return const Center( 42 | child: Text("Initial State"), 43 | ); 44 | } 45 | } 46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/widget/frame_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// A widget that displays a frame around its child widget. 4 | /// 5 | /// The frame widget is typically used to provide a visual boundary or highlight 6 | /// for a specific area of the user interface. It consists of a child widget 7 | /// placed inside a stack, with an optional close button positioned at the top 8 | /// right corner of the frame. 9 | /// 10 | /// The frame can be customized by specifying the size of the frame and the 11 | /// child widget. The close button can be triggered by tapping on it, and it 12 | /// displays an icon of a cross with a white color. 13 | class FrameWidget extends StatelessWidget { 14 | const FrameWidget({ 15 | Key? key, 16 | this.onPressed, 17 | this.size = const Size(70, 70), 18 | required this.child, 19 | }) : super(key: key); 20 | 21 | /// A callback function that is triggered when the close button is tapped. 22 | final VoidCallback? onPressed; 23 | 24 | /// The size of the frame widget. 25 | final Size size; 26 | 27 | /// The child widget that is displayed inside the frame. 28 | final Widget child; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Stack( 33 | children: [ 34 | SizedBox( 35 | width: size.width, 36 | height: size.height, 37 | child: child, 38 | ), 39 | deleteIconButton(), 40 | ], 41 | ); 42 | } 43 | 44 | /// A close button that is positioned at the top right corner of the frame. 45 | Widget deleteIconButton() { 46 | return Positioned( 47 | top: 0, 48 | right: 0, 49 | child: InkWell( 50 | onTap: onPressed, 51 | child: Container( 52 | width: 25, 53 | height: 25, 54 | decoration: const BoxDecoration( 55 | shape: BoxShape.circle, 56 | color: Colors.black, 57 | ), 58 | child: const Icon( 59 | Icons.close, 60 | color: Colors.white, 61 | ), 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_home_lastSent/home_last_sent_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_problem_list_last_usecase.dart'; 6 | 7 | part 'home_last_sent_state.dart'; 8 | 9 | class HomeLastSentCubit extends Cubit { 10 | HomeLastSentCubit( 11 | this.getCommentProblemListLastUsecase, 12 | ) : super(HomeLastSentInitial()); 13 | 14 | GetCommentProblemListLastUsecase getCommentProblemListLastUsecase; 15 | 16 | final List comments = []; 17 | QueryDocumentSnapshot? lastVisible; 18 | 19 | Future getCommentProblemListLastRefresh() async { 20 | emit(HomeLastSentLoading()); 21 | comments.clear(); 22 | lastVisible = null; 23 | final failureOrCommentProblemList = 24 | await getCommentProblemListLastUsecase(lastVisible,gettingData: 10); 25 | failureOrCommentProblemList.fold( 26 | (failure) => emit(HomeLastSentLoaded(comments, isLoadingNewData: false, isError: true)), 27 | (commentProblemList) { 28 | comments.addAll(commentProblemList.value1); 29 | emit(HomeLastSentLoaded(comments, isLoadingNewData: false)); 30 | lastVisible = commentProblemList.value2; 31 | }, 32 | ); 33 | } 34 | 35 | Future getCommentProblemListLast() async { 36 | emit(HomeLastSentLoaded(comments, isLoadingNewData: true)); 37 | final failureOrCommentProblemList = 38 | await getCommentProblemListLastUsecase(lastVisible,gettingData: 10); 39 | failureOrCommentProblemList.fold( 40 | (failure) => emit(HomeLastSentLoaded(comments, isLoadingNewData: false, isError: true)), 41 | (commentProblemList) { 42 | comments.addAll(commentProblemList.value1); 43 | emit(HomeLastSentLoaded(comments, isLoadingNewData: false, isError: false)); 44 | lastVisible = commentProblemList.value2; 45 | }, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.google.recommender_app.solution_challenge_2023_recommender_app" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.google.recommender_app.solution_challenge_2023_recommender_app" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion 21 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | 53 | } 54 | 55 | buildTypes { 56 | release { 57 | // TODO: Add your own signing config for the release build. 58 | // Signing with the debug keys for now, so `flutter run --release` works. 59 | signingConfig signingConfigs.debug 60 | } 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies {} 69 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_page_bloc/auth_page_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/usecases/sign_in_with_email_usecase.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/usecases/sign_in_with_google_usecase.dart'; 5 | 6 | part 'auth_page_event.dart'; 7 | part 'auth_page_state.dart'; 8 | 9 | /// BLoC responsible for handling authentication page events and state. 10 | class AuthPageBloc extends Bloc { 11 | final SignInWithEmailAndPasswordUsecase signInWithEmailAndPasswordUsecase; 12 | final SignInWithGoogleUsecase signInWithGoogleUsecase; 13 | 14 | AuthPageBloc({ 15 | required this.signInWithGoogleUsecase, 16 | required this.signInWithEmailAndPasswordUsecase, 17 | }) : super(const AuthPageBlocState()) { 18 | on((event, emit) {}); 19 | on(_onEmailChanged); 20 | on(_onPasswordChanged); 21 | on(_onPasswordObscureChanged); 22 | on(_onSubmitted); 23 | } 24 | 25 | /// Updates the state with the new email. 26 | void _onEmailChanged( 27 | AuthPageEmailChanged event, Emitter emit) { 28 | emit(state.copyWith(email: event.email)); 29 | } 30 | 31 | /// Updates the state with the new password. 32 | void _onPasswordChanged( 33 | AuthPagePasswordChanged event, Emitter emit) { 34 | emit(state.copyWith(password: event.password)); 35 | } 36 | 37 | /// Updates the state with the password obscuration status. 38 | void _onPasswordObscureChanged( 39 | AuthPagePasswordObscureChanged event, Emitter emit) { 40 | emit(state.copyWith(passwordIsObscure: event.passwordIsObscure)); 41 | } 42 | 43 | /// Handles the authentication submission event. 44 | void _onSubmitted(AuthPageSubmitted event, Emitter emit) { 45 | signInWithEmailAndPasswordUsecase.call( 46 | state.email, 47 | state.password, 48 | ); 49 | } 50 | 51 | /// Triggers the sign-in process using Google. 52 | void signInWithGoogle() { 53 | signInWithGoogleUsecase.call(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/feature/Auth/domain/repository/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | 3 | /// `AuthRepository` defines the contract for authentication operations in the application. 4 | /// 5 | /// This abstract class provides a set of methods to interact with the authentication system, 6 | /// including signing in with Google, retrieving user information, managing authentication state, 7 | /// and signing out. 8 | abstract class AuthRepository { 9 | /// Initiates the authentication process using Google Sign-In. 10 | /// 11 | /// Returns a [Future] that completes when the Google Sign-In process is complete. 12 | Future signInWithGoogle(); 13 | 14 | /// Signs in the user with email and password. 15 | /// 16 | /// Takes an [email] and [password] as parameters and returns a [Future] that completes with a [UserCredential]. 17 | Future signInWithEmailAndPassword(String email,String password); 18 | 19 | /// Signs up the user with email and password. 20 | /// 21 | /// Takes an [email] and [password] as parameters and returns a [Future] that completes with a [UserCredential]. 22 | Future signUpWithEmailAndPassword(String email,String password); 23 | 24 | /// Provides a [Stream] to listen for changes in the authenticated user. 25 | /// 26 | /// The stream emits the current authenticated user and updates when the authentication state changes. 27 | Stream get user; 28 | 29 | /// Retrieves the UID (User ID) of the currently authenticated user. 30 | /// 31 | /// Returns a [Future] containing the UID as a [String], or `null` if no user is authenticated. 32 | Future getCurrentUId(); 33 | 34 | /// Signs in with the provided authentication credential. 35 | /// 36 | /// Takes an [AuthCredential] as a parameter and returns a [Future] that completes with a [UserCredential]. 37 | Future signInWithAuthCredential(AuthCredential credential); 38 | 39 | /// Checks if a user is currently signed in. 40 | /// 41 | /// Returns a [Future] that completes with a boolean value indicating the sign-in status. 42 | Future isSignIn(); 43 | 44 | /// Signs out the currently authenticated user. 45 | /// 46 | /// Returns a [Future] that completes when the sign-out process is complete. 47 | Future signOut(); 48 | } 49 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Leaderboard/mixin/leaderboard_suggest_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/utility/custom_scroll.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/bottomNavBar/bottomNavBar_cubit.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Leaderboard/suggest_leaderboard.dart'; 7 | 8 | /// This mixin is used to handle the scroll events of the [SuggestLeaderBoardPageView] page. 9 | mixin LeaderBoardSuggestMixin on State { 10 | late CustomScrollController scrollControllerLeaderboardSuggest; 11 | 12 | @override 13 | void initState() { 14 | super.initState(); 15 | scrollControllerLeaderboardSuggest = CustomScrollController(); 16 | scrollControllerLeaderboardSuggest.addListener( 17 | () { 18 | widget.scrollControllerNested.jumpTo( 19 | widget.scrollControllerNested.position.pixels + 20 | scrollControllerLeaderboardSuggest.distanceOfScroll()); 21 | 22 | if (scrollControllerLeaderboardSuggest.isMaxExtent()) { 23 | //context.read().getCommentSuggestListLast(); 24 | } 25 | 26 | if (scrollControllerLeaderboardSuggest.position.userScrollDirection == 27 | ScrollDirection.reverse) { 28 | if (context 29 | .read() 30 | .state 31 | .bottomNavBarVisibleState 32 | .isVisible) { 33 | context.read().setIsVisible(false); 34 | } 35 | } 36 | if (scrollControllerLeaderboardSuggest.position.userScrollDirection == 37 | ScrollDirection.forward) { 38 | if (!context 39 | .read() 40 | .state 41 | .bottomNavBarVisibleState 42 | .isVisible) { 43 | context.read().setIsVisible(true); 44 | } 45 | } 46 | }, 47 | ); 48 | } 49 | 50 | @override 51 | void dispose() { 52 | scrollControllerLeaderboardSuggest.dispose(); 53 | super.dispose(); 54 | } 55 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Leaderboard/mixin/leaderboard_problem_mixin.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/core/utility/custom_scroll.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/bottomNavBar/bottomNavBar_cubit.dart'; 8 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Leaderboard/problem_leaderboard.dart'; 9 | 10 | /// This mixin is used to handle the scroll events of the [ProblemLeaderBoardPageView] page. 11 | mixin LeaderBoardProblemMixin on State { 12 | late CustomScrollController scrollControllerLeaderboardProblem; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | scrollControllerLeaderboardProblem = CustomScrollController(); 18 | scrollControllerLeaderboardProblem.addListener( 19 | () { 20 | widget.scrollControllerNested.jumpTo( 21 | widget.scrollControllerNested.position.pixels + 22 | scrollControllerLeaderboardProblem.distanceOfScroll()); 23 | 24 | if (scrollControllerLeaderboardProblem.isMaxExtent()) { 25 | //context.read().getCommentProblemListLast(); 26 | } 27 | 28 | if (scrollControllerLeaderboardProblem.position.userScrollDirection == 29 | ScrollDirection.reverse) { 30 | if (context 31 | .read() 32 | .state 33 | .bottomNavBarVisibleState 34 | .isVisible) { 35 | context.read().setIsVisible(false); 36 | } 37 | } 38 | if (scrollControllerLeaderboardProblem.position.userScrollDirection == 39 | ScrollDirection.forward) { 40 | if (!context 41 | .read() 42 | .state 43 | .bottomNavBarVisibleState 44 | .isVisible) { 45 | context.read().setIsVisible(true); 46 | } 47 | } 48 | }, 49 | ); 50 | } 51 | 52 | @override 53 | void dispose() { 54 | scrollControllerLeaderboardProblem.dispose(); 55 | super.dispose(); 56 | } 57 | } -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Problem/comment_problem_suggest.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_comment_problem/comment_problem_cubit.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Problem/mixin/comment_problem_page_mixin.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/widget/comments_suggestion_card.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 7 | 8 | class CommnetProblemSuggest extends StatefulWidget { 9 | const CommnetProblemSuggest({super.key, required this.commentProblemEntity}); 10 | final CommentProblemEntity commentProblemEntity; 11 | 12 | @override 13 | State createState() => _CommnetProblemSuggestState(); 14 | } 15 | 16 | class _CommnetProblemSuggestState extends State 17 | with CommentProblemPageMixin { 18 | @override 19 | Widget build(BuildContext context) { 20 | return BlocBuilder( 21 | builder: (context, state) { 22 | if (state is CommentProblemLoading) { 23 | return const Center(child: CircularProgressIndicator()); 24 | } else if (state is CommentProblemLoaded) { 25 | return Expanded( 26 | child: Column( 27 | children: [ 28 | Expanded( 29 | child: ListView.builder( 30 | controller: scrollControllerCommentProblemPage, 31 | itemCount: state.comments.length, 32 | itemBuilder: (context, index) { 33 | return CommentsSuggestionCard( 34 | commentSolutionEntity: state.comments[index]!, 35 | ); 36 | }, 37 | ), 38 | ), 39 | 40 | if (state.isLoadingNewData) 41 | const Center(child: CircularProgressIndicator()), 42 | //if(state.isError) const Center(child: Icon(Icons.error)), 43 | ], 44 | ), 45 | ); 46 | } else { 47 | return const Center(child: Text('Initial state')); 48 | } 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_home_specialForYou/home_special_for_you_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_tags_usecase.dart'; 6 | 7 | 8 | part 'home_special_for_you_state.dart'; 9 | 10 | 11 | class HomeSpecialForYouCubit extends Cubit { 12 | HomeSpecialForYouCubit( 13 | this.getCommentProblemListAccordingToTagsUsecase, 14 | ) : super(HomeSpecialForYouInitial()); 15 | 16 | 17 | GetCommentProblemListAccordingToTagsUsecase getCommentProblemListAccordingToTagsUsecase; 18 | 19 | final List comments = []; 20 | QueryDocumentSnapshot? lastVisible; 21 | 22 | Future getCommentProblemListLastRefresh() async { 23 | emit(HomeSpecialForYouLoading()); 24 | 25 | comments.clear(); 26 | lastVisible = null; 27 | final failureOrCommentProblemList = 28 | await getCommentProblemListAccordingToTagsUsecase(lastVisible,gettingData: 4); 29 | failureOrCommentProblemList.fold( 30 | (failure) => emit(HomeSpecialForYouLoaded(comments, isLoadingNewData: false, isError: true)), 31 | (commentProblemList) { 32 | comments.addAll(commentProblemList.value1); 33 | emit(HomeSpecialForYouLoaded(comments, isLoadingNewData: false)); 34 | lastVisible = commentProblemList.value2; 35 | }, 36 | ); 37 | } 38 | 39 | Future getCommentProblemListLast() async { 40 | emit(HomeSpecialForYouLoaded(comments, isLoadingNewData: true)); 41 | final failureOrCommentProblemList = 42 | await getCommentProblemListAccordingToTagsUsecase(lastVisible,gettingData: 4); 43 | failureOrCommentProblemList.fold( 44 | (failure) => emit(HomeSpecialForYouLoaded(comments, isLoadingNewData: false, isError: true)), 45 | (commentProblemList) { 46 | comments.addAll(commentProblemList.value1); 47 | emit(HomeSpecialForYouLoaded(comments, isLoadingNewData: false, isError: false)); 48 | lastVisible = commentProblemList.value2; 49 | }, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_profile/profile_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_profileID.dart'; 6 | 7 | part 'profile_state.dart'; 8 | 9 | class ProfileCubit extends Cubit { 10 | ProfileCubit( 11 | this.getCommentProblemListAccordingToProfileIDUsecas, 12 | ) : super(ProfileInitial()); 13 | 14 | GetCommentProblemListAccordingToProfileIDUsecase 15 | getCommentProblemListAccordingToProfileIDUsecas; 16 | 17 | final List comments = []; 18 | QueryDocumentSnapshot? lastVisible; 19 | 20 | Future getCommentProblemListLastRefresh(String profileID) async { 21 | emit(ProfileLoading()); 22 | comments.clear(); 23 | lastVisible = null; 24 | final failureOrCommentProblemList = 25 | await getCommentProblemListAccordingToProfileIDUsecas( 26 | profileID, lastVisible, 27 | gettingData: 4); 28 | failureOrCommentProblemList.fold( 29 | (failure) => 30 | emit(ProfileLoaded(comments, isLoadingNewData: false, isError: true)), 31 | (commentProblemList) { 32 | comments.addAll(commentProblemList.value1); 33 | emit(ProfileLoaded(comments, isLoadingNewData: false)); 34 | lastVisible = commentProblemList.value2; 35 | }, 36 | ); 37 | } 38 | 39 | Future getCommentProblemListLast(String profileID) async { 40 | emit(ProfileLoaded(comments, isLoadingNewData: true)); 41 | final failureOrCommentProblemList = 42 | await getCommentProblemListAccordingToProfileIDUsecas( 43 | profileID, lastVisible, 44 | gettingData: 4); 45 | failureOrCommentProblemList.fold( 46 | (failure) => 47 | emit(ProfileLoaded(comments, isLoadingNewData: false, isError: true)), 48 | (commentProblemList) { 49 | comments.addAll(commentProblemList.value1); 50 | emit(ProfileLoaded(comments, isLoadingNewData: false, isError: false)); 51 | lastVisible = commentProblemList.value2; 52 | }, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_leaderboard_problem/leaderboard_problem_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_likecount_usecase.dart'; 6 | 7 | part 'leaderboard_problem_state.dart'; 8 | 9 | class LeaderboardProblemCubit extends Cubit { 10 | LeaderboardProblemCubit( 11 | this.getCommentProblemListAccordingToLikeCountUseCase, 12 | ) : super(LeaderboardProblemInitial()); 13 | 14 | GetCommentProblemListAccordingToLikeCountUseCase getCommentProblemListAccordingToLikeCountUseCase; 15 | 16 | final List comments = []; 17 | QueryDocumentSnapshot? lastVisible; 18 | 19 | Future getCommentProblemListLastRefresh() async { 20 | emit(LeaderboardProblemLoading()); 21 | comments.clear(); 22 | lastVisible = null; 23 | final failureOrCommentProblemList = 24 | await getCommentProblemListAccordingToLikeCountUseCase(lastVisible,gettingData: 4); 25 | failureOrCommentProblemList.fold( 26 | (failure) => emit(LeaderboardProblemLoaded(comments, isLoadingNewData: false, isError: true)), 27 | (commentProblemList) { 28 | comments.addAll(commentProblemList.value1); 29 | emit(LeaderboardProblemLoaded(comments, isLoadingNewData: false)); 30 | lastVisible = commentProblemList.value2; 31 | }, 32 | ); 33 | } 34 | 35 | Future getCommentProblemListLast() async { 36 | emit(LeaderboardProblemLoaded(comments, isLoadingNewData: true)); 37 | final failureOrCommentProblemList = 38 | await getCommentProblemListAccordingToLikeCountUseCase(lastVisible,gettingData: 4); 39 | failureOrCommentProblemList.fold( 40 | (failure) => emit(LeaderboardProblemLoaded(comments, isLoadingNewData: false, isError: true)), 41 | (commentProblemList) { 42 | comments.addAll(commentProblemList.value1); 43 | emit(LeaderboardProblemLoaded(comments, isLoadingNewData: false, isError: false)); 44 | lastVisible = commentProblemList.value2; 45 | }, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_leaderboard_suggest/leaderboard_suggest_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_suggestions_entities.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_suggest_list_according_to_likecount_usecase.dart'; 6 | 7 | part 'leaderboard_suggest_state.dart'; 8 | 9 | class LeaderboardSuggestCubit extends Cubit { 10 | LeaderboardSuggestCubit( 11 | this.getCommentSuggestListAccordingToLikeCountUseCase, 12 | ) : super(LeaderboardSuggestInitial()); 13 | 14 | GetCommentSuggestionListAccordingToLikeCountUseCase getCommentSuggestListAccordingToLikeCountUseCase; 15 | 16 | final List comments = []; 17 | QueryDocumentSnapshot? lastVisible; 18 | 19 | Future getCommentSuggestListLastRefresh() async { 20 | emit(LeaderboardSuggestLoading()); 21 | comments.clear(); 22 | lastVisible = null; 23 | final failureOrCommentSuggestList = 24 | await getCommentSuggestListAccordingToLikeCountUseCase(lastVisible,gettingData: 4); 25 | failureOrCommentSuggestList.fold( 26 | (failure) => emit(LeaderboardSuggestLoaded(comments, isLoadingNewData: false, isError: true)), 27 | (commentSuggestList) { 28 | comments.addAll(commentSuggestList.value1); 29 | emit(LeaderboardSuggestLoaded(comments, isLoadingNewData: false)); 30 | lastVisible = commentSuggestList.value2; 31 | }, 32 | ); 33 | } 34 | 35 | Future getCommentSuggestListLast() async { 36 | emit(LeaderboardSuggestLoaded(comments, isLoadingNewData: true)); 37 | final failureOrCommentSuggestList = 38 | await getCommentSuggestListAccordingToLikeCountUseCase(lastVisible,gettingData: 4); 39 | failureOrCommentSuggestList.fold( 40 | (failure) => emit(LeaderboardSuggestLoaded(comments, isLoadingNewData: false, isError: true)), 41 | (commentSuggestList) { 42 | comments.addAll(commentSuggestList.value1); 43 | emit(LeaderboardSuggestLoaded(comments, isLoadingNewData: false, isError: false)); 44 | lastVisible = commentSuggestList.value2; 45 | }, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/feature/Auth/data/repository/auth_repository_impl.dart: -------------------------------------------------------------------------------- 1 | /// File: auth_repository_impl.dart 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/data/datasources/auth_remote_data_source.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/repository/auth_repository.dart'; 6 | 7 | /// The `AuthRepositoryImpl` class implements the `AuthRepository` interface, 8 | /// providing methods for handling authentication operations. 9 | class AuthRepositoryImpl implements AuthRepository { 10 | final AuthRemoteDataSource dataSource; 11 | 12 | /// Constructor for `AuthRepositoryImpl`. 13 | AuthRepositoryImpl({required this.dataSource}); 14 | 15 | @override 16 | Future getCurrentUId() async { 17 | try { 18 | return await dataSource.getCurrentUId(); 19 | } catch (e) { 20 | return null; 21 | } 22 | } 23 | 24 | @override 25 | Future isSignIn() async { 26 | try { 27 | return await dataSource.isSignIn(); 28 | } catch (e) { 29 | return Future.value(false); 30 | } 31 | } 32 | 33 | @override 34 | Future signOut() async { 35 | try { 36 | return await dataSource.signOut(); 37 | } catch (e) { 38 | return Future.value(); 39 | } 40 | } 41 | 42 | @override 43 | Future signInWithAuthCredential( 44 | AuthCredential credential) async { 45 | return await dataSource.signInWithAuthCredential(credential); 46 | } 47 | 48 | @override 49 | Future signInWithGoogle() async{ 50 | try { 51 | return await dataSource.signInWithGoogle(); 52 | } catch (e) { 53 | return Future.value(); 54 | } 55 | } 56 | 57 | @override 58 | Stream get user => dataSource.user; 59 | 60 | @override 61 | Future signInWithEmailAndPassword( 62 | String email, String password) async{ 63 | try { 64 | return await dataSource.signInWithEmailAndPassword(email, password); 65 | } catch (e) { 66 | return Future.value(null); 67 | } 68 | } 69 | 70 | @override 71 | Future signUpWithEmailAndPassword( 72 | String email, String password) async { 73 | try { 74 | return await dataSource.signUpWithEmailAndPassword(email, password); 75 | } catch(e){ 76 | return Future.value(null); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/pages/Category/category_list_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/feature/App/data/models/category_card_model.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/bloc/cubit_category/category_cubit.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Category/category_list_page_body.dart'; 7 | import 'package:solution_challenge_2023_recommender_app/feature/App/presentation/pages/Category/mixin/category_mixin.dart'; 8 | import 'package:solution_challenge_2023_recommender_app/injection.dart'; 9 | 10 | /// Page for displaying a list of problems in a category. 11 | /// 12 | /// This page is displayed when a category card is tapped. 13 | @RoutePage() 14 | class CategoryListPageView extends StatefulWidget { 15 | final CategoryCardModel categoryCardModel; 16 | 17 | const CategoryListPageView({super.key, required this.categoryCardModel}); 18 | 19 | @override 20 | State createState() => _CategoryListPageViewState(); 21 | } 22 | 23 | class _CategoryListPageViewState extends State 24 | with CategoryMixin { 25 | @override 26 | Widget build(BuildContext context) { 27 | return BlocProvider( 28 | create: (context) => sl.get(), 29 | child: Builder( 30 | builder: (context) { 31 | return Scaffold( 32 | body: NestedScrollView( 33 | controller: scrollControllerNested, 34 | floatHeaderSlivers: true, 35 | headerSliverBuilder: 36 | (BuildContext context, bool innerBoxIsScrolled) { 37 | return [ 38 | const SliverAppBar( 39 | title: Text('Category'), 40 | centerTitle: true, 41 | floating: true, 42 | snap: true, 43 | pinned: false, 44 | ), 45 | ]; 46 | }, 47 | body: CategoryListPageBody( 48 | scrollControllerNested: scrollControllerNested, 49 | categoryEnum: widget.categoryCardModel.categoriesEnum, 50 | ), 51 | ), 52 | ); 53 | }, 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/feature/Auth/presentation/bloc/auth_register_bloc/auth_register_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:solution_challenge_2023_recommender_app/feature/Auth/domain/usecases/sign_up_with_email_usecase.dart'; 4 | 5 | part 'auth_register_event.dart'; 6 | part 'auth_register_state.dart'; 7 | 8 | /// A BLoC responsible for handling user registration/authentication. 9 | class AuthRegisterBloc extends Bloc { 10 | final SignUpWithEmailAndPasswordUsecase signUpWithEmailAndPasswordUsecase; 11 | 12 | /// Initializes the AuthRegisterBloc with the SignUpWithEmailAndPasswordUsecase. 13 | AuthRegisterBloc({required this.signUpWithEmailAndPasswordUsecase}) 14 | : super(const AuthRegisterState()) { 15 | on(_onEmailChanged); 16 | on(_onPasswordChanged); 17 | on(_onPasswordSecondChanged); 18 | on(_onPasswordObscureChanged); 19 | on(_onSubmitted); 20 | } 21 | 22 | /// Updates the state with the new email. 23 | void _onEmailChanged( 24 | AuthRegisterEmailChanged event, Emitter emit) { 25 | emit(state.copyWith(email: event.email)); 26 | } 27 | 28 | /// Updates the state with the new first password. 29 | void _onPasswordChanged( 30 | AuthRegisterPasswordFirstChanged event, Emitter emit) { 31 | emit(state.copyWith(passwordFirst: event.password)); 32 | } 33 | 34 | /// Updates the state with the new second password. 35 | void _onPasswordSecondChanged( 36 | AuthRegisterPasswordSecondChanged event, 37 | Emitter emit) { 38 | emit(state.copyWith(passwordSecond: event.password)); 39 | } 40 | 41 | /// Updates the state with the password obscuration status. 42 | void _onPasswordObscureChanged( 43 | AuthRegisterPasswordObscureChanged event, 44 | Emitter emit) { 45 | emit(state.copyWith(passwordIsObscure: event.passwordIsObscure)); 46 | } 47 | 48 | /// Handles the registration submission event. 49 | void _onSubmitted(AuthRegisterSubmitted event, Emitter emit) async { 50 | if (state.passwordFirst == state.passwordSecond) { 51 | await signUpWithEmailAndPasswordUsecase.call(state.email, state.passwordFirst); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/feature/App/presentation/bloc/cubit_category/category_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:solution_challenge_2023_recommender_app/core/constants/enums/firestore_constants.dart'; 5 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/entities/comments_problems_entites.dart'; 6 | import 'package:solution_challenge_2023_recommender_app/feature/Firestorage/domain/usecases/get_comment_problem_list_according_to_category_usecase.dart'; 7 | 8 | part 'category_state.dart'; 9 | 10 | class CategoryCubit extends Cubit { 11 | CategoryCubit( 12 | this.getCommentProblemListAccordingToCategoryUsecase 13 | ) : super(CategoryInitial()); 14 | 15 | GetCommentProblemListAccordingToCategoryUsecase getCommentProblemListAccordingToCategoryUsecase; 16 | 17 | 18 | final List comments = []; 19 | QueryDocumentSnapshot? lastVisible; 20 | 21 | Future getCommentProblemListLastRefresh(CategoriesEnum categoriesEnum) async { 22 | emit(CategoryLoading()); 23 | 24 | comments.clear(); 25 | lastVisible = null; 26 | final failureOrCommentProblemList = 27 | await getCommentProblemListAccordingToCategoryUsecase(categoriesEnum,lastVisible,gettingData: 4); 28 | failureOrCommentProblemList.fold( 29 | (failure) => emit(CategoryLoaded(comments, isLoadingNewData: false, isError: true)), 30 | (commentProblemList) { 31 | comments.addAll(commentProblemList.value1); 32 | emit(CategoryLoaded(comments, isLoadingNewData: false)); 33 | lastVisible = commentProblemList.value2; 34 | }, 35 | ); 36 | } 37 | 38 | Future getCommentProblemListLast(CategoriesEnum categoriesEnum) async { 39 | emit(CategoryLoaded(comments, isLoadingNewData: true)); 40 | final failureOrCommentProblemList = 41 | await getCommentProblemListAccordingToCategoryUsecase(categoriesEnum,lastVisible,gettingData: 4); 42 | failureOrCommentProblemList.fold( 43 | (failure) => emit(CategoryLoaded(comments, isLoadingNewData: false, isError: true)), 44 | (commentProblemList) { 45 | comments.addAll(commentProblemList.value1); 46 | emit(CategoryLoaded(comments, isLoadingNewData: false, isError: false)); 47 | lastVisible = commentProblemList.value2; 48 | }, 49 | ); 50 | } 51 | } 52 | --------------------------------------------------------------------------------