├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yaml ├── .gitignore ├── .idea └── runConfigurations │ ├── development.xml │ ├── production.xml │ └── staging.xml ├── .metadata ├── .vscode ├── extensions.json └── launch.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── development │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── 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 │ │ │ ├── values-night │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── annulus │ │ │ │ └── pure │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_launch_image.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── 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 │ │ │ ├── values-night │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── profile │ │ └── AndroidManifest.xml │ │ └── staging │ │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launch_image.png │ │ ├── drawable-mdpi │ │ └── ic_launch_image.png │ │ ├── drawable-v21 │ │ └── launch_background.xml │ │ ├── drawable-xhdpi │ │ └── ic_launch_image.png │ │ ├── drawable-xxhdpi │ │ └── ic_launch_image.png │ │ ├── drawable-xxxhdpi │ │ └── ic_launch_image.png │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── 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 │ │ ├── values-night │ │ ├── strings.xml │ │ └── styles.xml │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ ├── Migra-Bold.otf │ ├── PublicSans-Bold.otf │ ├── PublicSans-Light.otf │ ├── PublicSans-Medium.otf │ ├── PublicSans-Regular.otf │ └── PublicSans-SemiBold.otf └── images │ ├── 2.0 │ ├── notify_dark.png │ └── notify_light.png │ ├── 3.0 │ ├── notify_dark.png │ └── notify_light.png │ ├── apple.png │ ├── calendar.png │ ├── communities.png │ ├── edit.png │ ├── emptyMessageDark.png │ ├── emptyMessageLight.png │ ├── eye.png │ ├── friends.png │ ├── google.png │ ├── guide.png │ ├── help.png │ ├── home.png │ ├── location.png │ ├── logo.png │ ├── logout.png │ ├── message.png │ ├── notifications.png │ ├── notify_dark.png │ ├── notify_light.png │ ├── privacy.png │ ├── settings.png │ ├── slide1_dark.png │ ├── slide1_light.png │ ├── slide2_dark.png │ ├── slide2_light.png │ ├── slide3_dark.png │ ├── slide3_light.png │ ├── sound.png │ ├── user.png │ ├── username.png │ └── video.png ├── coverage_badge.svg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── Runner.xcscheme │ │ ├── development.xcscheme │ │ ├── production.xcscheme │ │ └── staging.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── ic_launch_image.png │ │ ├── ic_launch_image@2x.png │ │ └── ic_launch_image@3x.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── l10n.yaml ├── lib ├── app.dart ├── blocs │ ├── authentication │ │ ├── auth_cubit.dart │ │ ├── auth_state.dart │ │ ├── auth_user_cubit.dart │ │ └── auth_user_state.dart │ ├── bloc.dart │ ├── chats │ │ ├── chats │ │ │ ├── chat_cubit.dart │ │ │ ├── chat_state.dart │ │ │ ├── group │ │ │ │ ├── add_participant_cubit.dart │ │ │ │ ├── add_particpants │ │ │ │ │ ├── new_participant_cubit.dart │ │ │ │ │ └── new_particpant_state.dart │ │ │ │ ├── group_chat_cubit.dart │ │ │ │ ├── group_chat_state.dart │ │ │ │ ├── group_cubit.dart │ │ │ │ └── group_state.dart │ │ │ ├── load_more_chats_cubit.dart │ │ │ ├── unread_chat.dart │ │ │ └── unread_message_cubit.dart │ │ └── messages │ │ │ ├── load_more_messages_cubit.dart │ │ │ ├── message_cubit.dart │ │ │ ├── message_state.dart │ │ │ └── new_messages_cubit.dart │ ├── connections │ │ ├── connection_cubit.dart │ │ ├── connection_state.dart │ │ └── load_more_connection_cubit.dart │ ├── invitation │ │ ├── invitation_cubit.dart │ │ ├── received │ │ │ ├── load_more_received_invitation.dart │ │ │ ├── received_invitation_cubit.dart │ │ │ └── received_invitation_state.dart │ │ └── sent │ │ │ ├── load_more_sent_invitation.dart │ │ │ ├── sent_invitation_cubits.dart │ │ │ └── sent_invitations_state.dart │ ├── onboarding_cubit.dart │ ├── search │ │ ├── connection │ │ │ ├── search_connection_bloc.dart │ │ │ └── search_connection_state.dart │ │ ├── friends │ │ │ ├── search_friends_bloc.dart │ │ │ └── search_friends_state.dart │ │ └── search_username.dart │ └── user_profile │ │ ├── profile │ │ ├── profile_cubit.dart │ │ └── profile_state.dart │ │ ├── user_extra │ │ ├── user_extra_cubit.dart │ │ └── user_extra_state.dart │ │ ├── user_presence │ │ ├── user_presence_cubit.dart │ │ └── user_presence_state.dart │ │ ├── user_profile_cubit.dart │ │ └── user_profile_state.dart ├── l10n │ ├── arb │ │ ├── app_en.arb │ │ └── app_es.arb │ └── l10n.dart ├── main_development.dart ├── main_production.dart ├── main_staging.dart ├── model │ ├── app_enum.dart │ ├── chat │ │ ├── attachment_model.dart │ │ ├── chat_model.dart │ │ └── message_model.dart │ ├── connection_model.dart │ ├── invitation_model.dart │ ├── invitee_model.dart │ ├── inviter_model.dart │ ├── onboarding.dart │ ├── post_model.dart │ ├── pure_user_extra.dart │ ├── pure_user_model.dart │ ├── route │ │ └── message_route.dart │ └── user_presence_model.dart ├── repositories │ ├── algolia_application.dart │ ├── connection.dart │ ├── local_storage.dart │ └── push_notification.dart ├── services │ ├── auth_service.dart │ ├── chat │ │ ├── chat_service.dart │ │ └── message_service.dart │ ├── connection_service.dart │ ├── invitation_service.dart │ ├── remote_storage_service.dart │ ├── search_service.dart │ ├── upload.dart │ └── user_service.dart ├── utils │ ├── adaptive_icons.dart │ ├── app_extension.dart │ ├── app_permission.dart │ ├── app_utils.dart │ ├── chat_utils.dart │ ├── exception.dart │ ├── file_utils.dart │ ├── flavors.dart │ ├── global_utils.dart │ ├── image_utils.dart │ ├── navigate.dart │ ├── palette.dart │ ├── pick_file_dialog.dart │ ├── request_messages.dart │ ├── routing.dart │ ├── true_time.dart │ └── validators.dart └── views │ ├── screens │ ├── app_base.dart │ ├── authentication │ │ ├── reset_password_screen.dart │ │ ├── reset_password_success_screen.dart │ │ ├── signin_screen.dart │ │ ├── signup_screen.dart │ │ ├── social_signin_screen.dart │ │ └── widgets │ │ │ ├── auth_bloc_provider.dart │ │ │ ├── intro_section.dart │ │ │ └── social_signin_button.dart │ ├── chats │ │ ├── chat_screen.dart │ │ ├── group │ │ │ ├── add_participants_screen.dart │ │ │ ├── create_group_screen.dart │ │ │ ├── edit_group_description_screen.dart │ │ │ ├── edit_group_subject_screen.dart │ │ │ ├── friend_profile.dart │ │ │ ├── group_info_screen.dart │ │ │ ├── search_friend_chat.dart │ │ │ └── widget │ │ │ │ ├── group_banner.dart │ │ │ │ ├── new_applicant_profile.dart │ │ │ │ ├── participants.dart │ │ │ │ └── user_connection_list.dart │ │ ├── messages │ │ │ ├── group_chat_message_screen.dart │ │ │ ├── messages_screen.dart │ │ │ └── widgets │ │ │ │ ├── chat_app_bar.dart │ │ │ │ ├── docfile_preview_widget.dart │ │ │ │ ├── empty_widget.dart │ │ │ │ ├── file_picked_widget.dart │ │ │ │ ├── file_widget.dart │ │ │ │ ├── image_preview_screen.dart │ │ │ │ ├── load_more_widgets.dart │ │ │ │ ├── message_inbox_widget.dart │ │ │ │ ├── message_screen_widget.dart │ │ │ │ ├── message_widgets.dart │ │ │ │ ├── messages_body.dart │ │ │ │ ├── new_message_widget.dart │ │ │ │ ├── pure_link_preview.dart │ │ │ │ ├── receipient_message_widget.dart │ │ │ │ ├── separator_widget.dart │ │ │ │ ├── tagged_user_profile.dart │ │ │ │ ├── tagged_user_sheet.dart │ │ │ │ └── user_message_widget.dart │ │ └── widget │ │ │ ├── chat_list_widget.dart │ │ │ ├── group_card.dart │ │ │ ├── load_more_chats_widget.dart │ │ │ ├── one_to_one_card.dart │ │ │ └── unread_message_provider.dart │ ├── connections │ │ ├── connections_page.dart │ │ ├── search │ │ │ ├── search_all_connections.dart │ │ │ ├── search_friends_screen.dart │ │ │ ├── search_screen.dart │ │ │ └── widgets │ │ │ │ ├── connection_status_widget.dart │ │ │ │ ├── search_small.dart │ │ │ │ └── search_user_profile.dart │ │ ├── tabs │ │ │ └── connections │ │ │ │ ├── connections_network.dart │ │ │ │ ├── connectors_list.dart │ │ │ │ ├── connectors_widget.dart │ │ │ │ └── invitations │ │ │ │ ├── invitation_screen.dart │ │ │ │ ├── tabs │ │ │ │ ├── received_screen.dart │ │ │ │ └── sent_screen.dart │ │ │ │ └── widgets │ │ │ │ ├── invitee_profile.dart │ │ │ │ └── inviter_profile.dart │ │ └── widgets │ │ │ └── load_more.dart │ ├── home │ │ ├── home_page.dart │ │ └── posts │ │ │ ├── create_post_screen.dart │ │ │ ├── video_trimmer_screen.dart │ │ │ └── widget │ │ │ ├── post_file_icons.dart │ │ │ ├── post_visibility.dart │ │ │ └── selected_file_preview.dart │ ├── notifications │ │ ├── notifications_screen.dart │ │ └── widgets │ │ │ └── no_notification_widget.dart │ ├── onboarding │ │ ├── onboading_body.dart │ │ ├── onboarding_screen.dart │ │ └── widgets │ │ │ └── action_buttons.dart │ ├── photo_view_screen.dart │ ├── settings │ │ ├── account_screen.dart │ │ ├── profile │ │ │ ├── edit_profile_screen.dart │ │ │ ├── mutual_connection_screen.dart │ │ │ ├── profile_screen.dart │ │ │ └── widgets │ │ │ │ ├── connection_status_button.dart │ │ │ │ └── profile_pic_widget.dart │ │ ├── settings_screen.dart │ │ ├── update_email_screen.dart │ │ ├── update_password_screen.dart │ │ ├── update_username_screen.dart │ │ └── widgets │ │ │ ├── items.dart │ │ │ └── profile.dart │ └── splash_screen.dart │ └── widgets │ ├── avatar.dart │ ├── custom_button.dart │ ├── custom_keep_alive.dart │ ├── custom_multi_bloc_provider.dart │ ├── editable_text_controller.dart │ ├── error_page.dart │ ├── failure_widget.dart │ ├── grouped_list │ ├── group_list_order.dart │ └── grouped_list.dart │ ├── message_widget.dart │ ├── nav_bar_notification.dart │ ├── page_transition.dart │ ├── progress_indicator.dart │ ├── push_notification_navigation.dart │ ├── shimmers │ └── loading_shimmer.dart │ ├── snackbars.dart │ ├── textfield_label_widget.dart │ └── user_profile_provider.dart ├── pubspec.lock ├── pubspec.yaml └── test └── app_test.dart /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Description 10 | 11 | 12 | 13 | ## Type of Change 14 | 15 | 16 | 17 | - [ ] ✨ New feature (non-breaking change which adds functionality) 18 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) 19 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) 20 | - [ ] 🧹 Code refactor 21 | - [ ] ✅ Build configuration change 22 | - [ ] 📝 Documentation 23 | - [ ] 🗑️ Chore 24 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: subosito/flutter-action@v1.5.3 12 | with: 13 | flutter-version: '2.8.0' 14 | 15 | - name: Install Dependencies 16 | run: flutter packages get 17 | 18 | - name: Format 19 | run: flutter format --set-exit-if-changed lib test 20 | 21 | - name: Analyze 22 | run: flutter analyze lib test 23 | -------------------------------------------------------------------------------- /.idea/runConfigurations/development.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/production.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/runConfigurations/staging.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.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: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dart-code.dart-code", 6 | "dart-code.flutter", 7 | "felixangelov.bloc" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch development", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "lib/main_development.dart", 12 | "args": [ 13 | "--flavor", 14 | "development", 15 | "--target", 16 | "lib/main_development.dart" 17 | ] 18 | }, 19 | { 20 | "name": "Launch staging", 21 | "request": "launch", 22 | "type": "dart", 23 | "program": "lib/main_staging.dart", 24 | "args": ["--flavor", "staging", "--target", "lib/main_staging.dart"] 25 | }, 26 | { 27 | "name": "Launch production", 28 | "request": "launch", 29 | "type": "dart", 30 | "program": "lib/main_production.dart", 31 | "args": ["--flavor", "production", "--target", "lib/main_production.dart"] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Very Good Ventures 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.2.4.0.yaml 2 | linter: 3 | rules: 4 | public_member_api_docs: true, 5 | avoid_void_async: false 6 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | /key.properties 8 | GeneratedPluginRegistrant.java 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | 16 | app/src/development/google-services.json 17 | app/src/staging/google-services.json 18 | app/google-services.json 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/development/res/drawable-hdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/drawable-hdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/development/res/drawable-mdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/drawable-mdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/development/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/development/res/drawable-xhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/drawable-xhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/development/res/drawable-xxhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/drawable-xxhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/development/res/drawable-xxxhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/drawable-xxxhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/development/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/development/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/development/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/development/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/development/res/values-night/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure Dev 4 | puredev.page.link 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/development/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/development/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure Dev 4 | puredev.page.link 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/development/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/annulus/pure/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.annulus.pure 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/drawable-hdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/drawable-mdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/drawable-xhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/drawable-xxhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/drawable-xxxhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure 4 | pure.page.link 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure 4 | pure.page.link 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable-hdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/drawable-hdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable-mdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/drawable-mdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable-xhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/drawable-xhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable-xxhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/drawable-xxhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable-xxxhdpi/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/drawable-xxxhdpi/ic_launch_image.png -------------------------------------------------------------------------------- /android/app/src/staging/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/staging/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/staging/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/staging/res/values-night/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure Beta 4 | purestg.page.link 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/staging/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/staging/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pure Beta 4 | purestg.page.link 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/staging/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath 'com.google.gms:google-services:4.3.8' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | } 22 | 23 | rootProject.buildDir = '../build' 24 | subprojects { 25 | project.buildDir = "${rootProject.buildDir}/${project.name}" 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/fonts/Migra-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/fonts/Migra-Bold.otf -------------------------------------------------------------------------------- /assets/fonts/PublicSans-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/fonts/PublicSans-Bold.otf -------------------------------------------------------------------------------- /assets/fonts/PublicSans-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/fonts/PublicSans-Light.otf -------------------------------------------------------------------------------- /assets/fonts/PublicSans-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/fonts/PublicSans-Medium.otf -------------------------------------------------------------------------------- /assets/fonts/PublicSans-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/fonts/PublicSans-Regular.otf -------------------------------------------------------------------------------- /assets/fonts/PublicSans-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/fonts/PublicSans-SemiBold.otf -------------------------------------------------------------------------------- /assets/images/2.0/notify_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/2.0/notify_dark.png -------------------------------------------------------------------------------- /assets/images/2.0/notify_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/2.0/notify_light.png -------------------------------------------------------------------------------- /assets/images/3.0/notify_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/3.0/notify_dark.png -------------------------------------------------------------------------------- /assets/images/3.0/notify_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/3.0/notify_light.png -------------------------------------------------------------------------------- /assets/images/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/apple.png -------------------------------------------------------------------------------- /assets/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/calendar.png -------------------------------------------------------------------------------- /assets/images/communities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/communities.png -------------------------------------------------------------------------------- /assets/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/edit.png -------------------------------------------------------------------------------- /assets/images/emptyMessageDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/emptyMessageDark.png -------------------------------------------------------------------------------- /assets/images/emptyMessageLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/emptyMessageLight.png -------------------------------------------------------------------------------- /assets/images/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/eye.png -------------------------------------------------------------------------------- /assets/images/friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/friends.png -------------------------------------------------------------------------------- /assets/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/google.png -------------------------------------------------------------------------------- /assets/images/guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/guide.png -------------------------------------------------------------------------------- /assets/images/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/help.png -------------------------------------------------------------------------------- /assets/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/home.png -------------------------------------------------------------------------------- /assets/images/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/location.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/logout.png -------------------------------------------------------------------------------- /assets/images/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/message.png -------------------------------------------------------------------------------- /assets/images/notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/notifications.png -------------------------------------------------------------------------------- /assets/images/notify_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/notify_dark.png -------------------------------------------------------------------------------- /assets/images/notify_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/notify_light.png -------------------------------------------------------------------------------- /assets/images/privacy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/privacy.png -------------------------------------------------------------------------------- /assets/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/settings.png -------------------------------------------------------------------------------- /assets/images/slide1_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/slide1_dark.png -------------------------------------------------------------------------------- /assets/images/slide1_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/slide1_light.png -------------------------------------------------------------------------------- /assets/images/slide2_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/slide2_dark.png -------------------------------------------------------------------------------- /assets/images/slide2_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/slide2_light.png -------------------------------------------------------------------------------- /assets/images/slide3_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/slide3_dark.png -------------------------------------------------------------------------------- /assets/images/slide3_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/slide3_light.png -------------------------------------------------------------------------------- /assets/images/sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/sound.png -------------------------------------------------------------------------------- /assets/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/user.png -------------------------------------------------------------------------------- /assets/images/username.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/username.png -------------------------------------------------------------------------------- /assets/images/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/assets/images/video.png -------------------------------------------------------------------------------- /coverage_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | coverage 16 | coverage 17 | 100% 18 | 100% 19 | 20 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | Runner/Firebase/development/GoogleService-Info.plist 28 | Runner/Firebase/staging/GoogleService-Info.plist 29 | Runner/Firebase/production/GoogleService-Info.plist 30 | Runner/GoogleService-Info.plist 31 | 32 | 33 | # Exceptions to above rules. 34 | !default.mode1v3 35 | !default.mode2v3 36 | !default.pbxuser 37 | !default.perspectivev3 38 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '8.9.0' 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | 43 | target.build_configurations.each do |config| 44 | # # for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h 45 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 46 | '$(inherited)', 47 | 48 | ## dart: PermissionGroup.camera 49 | 'PERMISSION_CAMERA=1', 50 | 51 | ## dart: PermissionGroup.photos 52 | 'PERMISSION_PHOTOS=1', 53 | 54 | ## dart: PermissionGroup.microphone 55 | 'PERMISSION_MICROPHONE=1', 56 | 57 | ## dart: PermissionGroup.notification 58 | 'PERMISSION_NOTIFICATIONS=1', 59 | ] 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import Firebase 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | FirebaseApp.configure() 12 | GeneratedPluginRegistrant.register(with: self) 13 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_launch_image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_launch_image@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_launch_image@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/ic_launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/LaunchImage.imageset/ic_launch_image.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/ic_launch_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/LaunchImage.imageset/ic_launch_image@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/ic_launch_image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aanu1995/Pure/b6f39f25fe6efd75c738589b9971f0fcbda91b47/ios/Runner/Assets.xcassets/LaunchImage.imageset/ic_launch_image@3x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleLocalizations 6 | 7 | en 8 | es 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleDisplayName 13 | $(FLAVOR_APP_NAME) 14 | CFBundleExecutable 15 | $(EXECUTABLE_NAME) 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | Pure 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | $(FLUTTER_BUILD_NAME) 26 | CFBundleSignature 27 | ???? 28 | CFBundleURLTypes 29 | 30 | 31 | CFBundleTypeRole 32 | Editor 33 | CFBundleURLName 34 | Google_Sign_In 35 | CFBundleURLSchemes 36 | 37 | $(GOOGLE_INFO_URL) 38 | 39 | 40 | 41 | CFBundleTypeRole 42 | Editor 43 | CFBundleURLName 44 | Bundle ID 45 | CFBundleURLSchemes 46 | 47 | $(PRODUCT_BUNDLE_IDENTIFIER) 48 | 49 | 50 | 51 | CFBundleVersion 52 | $(FLUTTER_BUILD_NUMBER) 53 | LSRequiresIPhoneOS 54 | 55 | NSCameraUsageDescription 56 | This app requires camera access to enable you use camera to take photo 57 | NSPhotoLibraryUsageDescription 58 | This app requires library access to enable you pick photo from library 59 | NSMicrophoneUsageDescription 60 | This app requires microphone access to enable you use record video 61 | UILaunchStoryboardName 62 | LaunchScreen 63 | UIMainStoryboardFile 64 | Main 65 | UISupportedInterfaceOrientations 66 | 67 | UIInterfaceOrientationPortrait 68 | 69 | UISupportedInterfaceOrientations~ipad 70 | 71 | UIInterfaceOrientationPortrait 72 | UIInterfaceOrientationPortraitUpsideDown 73 | 74 | UIViewControllerBasedStatusBarAppearance 75 | 76 | NSAppTransportSecurity 77 | 78 | NSAllowsArbitraryLoads 79 | 80 | 81 | LSApplicationQueriesSchemes 82 | 83 | https 84 | http 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n/arb 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | nullable-getter: false 5 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | 5 | import 'utils/flavors.dart'; 6 | import 'utils/palette.dart'; 7 | import 'utils/routing.dart'; 8 | import 'views/widgets/custom_multi_bloc_provider.dart'; 9 | 10 | class App extends StatelessWidget { 11 | const App({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CustomMultiBlocProvider( 16 | child: ScreenUtilInit( 17 | // the resolution for the design in Figma 18 | designSize: const Size(375, 812), 19 | builder: () { 20 | return GestureDetector( 21 | onTap: () => removeKeyboardFocus(context), 22 | child: MaterialApp.router( 23 | routeInformationParser: router.routeInformationParser, 24 | routerDelegate: router.routerDelegate, 25 | title: F.title, 26 | theme: Palette.lightTheme.copyWith( 27 | primaryColorBrightness: Brightness.light, 28 | appBarTheme: Palette.lightTheme.appBarTheme.copyWith( 29 | titleTextStyle: Palette.appBarStyle(), 30 | ), 31 | ), 32 | darkTheme: Palette.darkTheme.copyWith( 33 | primaryColorBrightness: Brightness.dark, 34 | appBarTheme: Palette.darkTheme.appBarTheme.copyWith( 35 | titleTextStyle: Palette.appBarStyle(isLightMode: false), 36 | ), 37 | ), 38 | debugShowCheckedModeBanner: false, 39 | builder: EasyLoading.init(), 40 | ), 41 | ); 42 | }, 43 | ), 44 | ); 45 | } 46 | 47 | // This method hides keyboard when it is tapped outside the focus area 48 | // This is implemented to get expected behaviour in IOS 49 | 50 | void removeKeyboardFocus(BuildContext context) { 51 | FocusManager.instance.primaryFocus?.unfocus(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/blocs/authentication/auth_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../model/pure_user_model.dart'; 7 | import '../../repositories/local_storage.dart'; 8 | import '../../services/user_service.dart'; 9 | import 'auth_state.dart'; 10 | 11 | class AuthCubit extends Cubit { 12 | AuthCubit( 13 | this.firebaseAuth, 14 | this.localStorage, 15 | this.userService, 16 | ) : super(Authenticating()); 17 | 18 | final LocalStorage localStorage; 19 | final FirebaseAuth firebaseAuth; 20 | final UserService userService; 21 | 22 | Future authenticateUser() async { 23 | try { 24 | if (firebaseAuth.currentUser != null) { 25 | // if user data exists in the local storage is not null, 26 | // then the user is logged in 27 | final data = await localStorage.getUserData(); 28 | if (data != null) { 29 | final user = PureUser.fromMap(data); 30 | emit(Authenticated(user)); 31 | return _syncUserLocalDataWithRemoteData(user.id); 32 | } 33 | } 34 | emit(UnAuthenticated()); 35 | } catch (_) { 36 | emit(UnAuthenticated()); 37 | } 38 | } 39 | 40 | StreamSubscription? _subscription; 41 | 42 | // This sync the user data in remote database with the local database in 43 | // order to have same data 44 | Future _syncUserLocalDataWithRemoteData(final String userId) async { 45 | try { 46 | if (_subscription != null) { 47 | _subscription?.cancel(); 48 | } 49 | _subscription = userService.getCurrentUserData(userId).listen((user) { 50 | emit(Authenticated(user)); 51 | }); 52 | } catch (_) {} 53 | } 54 | 55 | Future setUserOnline(final String userId) async { 56 | await userService.setUserPresence(userId); 57 | } 58 | 59 | Future signOut(final String userId) async { 60 | try { 61 | await userService.setUserOfflineOnSignOut(userId); 62 | dispose(); 63 | await firebaseAuth.signOut(); 64 | await localStorage.clear(); 65 | emit(UnAuthenticated()); 66 | } catch (_) {} 67 | } 68 | 69 | void update(PureUser user) { 70 | emit(Authenticated(user)); 71 | } 72 | 73 | void dispose() { 74 | _subscription?.cancel(); 75 | _subscription = null; 76 | } 77 | 78 | @override 79 | Future close() { 80 | dispose(); 81 | return super.close(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/blocs/authentication/auth_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../model/pure_user_model.dart'; 4 | 5 | abstract class AuthState extends Equatable { 6 | const AuthState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class Authenticating extends AuthState {} 13 | 14 | class UnAuthenticated extends AuthState {} 15 | 16 | class Authenticated extends AuthState { 17 | const Authenticated(this.user); 18 | final PureUser user; 19 | 20 | @override 21 | List get props => [user]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/blocs/authentication/auth_user_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../model/pure_user_model.dart'; 4 | 5 | abstract class AuthUserState extends Equatable { 6 | const AuthUserState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class AuthInitial extends AuthUserState {} 13 | 14 | class AuthInProgress extends AuthUserState {} 15 | 16 | class LoginSuccess extends AuthUserState { 17 | const LoginSuccess({required this.pureUser}); 18 | 19 | final PureUser pureUser; 20 | 21 | @override 22 | List get props => [pureUser]; 23 | } 24 | 25 | class SignUpSuccess extends AuthUserState { 26 | const SignUpSuccess({required this.message}); 27 | final String message; 28 | 29 | @override 30 | List get props => [message]; 31 | } 32 | 33 | class ResetPasswordSuccess extends AuthUserState { 34 | const ResetPasswordSuccess(); 35 | } 36 | 37 | class AuthUserFailure extends AuthUserState { 38 | const AuthUserFailure({required this.message}); 39 | final String message; 40 | } 41 | 42 | // Update Password State 43 | 44 | class UpdatePasswordSuccess extends AuthUserState { 45 | const UpdatePasswordSuccess(); 46 | } 47 | 48 | class UpdatePasswordFailed extends AuthUserState { 49 | const UpdatePasswordFailed({required this.message}); 50 | final String message; 51 | } 52 | 53 | class UpdateEmailSuccess extends AuthUserState { 54 | const UpdateEmailSuccess(); 55 | } 56 | 57 | class UpdateEmailFailed extends AuthUserState { 58 | const UpdateEmailFailed({required this.message}); 59 | final String message; 60 | } 61 | -------------------------------------------------------------------------------- /lib/blocs/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'authentication/auth_cubit.dart'; 2 | export 'authentication/auth_state.dart'; 3 | export 'authentication/auth_user_cubit.dart'; 4 | export 'authentication/auth_user_state.dart'; 5 | export 'chats/chats/chat_cubit.dart'; 6 | export 'chats/chats/chat_state.dart'; 7 | export 'chats/chats/group/add_participant_cubit.dart'; 8 | export 'chats/chats/group/add_particpants/new_participant_cubit.dart'; 9 | export 'chats/chats/group/add_particpants/new_particpant_state.dart'; 10 | export 'chats/chats/group/group_chat_cubit.dart'; 11 | export 'chats/chats/group/group_chat_state.dart'; 12 | export 'chats/chats/group/group_cubit.dart'; 13 | export 'chats/chats/group/group_state.dart'; 14 | export 'chats/chats/unread_chat.dart'; 15 | export 'chats/chats/load_more_chats_cubit.dart'; 16 | export 'chats/chats/unread_message_cubit.dart'; 17 | export 'chats/messages/load_more_messages_cubit.dart'; 18 | export 'chats/messages/message_cubit.dart'; 19 | export 'chats/messages/message_state.dart'; 20 | export 'chats/messages/new_messages_cubit.dart'; 21 | export 'connections/connection_cubit.dart'; 22 | export 'connections/connection_state.dart'; 23 | export 'connections/load_more_connection_cubit.dart'; 24 | export 'invitation/invitation_cubit.dart'; 25 | export 'invitation/received/load_more_received_invitation.dart'; 26 | export 'invitation/received/received_invitation_cubit.dart'; 27 | export 'invitation/received/received_invitation_state.dart'; 28 | export 'invitation/sent/load_more_sent_invitation.dart'; 29 | export 'invitation/sent/sent_invitation_cubits.dart'; 30 | export 'invitation/sent/sent_invitations_state.dart'; 31 | export 'onboarding_cubit.dart'; 32 | export 'search/connection/search_connection_bloc.dart'; 33 | export 'search/connection/search_connection_state.dart'; 34 | export 'search/friends/search_friends_bloc.dart'; 35 | export 'search/friends/search_friends_state.dart'; 36 | export 'user_profile/profile/profile_cubit.dart'; 37 | export 'user_profile/profile/profile_state.dart'; 38 | export 'user_profile/user_extra/user_extra_cubit.dart'; 39 | export 'user_profile/user_extra/user_extra_state.dart'; 40 | export 'user_profile/user_presence/user_presence_cubit.dart'; 41 | export 'user_profile/user_presence/user_presence_state.dart'; 42 | export 'user_profile/user_profile_cubit.dart'; 43 | export 'user_profile/user_profile_state.dart'; 44 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/chat_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/chat/chat_model.dart'; 4 | 5 | class ChatState extends Equatable { 6 | const ChatState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class ChatInitial extends ChatState {} 13 | 14 | class LoadingChats extends ChatState {} 15 | 16 | class ChatsLoaded extends ChatState { 17 | final ChatsModel chatsModel; 18 | final bool hasMore; 19 | 20 | const ChatsLoaded({ 21 | required this.chatsModel, 22 | this.hasMore = true, 23 | }); 24 | 25 | @override 26 | List get props => [chatsModel]; 27 | } 28 | 29 | class ChatsFailed extends ChatState { 30 | final String message; 31 | 32 | const ChatsFailed(this.message); 33 | } 34 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/group/add_participant_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | import '../../../../model/pure_user_model.dart'; 4 | import '../../../../services/chat/chat_service.dart'; 5 | import '../../../bloc.dart'; 6 | 7 | class AddParticipantCubit extends Cubit { 8 | final ChatService chatService; 9 | AddParticipantCubit(this.chatService) : super(GroupMembers(members: [])); 10 | 11 | void addMember(PureUser user) { 12 | final currentState = state; 13 | 14 | if (currentState is GroupMembers) { 15 | final members = currentState.members.toList(); 16 | members.add(user); 17 | emit(GroupMembers(members: members)); 18 | } 19 | } 20 | 21 | void removeMember(PureUser user) { 22 | final currentState = state; 23 | 24 | if (currentState is GroupMembers) { 25 | final members = currentState.members.toList(); 26 | members.removeWhere((member) => member.id == user.id); 27 | emit(GroupMembers(members: members)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/group/add_particpants/new_participant_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:pure/model/chat/message_model.dart'; 3 | 4 | import '../../../../../model/pure_user_model.dart'; 5 | import '../../../../../services/chat/chat_service.dart'; 6 | import '../../../../../utils/exception.dart'; 7 | import '../../../../../utils/request_messages.dart'; 8 | import 'new_particpant_state.dart'; 9 | 10 | class ParticipantCubit extends Cubit { 11 | final ChatService chatService; 12 | 13 | ParticipantCubit(this.chatService) : super(ParticipantInitial()); 14 | 15 | Future addGroupMembers( 16 | String chatId, List newMembers, MessageModel message) async { 17 | emit(AddingParticipant()); 18 | 19 | try { 20 | await chatService.addNewParticipants( 21 | chatId, 22 | newMembers.map((e) => e.id).toList(), 23 | message, 24 | ); 25 | emit(NewParticipant(newMembers: newMembers)); 26 | } on NetworkException catch (e) { 27 | emit(NewParticipantFailed(e.message!)); 28 | } on ServerException catch (e) { 29 | emit(NewParticipantFailed(e.message!)); 30 | } catch (_) { 31 | emit(NewParticipantFailed(ErrorMessages.generalMessage2)); 32 | } 33 | } 34 | 35 | Future removeMember( 36 | String chatId, int index, MessageModel message, PureUser member) async { 37 | emit(RemovingParticipant(member)); 38 | 39 | try { 40 | await chatService.removeParticipant(chatId, message, member.id); 41 | emit(ParticipantRemoved(member)); 42 | } on NetworkException catch (_) { 43 | emit(FailedToRemoveParticipant(index, member)); 44 | } on ServerException catch (_) { 45 | emit(FailedToRemoveParticipant(index, member)); 46 | } catch (_) { 47 | emit(FailedToRemoveParticipant(index, member)); 48 | } 49 | } 50 | 51 | Future exitGroup( 52 | String chatId, MessageModel message, String userId) async { 53 | emit(ExitingGroup()); 54 | 55 | try { 56 | await chatService.removeParticipant(chatId, message, userId); 57 | emit(GroupExited(chatId)); 58 | } on NetworkException catch (error) { 59 | emit(FailedToExitGroup(error.message!)); 60 | } on ServerException catch (error) { 61 | emit(FailedToExitGroup(error.message!)); 62 | } catch (_) { 63 | emit(FailedToExitGroup(ErrorMessages.generalMessage2)); 64 | } 65 | } 66 | 67 | Future addAdmin(String chatId, String userId) async { 68 | emit(AddingAdmin(userId)); 69 | await chatService.addAdmin(chatId, userId); 70 | emit(OperationCompleted()); 71 | } 72 | 73 | Future removeAdmin(String chatId, String userId) async { 74 | emit(RemovingAdmin(userId)); 75 | await chatService.removeAdmin(chatId, userId); 76 | emit(OperationCompleted()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/group/add_particpants/new_particpant_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../../../model/pure_user_model.dart'; 4 | 5 | class ParticipantState extends Equatable { 6 | const ParticipantState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class ParticipantInitial extends ParticipantState {} 13 | 14 | class AddingParticipant extends ParticipantState {} 15 | 16 | class NewParticipant extends ParticipantState { 17 | final List newMembers; 18 | 19 | const NewParticipant({required this.newMembers}); 20 | 21 | @override 22 | List get props => [newMembers]; 23 | } 24 | 25 | class NewParticipantFailed extends ParticipantState { 26 | final String message; 27 | 28 | const NewParticipantFailed(this.message); 29 | } 30 | 31 | class RemovingParticipant extends ParticipantState { 32 | final PureUser participant; 33 | 34 | const RemovingParticipant(this.participant); 35 | } 36 | 37 | class AddingAdmin extends ParticipantState { 38 | final String memberId; 39 | 40 | const AddingAdmin(this.memberId); 41 | } 42 | 43 | class RemovingAdmin extends ParticipantState { 44 | final String memberId; 45 | 46 | const RemovingAdmin(this.memberId); 47 | } 48 | 49 | class OperationCompleted extends ParticipantState {} 50 | 51 | class ParticipantRemoved extends ParticipantState { 52 | final PureUser participant; 53 | 54 | const ParticipantRemoved(this.participant); 55 | } 56 | 57 | class FailedToRemoveParticipant extends ParticipantState { 58 | final PureUser participant; 59 | final int index; 60 | 61 | const FailedToRemoveParticipant(this.index, this.participant); 62 | } 63 | 64 | class ExitingGroup extends ParticipantState {} 65 | 66 | class GroupExited extends ParticipantState { 67 | final String chatId; 68 | 69 | const GroupExited(this.chatId); 70 | } 71 | 72 | class FailedToExitGroup extends ParticipantState { 73 | final String message; 74 | 75 | const FailedToExitGroup(this.message); 76 | } 77 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/group/group_chat_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../../model/chat/chat_model.dart'; 4 | 5 | class GroupChatState extends Equatable { 6 | const GroupChatState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class GroupChatInitial extends GroupChatState {} 13 | 14 | class CreatingGroupChat extends GroupChatState {} 15 | 16 | class UpdatingGroupChat extends GroupChatState {} 17 | 18 | class UploadingGroupImage extends GroupChatState {} 19 | 20 | class GroupChatCreated extends GroupChatState { 21 | final ChatModel chatModel; 22 | 23 | const GroupChatCreated({required this.chatModel}); 24 | 25 | @override 26 | List get props => [chatModel]; 27 | } 28 | 29 | class GroupChatUpdated extends GroupChatState { 30 | final ChatModel chatModel; 31 | 32 | const GroupChatUpdated({required this.chatModel}); 33 | 34 | @override 35 | List get props => [chatModel]; 36 | } 37 | 38 | class GroupChatsFailed extends GroupChatState { 39 | final String message; 40 | 41 | const GroupChatsFailed(this.message); 42 | } 43 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/group/group_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../../model/pure_user_model.dart'; 6 | import '../../../../services/chat/chat_service.dart'; 7 | import 'group_state.dart'; 8 | 9 | class GroupCubit extends Cubit { 10 | final ChatService chatService; 11 | GroupCubit(this.chatService) : super(GroupMembers(members: [])); 12 | 13 | void addMember(PureUser user) { 14 | final currentState = state; 15 | 16 | if (currentState is GroupMembers) { 17 | final members = currentState.members.toList(); 18 | members.add(user); 19 | emit(GroupMembers(members: members)); 20 | } 21 | } 22 | 23 | void addAllMembers(List participants) { 24 | emit(GroupMembers(members: participants)); 25 | } 26 | 27 | void removeMember(PureUser user) { 28 | final currentState = state; 29 | 30 | if (currentState is GroupMembers) { 31 | final members = currentState.members.toList(); 32 | members.removeWhere((member) => member.id == user.id); 33 | emit(GroupMembers(members: members)); 34 | } 35 | } 36 | 37 | StreamSubscription?>? _subscription; 38 | 39 | void getGroupMembersProfile(List userIds) { 40 | _subscription?.cancel(); 41 | _subscription = 42 | chatService.getGroupMembersProfile(userIds).listen((members) { 43 | if (members != null) { 44 | emit(GroupMembers(members: members.toList())); 45 | // cancel subscription once data is available 46 | _subscription?.cancel(); 47 | } 48 | }); 49 | } 50 | 51 | @override 52 | Future close() { 53 | _subscription?.cancel(); 54 | return super.close(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/group/group_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../../model/pure_user_model.dart'; 4 | 5 | class GroupState extends Equatable { 6 | const GroupState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class GroupMembers extends GroupState { 13 | final List members; 14 | 15 | const GroupMembers({required this.members}); 16 | 17 | @override 18 | List get props => [members]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/load_more_chats_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:pure/utils/exception.dart'; 4 | import 'package:pure/utils/request_messages.dart'; 5 | 6 | import '../../../model/chat/chat_model.dart'; 7 | import '../../../services/chat/chat_service.dart'; 8 | import '../../../utils/global_utils.dart'; 9 | import '../../bloc.dart'; 10 | 11 | class LoadMoreChatsCubit extends Cubit { 12 | final ChatService chatService; 13 | 14 | LoadMoreChatsCubit(this.chatService) : super(ChatInitial()); 15 | 16 | Future loadMoreChats(String userId, DocumentSnapshot lastDoc) async { 17 | emit(LoadingChats()); 18 | 19 | try { 20 | final result = await chatService.loadMoreChats(userId, lastDoc); 21 | 22 | emit(ChatsLoaded(chatsModel: result, hasMore: _hasMore(result.chats))); 23 | } on NetworkException catch (e) { 24 | emit(ChatsFailed(e.message!)); 25 | } on ServerException catch (e) { 26 | emit(ChatsFailed(e.message!)); 27 | } catch (_) { 28 | emit(ChatsFailed(ErrorMessages.generalMessage2)); 29 | } 30 | } 31 | 32 | bool _hasMore(final List chats) { 33 | if (chats.isEmpty) return false; 34 | 35 | return chats.length % GlobalUtils.chatsLimit == 0; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/unread_chat.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../services/chat/chat_service.dart'; 6 | 7 | class UnReadChatCubit extends Cubit { 8 | final ChatService chatService; 9 | UnReadChatCubit(this.chatService) : super(0); 10 | 11 | StreamSubscription? _subscription; 12 | 13 | Future getUnreadMessageCounts(String userId) async { 14 | _subscription?.cancel(); 15 | 16 | _subscription = 17 | chatService.getUnReadChatCount(userId).listen((unreadChatCount) { 18 | emit(unreadChatCount); 19 | }); 20 | } 21 | 22 | @override 23 | Future close() { 24 | _subscription?.cancel(); 25 | return super.close(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/blocs/chats/chats/unread_message_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../services/chat/chat_service.dart'; 6 | 7 | class UnreadMessageCubit extends Cubit { 8 | final ChatService chatService; 9 | UnreadMessageCubit(this.chatService) : super(0); 10 | 11 | StreamSubscription? _subscription; 12 | 13 | Future getUnreadMessageCounts(String chatId, String userId) async { 14 | _subscription = chatService 15 | .getUnReadMessageCount(chatId, userId) 16 | .listen((unreadMessagesCount) { 17 | emit(unreadMessagesCount); 18 | }); 19 | } 20 | 21 | @override 22 | Future close() { 23 | _subscription?.cancel(); 24 | return super.close(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/blocs/chats/messages/load_more_messages_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../../model/chat/message_model.dart'; 7 | import '../../../services/chat/message_service.dart'; 8 | import '../../../utils/exception.dart'; 9 | import '../../../utils/global_utils.dart'; 10 | import '../../../utils/request_messages.dart'; 11 | import 'message_state.dart'; 12 | 13 | class LoadMoreMessageCubit extends Cubit { 14 | final MessageService messageService; 15 | 16 | LoadMoreMessageCubit(this.messageService) : super(MessageInitial()); 17 | 18 | Future loadMoreMessages(String chatId, DocumentSnapshot lastDoc) async { 19 | emit(LoadingMessages()); 20 | 21 | try { 22 | final result = await messageService.loadMoreMessages(chatId, lastDoc); 23 | final msgModel = MessagesLoaded( 24 | messagesModel: result, 25 | hasMore: _hasMore(result.messages), 26 | ); 27 | 28 | emit(msgModel); 29 | } on NetworkException catch (e) { 30 | emit(MessagesFailed(e.message!)); 31 | } on ServerException catch (e) { 32 | emit(MessagesFailed(e.message!)); 33 | } catch (_) { 34 | emit(MessagesFailed(ErrorMessages.generalMessage2)); 35 | } 36 | } 37 | 38 | bool _hasMore(final List messages) { 39 | if (messages.isEmpty) return false; 40 | 41 | return messages.length % GlobalUtils.messagesLimit == 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/blocs/chats/messages/message_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/chat/message_model.dart'; 4 | 5 | class MessageState extends Equatable { 6 | const MessageState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class MessageInitial extends MessageState {} 13 | 14 | class LoadingMessages extends MessageState {} 15 | 16 | class MessagesLoaded extends MessageState { 17 | final MessagesModel messagesModel; 18 | final bool hasMore; 19 | 20 | const MessagesLoaded({required this.messagesModel, this.hasMore = true}); 21 | 22 | @override 23 | List get props => [messagesModel]; 24 | } 25 | 26 | class MessagesFailed extends MessageState { 27 | final String message; 28 | 29 | const MessagesFailed(this.message); 30 | } 31 | -------------------------------------------------------------------------------- /lib/blocs/connections/connection_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../model/connection_model.dart'; 4 | import '../../model/invitation_model.dart'; 5 | 6 | class ConnectorState extends Equatable { 7 | const ConnectorState(); 8 | 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class ConnectionInitial extends ConnectorState {} 14 | 15 | class LoadingConnections extends ConnectorState {} 16 | 17 | class ConnectionsLoaded extends ConnectorState { 18 | final ConnectionModel connectionModel; 19 | final bool hasMore; 20 | 21 | const ConnectionsLoaded({required this.connectionModel, this.hasMore = true}); 22 | 23 | @override 24 | List get props => [connectionModel]; 25 | } 26 | 27 | class ConnectionFailed extends ConnectorState { 28 | final String message; 29 | 30 | const ConnectionFailed(this.message); 31 | } 32 | 33 | // For Refresh 34 | class RefreshingConnectors extends ConnectorState {} 35 | 36 | class IsRefreshingConnectors extends ConnectorState {} 37 | 38 | class ConnectorsRefreshFailed extends ConnectorState {} 39 | 40 | // Remote Connection State 41 | 42 | class RemovingConnector extends ConnectorState { 43 | final int index; 44 | 45 | const RemovingConnector(this.index); 46 | } 47 | 48 | class ConnectorRemoved extends ConnectorState { 49 | final String connectorId; 50 | 51 | ConnectorRemoved(this.connectorId); 52 | } 53 | 54 | class ConnectorRemovalFailed extends ConnectorState { 55 | final int index; 56 | final Connector connector; 57 | 58 | const ConnectorRemovalFailed({required this.index, required this.connector}); 59 | } 60 | -------------------------------------------------------------------------------- /lib/blocs/invitation/invitation_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | import '../../services/invitation_service.dart'; 4 | import '../../utils/exception.dart'; 5 | import '../../utils/request_messages.dart'; 6 | 7 | class SendInvitationCubit extends Cubit { 8 | final InvitationService invitationService; 9 | SendInvitationCubit(this.invitationService) : super(InvitationInitial()); 10 | 11 | Future sendInvitation(Map data) async { 12 | emit(SendingInvitation()); 13 | 14 | try { 15 | await invitationService.sendInvitation(data); 16 | emit(InvitationSent(data['receiverId'] as String)); 17 | } on NetworkException catch (e) { 18 | emit(InvitationSentfailure(e.message!)); 19 | } on ServerException catch (e) { 20 | emit(InvitationSentfailure(e.message!)); 21 | } catch (_) { 22 | emit(InvitationSentfailure(ErrorMessages.generalMessage2)); 23 | } 24 | } 25 | } 26 | 27 | class SendInvitationState { 28 | const SendInvitationState(); 29 | } 30 | 31 | class InvitationInitial extends SendInvitationState {} 32 | 33 | class SendingInvitation extends SendInvitationState {} 34 | 35 | class InvitationSent extends SendInvitationState { 36 | final String receiverId; 37 | 38 | const InvitationSent(this.receiverId); 39 | } 40 | 41 | class InvitationSentfailure extends SendInvitationState { 42 | final String message; 43 | 44 | const InvitationSentfailure(this.message); 45 | } 46 | -------------------------------------------------------------------------------- /lib/blocs/invitation/received/received_invitation_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/invitation_model.dart'; 4 | import '../../../model/inviter_model.dart'; 5 | 6 | class ReceivedInvitationState extends Equatable { 7 | const ReceivedInvitationState(); 8 | 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class ReceivedInvitationInitial extends ReceivedInvitationState {} 14 | 15 | class LoadingInviters extends ReceivedInvitationState {} 16 | 17 | class InvitersLoaded extends ReceivedInvitationState { 18 | final InviterModel inviterModel; 19 | final bool hasMore; 20 | 21 | const InvitersLoaded({required this.inviterModel, this.hasMore = true}); 22 | 23 | @override 24 | List get props => [inviterModel]; 25 | } 26 | 27 | class InviterLoadingFailed extends ReceivedInvitationState { 28 | final String message; 29 | 30 | const InviterLoadingFailed(this.message); 31 | } 32 | 33 | // For Refresh 34 | class RefreshingInviters extends ReceivedInvitationState {} 35 | 36 | class RefreshingInvitersLoading extends ReceivedInvitationState {} 37 | 38 | class InviterRefreshFailed extends ReceivedInvitationState {} 39 | 40 | class OtherReceivedAction extends ReceivedInvitationState {} 41 | 42 | class Processing extends ReceivedInvitationState { 43 | final int index; 44 | const Processing(this.index); 45 | } 46 | 47 | class Ignored extends ReceivedInvitationState {} 48 | 49 | class OtherActionFailed extends ReceivedInvitationState { 50 | final int index; 51 | final Inviter inviter; 52 | 53 | const OtherActionFailed({required this.index, required this.inviter}); 54 | } 55 | 56 | class Accept extends ReceivedInvitationState { 57 | final Inviter inviter; 58 | final String fullName; 59 | 60 | const Accept({required this.inviter, required this.fullName}); 61 | } 62 | -------------------------------------------------------------------------------- /lib/blocs/invitation/sent/sent_invitations_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/invitation_model.dart'; 4 | import '../../../model/invitee_model.dart'; 5 | 6 | class SentInvitationState extends Equatable { 7 | const SentInvitationState(); 8 | 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class SentInvitationInitial extends SentInvitationState {} 14 | 15 | class LoadingInvitees extends SentInvitationState {} 16 | 17 | class InviteesLoaded extends SentInvitationState { 18 | final InviteeModel inviteeModel; 19 | final bool hasMore; 20 | 21 | const InviteesLoaded({required this.inviteeModel, this.hasMore = true}); 22 | 23 | @override 24 | List get props => [inviteeModel]; 25 | } 26 | 27 | class InviteeLoadingFailed extends SentInvitationState { 28 | final String message; 29 | 30 | const InviteeLoadingFailed(this.message); 31 | } 32 | 33 | // For other actions performed such as invitation withdrawal action 34 | class Withdrawing extends SentInvitationState { 35 | final int index; 36 | const Withdrawing(this.index); 37 | } 38 | 39 | class OtherInvitationAction extends SentInvitationState {} 40 | 41 | class Withdrawed extends SentInvitationState {} 42 | 43 | class WithdrawalFailed extends SentInvitationState { 44 | final int index; 45 | final Invitee invitee; 46 | 47 | const WithdrawalFailed({required this.index, required this.invitee}); 48 | } 49 | 50 | // For Refresh 51 | class RefreshingInvitees extends SentInvitationState {} 52 | 53 | class Refreshing extends SentInvitationState {} 54 | 55 | class InviteeRefreshFailed extends SentInvitationState {} 56 | -------------------------------------------------------------------------------- /lib/blocs/onboarding_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../repositories/local_storage.dart'; 5 | import '../utils/global_utils.dart'; 6 | 7 | class OnBoardingCubit extends Cubit { 8 | OnBoardingCubit(this.localStorage) : super(OnBoardingInitial()); 9 | final LocalStorage localStorage; 10 | 11 | Future isUserBoarded() async { 12 | final key = GlobalUtils.onBoardingSharedPrefKey; 13 | final isUserBoarded = await localStorage.getBoolData(key) ?? false; 14 | if (isUserBoarded) { 15 | emit(OnBoarded()); 16 | } else { 17 | emit(NotBoarded()); 18 | } 19 | } 20 | 21 | void setOnBoardingStatus() { 22 | localStorage.saveBoolData(GlobalUtils.onBoardingSharedPrefKey, true); 23 | emit(OnBoarded()); 24 | } 25 | } 26 | 27 | abstract class OnBoardingState extends Equatable { 28 | @override 29 | List get props => []; 30 | } 31 | 32 | class OnBoardingInitial extends OnBoardingState {} 33 | 34 | class OnBoarded extends OnBoardingState {} 35 | 36 | class NotBoarded extends OnBoardingState {} 37 | -------------------------------------------------------------------------------- /lib/blocs/search/connection/search_connection_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/pure_user_model.dart'; 4 | 5 | /// States for Search 6 | class SearchConnectionState extends Equatable { 7 | const SearchConnectionState(); 8 | 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class SearchConnEmpty extends SearchConnectionState {} 14 | 15 | class SearchConnLoading extends SearchConnectionState {} 16 | 17 | class SearchConnSuccess extends SearchConnectionState { 18 | final List users; 19 | final int currentPageNumber; 20 | final String query; 21 | final bool hasMore; 22 | 23 | const SearchConnSuccess({ 24 | required this.users, 25 | required this.currentPageNumber, 26 | required this.query, 27 | this.hasMore = true, 28 | }); 29 | 30 | @override 31 | List get props => [users, hasMore]; 32 | } 33 | 34 | class SearchConnFailure extends SearchConnectionState { 35 | const SearchConnFailure(this.message); 36 | final String message; 37 | } 38 | 39 | /// Events for search 40 | abstract class SearchConnEvent extends Equatable { 41 | const SearchConnEvent(); 42 | } 43 | 44 | class SearchConnTextChanged extends SearchConnEvent { 45 | const SearchConnTextChanged({required this.text}); 46 | 47 | final String text; 48 | 49 | @override 50 | List get props => [text]; 51 | } 52 | -------------------------------------------------------------------------------- /lib/blocs/search/friends/search_friends_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | import '../../../services/search_service.dart'; 4 | import '../../../utils/exception.dart'; 5 | import '../../../utils/request_messages.dart'; 6 | import '../search_username.dart'; 7 | import 'search_friends_state.dart'; 8 | 9 | class SearchFriendBloc extends Bloc { 10 | final SearchService searchService; 11 | SearchFriendBloc(this.searchService) : super(SearchFriendEmpty()) { 12 | on(_onTextChanged, 13 | transformer: debounce(duration)); 14 | on(_loadCurrentConnectiors, 15 | transformer: debounce(duration)); 16 | on(_addFriend, transformer: debounce(duration)); 17 | on(_deletFriend, transformer: debounce(duration)); 18 | } 19 | 20 | // load all current connectors 21 | Future _loadCurrentConnectiors( 22 | LoadAvailableFriends event, Emitter emit) async { 23 | emit(SearchFriendSuccess(friends: event.friends)); 24 | } 25 | 26 | Future _onTextChanged( 27 | SearchFriendTextChanged event, Emitter emit) async { 28 | try { 29 | if (event.text.isNotEmpty) { 30 | final result = await searchService.searchForFriends( 31 | event.text, 32 | event.currentUserId, 33 | event.friendIds, 34 | ); 35 | emit(SearchFriendSuccess(friends: result, query: event.text)); 36 | } else { 37 | emit(SearchFriendSuccess(friends: [], query: event.text)); 38 | } 39 | } on NetworkException catch (e) { 40 | emit(SearchFriendFailure(e.message!)); 41 | } on ServerException catch (e) { 42 | emit(SearchFriendFailure(e.message!)); 43 | } catch (_) { 44 | emit(SearchFriendFailure(ErrorMessages.generalMessage)); 45 | } 46 | } 47 | 48 | void _deletFriend(DeleteFriend event, Emitter emit) { 49 | final currentState = state; 50 | if (currentState is SearchFriendSuccess) { 51 | final friendList = currentState.friends.toList(); 52 | 53 | friendList.removeAt(event.index); 54 | emit(SearchFriendSuccess(friends: friendList, query: currentState.query)); 55 | } 56 | } 57 | 58 | void _addFriend(AddFriend event, Emitter emit) { 59 | final currentState = state; 60 | if (currentState is SearchFriendSuccess) { 61 | final friendList = currentState.friends.toList(); 62 | // remove the invitee from the list 63 | friendList.insert(event.index, event.friend); 64 | 65 | emit(SearchFriendSuccess(friends: friendList, query: currentState.query)); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/blocs/search/friends/search_friends_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/connection_model.dart'; 4 | 5 | /// States for Search 6 | class SearchFriendState extends Equatable { 7 | const SearchFriendState(); 8 | 9 | @override 10 | List get props => []; 11 | } 12 | 13 | class SearchFriendEmpty extends SearchFriendState {} 14 | 15 | class SearchFriendLoading extends SearchFriendState {} 16 | 17 | class SearchFriendSuccess extends SearchFriendState { 18 | final List friends; 19 | final String query; 20 | 21 | const SearchFriendSuccess({required this.friends, this.query = ""}); 22 | 23 | @override 24 | List get props => [friends, query]; 25 | } 26 | 27 | class SearchFriendFailure extends SearchFriendState { 28 | const SearchFriendFailure(this.message); 29 | final String message; 30 | } 31 | 32 | /// Events for search 33 | abstract class SearchFriendEvent extends Equatable { 34 | const SearchFriendEvent(); 35 | } 36 | 37 | class SearchFriendTextChanged extends SearchFriendEvent { 38 | const SearchFriendTextChanged({ 39 | required this.text, 40 | required this.currentUserId, 41 | required this.friendIds, 42 | }); 43 | 44 | final String text; 45 | final String currentUserId; 46 | final List friendIds; 47 | 48 | @override 49 | List get props => [text, currentUserId, friendIds]; 50 | } 51 | 52 | class LoadAvailableFriends extends SearchFriendEvent { 53 | const LoadAvailableFriends({required this.friends}); 54 | 55 | final List friends; 56 | 57 | @override 58 | List get props => [friends]; 59 | } 60 | 61 | class DeleteFriend extends SearchFriendEvent { 62 | const DeleteFriend({required this.index}); 63 | 64 | final int index; 65 | 66 | @override 67 | List get props => [index]; 68 | } 69 | 70 | class AddFriend extends SearchFriendEvent { 71 | const AddFriend({required this.index, required this.friend}); 72 | 73 | final int index; 74 | final Connector friend; 75 | 76 | @override 77 | List get props => [index, friend]; 78 | } 79 | -------------------------------------------------------------------------------- /lib/blocs/search/search_username.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:stream_transform/stream_transform.dart'; 4 | 5 | import '../../services/search_service.dart'; 6 | 7 | const duration = const Duration(milliseconds: 300); 8 | 9 | EventTransformer debounce(Duration duration) { 10 | return (events, mapper) => events.debounce(duration).switchMap(mapper); 11 | } 12 | 13 | class SearchBloc extends Bloc { 14 | final SearchService searchService; 15 | SearchBloc(this.searchService) : super(SearchStateEmpty()) { 16 | on(_onTextChanged, transformer: debounce(duration)); 17 | } 18 | 19 | Future _onTextChanged( 20 | TextChanged event, 21 | Emitter emit, 22 | ) async { 23 | final username = event.text; 24 | 25 | if (username.isEmpty) return emit(SearchStateEmpty()); 26 | 27 | emit(SearchStateLoading()); 28 | 29 | bool isAvailable = await searchService.searchUsername(username); 30 | emit(SearchSuccess(isAvailable, username)); 31 | } 32 | } 33 | 34 | class SearchState extends Equatable { 35 | const SearchState(); 36 | 37 | @override 38 | List get props => []; 39 | } 40 | 41 | /// States for Search 42 | 43 | class SearchStateEmpty extends SearchState {} 44 | 45 | class SearchStateLoading extends SearchState {} 46 | 47 | class SearchSuccess extends SearchState { 48 | final bool isAvailable; 49 | final String username; 50 | 51 | const SearchSuccess(this.isAvailable, this.username); 52 | 53 | @override 54 | List get props => [isAvailable]; 55 | } 56 | 57 | /// Events for search 58 | abstract class SearchEvent extends Equatable { 59 | const SearchEvent(); 60 | } 61 | 62 | class TextChanged extends SearchEvent { 63 | const TextChanged({required this.text}); 64 | 65 | final String text; 66 | 67 | @override 68 | List get props => [text]; 69 | } 70 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/profile/profile_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../../model/pure_user_model.dart'; 7 | import '../../../services/user_service.dart'; 8 | import 'profile_state.dart'; 9 | 10 | class ProfileCubit extends Cubit { 11 | final UserService userService; 12 | ProfileCubit(this.userService) : super(ProfileInitial()); 13 | 14 | StreamSubscription? _subscription; 15 | 16 | Future getProfile(String userId) async { 17 | try { 18 | _subscription?.cancel(); 19 | _subscription = userService.getUserData(userId).listen((user) { 20 | if (user != null) { 21 | emit(ProfileSuccess(user)); 22 | } 23 | }); 24 | } catch (e) { 25 | // No need to show message, it is a stream and will continue to listen 26 | log(e.toString()); 27 | } 28 | } 29 | 30 | @override 31 | Future close() { 32 | _subscription?.cancel(); 33 | _subscription = null; 34 | return super.close(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/profile/profile_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/pure_user_model.dart'; 4 | 5 | class ProfileState extends Equatable { 6 | const ProfileState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class ProfileInitial extends ProfileState {} 13 | 14 | class ProfileSuccess extends ProfileState { 15 | final PureUser user; 16 | 17 | const ProfileSuccess(this.user); 18 | 19 | @override 20 | List get props => [user]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/user_extra/user_extra_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | import '../../../services/user_service.dart'; 4 | import '../../../utils/exception.dart'; 5 | import '../../../utils/request_messages.dart'; 6 | import 'user_extra_state.dart'; 7 | 8 | class UserExtraCubit extends Cubit { 9 | UserExtraCubit(this.userService) : super(UserExtraInitial()); 10 | 11 | final UserService userService; 12 | 13 | Future getExtraData(String userId) async { 14 | emit(UserExtraLoading()); 15 | 16 | try { 17 | final result = await userService.getUserExtraData(userId); 18 | return emit(UserExtraSuccess(extraData: result)); 19 | } on NetworkException catch (e) { 20 | emit(UserExtraFailure(e.message!)); 21 | } on ServerException catch (e) { 22 | emit(UserExtraFailure(e.message!)); 23 | } on Exception catch (_) { 24 | emit(UserExtraFailure(ErrorMessages.generalMessage2)); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/user_extra/user_extra_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:pure/model/pure_user_extra.dart'; 3 | 4 | abstract class UserExtraState extends Equatable { 5 | const UserExtraState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class UserExtraInitial extends UserExtraState {} 12 | 13 | class UserExtraLoading extends UserExtraState {} 14 | 15 | class UserExtraSuccess extends UserExtraState { 16 | final PureUserExtraModel extraData; 17 | 18 | const UserExtraSuccess({required this.extraData}); 19 | 20 | @override 21 | List get props => [extraData]; 22 | } 23 | 24 | class UserExtraFailure extends UserExtraState { 25 | const UserExtraFailure(this.message); 26 | 27 | final String message; 28 | } 29 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/user_presence/user_presence_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../../model/user_presence_model.dart'; 7 | import '../../../services/user_service.dart'; 8 | import 'user_presence_state.dart'; 9 | 10 | class UserPresenceCubit extends Cubit { 11 | final UserService userService; 12 | UserPresenceCubit(this.userService) : super(UserPresenceInitial()); 13 | 14 | StreamSubscription? _subscription; 15 | 16 | Future getUserPresence(String userId) async { 17 | try { 18 | _subscription?.cancel(); 19 | _subscription = userService.getUserPresence(userId).listen((presence) { 20 | if (presence != null) { 21 | emit(UserPresenceSuccess(presence)); 22 | } 23 | }); 24 | } catch (e) { 25 | // No need to show message, it is a stream and will continue to listen 26 | log(e.toString()); 27 | } 28 | } 29 | 30 | @override 31 | Future close() { 32 | _subscription?.cancel(); 33 | _subscription = null; 34 | return super.close(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/user_presence/user_presence_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../../model/user_presence_model.dart'; 4 | 5 | class UserPresenceState extends Equatable { 6 | const UserPresenceState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class UserPresenceInitial extends UserPresenceState {} 13 | 14 | class UserPresenceSuccess extends UserPresenceState { 15 | final UserPresenceModel presence; 16 | 17 | const UserPresenceSuccess(this.presence); 18 | 19 | @override 20 | List get props => [presence]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/user_profile_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../services/user_service.dart'; 7 | import '../../utils/exception.dart'; 8 | import '../../utils/request_messages.dart'; 9 | import 'user_profile_state.dart'; 10 | 11 | class UserProfileCubit extends Cubit { 12 | UserProfileCubit(this.userService) : super(UserProfileInitial()); 13 | 14 | final UserService userService; 15 | 16 | Future updateUserProfile( 17 | String userId, Map data) async { 18 | emit(Loading()); 19 | try { 20 | await userService.updateUser(userId, data); 21 | emit(UserProfileUpdateSuccess()); 22 | } on NetworkException catch (e) { 23 | emit(UserProfileUpdateFailure(e.message!)); 24 | } on ServerException catch (e) { 25 | emit(UserProfileUpdateFailure(e.message!)); 26 | } on Exception catch (_) { 27 | emit(UserProfileUpdateFailure(ErrorMessages.generalMessage2)); 28 | } 29 | } 30 | 31 | Future updateProfileImage(String userId, File file) async { 32 | emit(ImageUploading()); 33 | try { 34 | await userService.updateUserProfileImage(userId, file); 35 | emit(ProfileImageUpdateSuccess()); 36 | } on NetworkException catch (e) { 37 | emit(ProfileImageUpdateFailure(e.message!)); 38 | } on ServerException catch (e) { 39 | emit(ProfileImageUpdateFailure(e.message!)); 40 | } on Exception catch (_) { 41 | emit(ProfileImageUpdateFailure(ErrorMessages.generalMessage2)); 42 | } 43 | } 44 | 45 | Future deleteProfileImage(String userId) async { 46 | emit(ImageUploading()); 47 | try { 48 | await userService.deleteProfileImage(userId); 49 | emit(ProfileImageUpdateSuccess()); 50 | } on NetworkException catch (e) { 51 | emit(ProfileImageUpdateFailure(e.message!)); 52 | } on ServerException catch (e) { 53 | emit(ProfileImageUpdateFailure(e.message!)); 54 | } on Exception catch (_) { 55 | emit(ProfileImageUpdateFailure(ErrorMessages.generalMessage2)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/blocs/user_profile/user_profile_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class UserProfileState extends Equatable { 4 | const UserProfileState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class UserProfileInitial extends UserProfileState {} 11 | 12 | class Loading extends UserProfileState {} 13 | 14 | class ImageUploading extends UserProfileState {} 15 | 16 | class UserProfileUpdateSuccess extends UserProfileState {} 17 | 18 | class ProfileImageUpdateSuccess extends UserProfileState {} 19 | 20 | class UserProfileUpdateFailure extends UserProfileState { 21 | const UserProfileUpdateFailure(this.message); 22 | 23 | final String message; 24 | } 25 | 26 | class ProfileImageUpdateFailure extends UserProfileState { 27 | const ProfileImageUpdateFailure(this.message); 28 | 29 | final String message; 30 | } 31 | -------------------------------------------------------------------------------- /lib/l10n/arb/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en", 3 | "counterAppBarTitle": "Counter", 4 | "@counterAppBarTitle": { 5 | "description": "Text shown in the AppBar of the Counter Page" 6 | } 7 | } -------------------------------------------------------------------------------- /lib/l10n/arb/app_es.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "es", 3 | "counterAppBarTitle": "Contador", 4 | "@counterAppBarTitle": { 5 | "description": "Texto mostrado en la AppBar de la página del contador" 6 | } 7 | } -------------------------------------------------------------------------------- /lib/l10n/l10n.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | 4 | export 'package:flutter_gen/gen_l10n/app_localizations.dart'; 5 | 6 | extension AppLocalizationsX on BuildContext { 7 | AppLocalizations get l10n => AppLocalizations.of(this); 8 | } 9 | -------------------------------------------------------------------------------- /lib/main_development.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | 4 | import 'package:firebase_core/firebase_core.dart'; 5 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'app.dart'; 10 | import 'utils/flavors.dart'; 11 | 12 | void main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | F.appFlavor = Flavor.dev; 15 | 16 | FlutterError.onError = (details) { 17 | log(details.exceptionAsString(), stackTrace: details.stack); 18 | }; 19 | 20 | // intializes Firebase 21 | await Firebase.initializeApp(); 22 | if (kDebugMode) { 23 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); 24 | } else { 25 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); 26 | // Pass all uncaught errors from the framework to Crashlytics. 27 | FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; 28 | } 29 | 30 | runZonedGuarded( 31 | () => runApp(const App()), 32 | (error, stackTrace) => log(error.toString(), stackTrace: stackTrace), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/main_production.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | 4 | import 'package:firebase_core/firebase_core.dart'; 5 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'app.dart'; 10 | import 'utils/flavors.dart'; 11 | 12 | void main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | F.appFlavor = Flavor.prod; 15 | 16 | FlutterError.onError = (details) { 17 | log(details.exceptionAsString(), stackTrace: details.stack); 18 | }; 19 | 20 | // intializes Firebase 21 | await Firebase.initializeApp(); 22 | if (kDebugMode) { 23 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); 24 | } else { 25 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); 26 | // Pass all uncaught errors from the framework to Crashlytics. 27 | FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; 28 | } 29 | 30 | runZonedGuarded( 31 | () => runApp(const App()), 32 | (error, stackTrace) => log(error.toString(), stackTrace: stackTrace), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/main_staging.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | 4 | import 'package:firebase_core/firebase_core.dart'; 5 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'app.dart'; 10 | import 'utils/flavors.dart'; 11 | 12 | void main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | F.appFlavor = Flavor.staging; 15 | 16 | FlutterError.onError = (details) { 17 | log(details.exceptionAsString(), stackTrace: details.stack); 18 | }; 19 | 20 | // intializes Firebase 21 | await Firebase.initializeApp(); 22 | if (kDebugMode) { 23 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(false); 24 | } else { 25 | await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); 26 | // Pass all uncaught errors from the framework to Crashlytics. 27 | FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError; 28 | } 29 | 30 | runZonedGuarded( 31 | () => runApp(const App()), 32 | (error, stackTrace) => log(error.toString(), stackTrace: stackTrace), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/model/app_enum.dart: -------------------------------------------------------------------------------- 1 | // This enum is used to show the user connection status with another user 2 | // You might ask what is the difference between PENDING and ACCEPT 3 | // When the inviter views the invitee profile it shows PENDING but when the 4 | // the invitee views the inviter profile it shows ACCEPT 5 | enum ConnectionAction { CONNECT, PENDING, ACCEPT, MESSAGE } 6 | -------------------------------------------------------------------------------- /lib/model/connection_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../utils/true_time.dart'; 4 | import 'inviter_model.dart'; 5 | 6 | // The person that you are connected with is called Connector 7 | class Connector extends Equatable { 8 | final String connectorId; 9 | final String connectionId; 10 | final DateTime? connectionDate; 11 | 12 | const Connector({ 13 | required this.connectorId, 14 | required this.connectionId, 15 | this.connectionDate, 16 | }); 17 | 18 | factory Connector.fromMap(Map data, {String? connectorId}) { 19 | return Connector( 20 | connectorId: connectorId ?? data['connectorId'] as String, 21 | connectionId: data['id'] as String, 22 | connectionDate: DateTime.parse(data['date'] as String).toLocal(), 23 | ); 24 | } 25 | 26 | factory Connector.fromInviter(Inviter inviter) { 27 | return Connector( 28 | connectorId: inviter.inviterId, 29 | connectionId: inviter.invitationId, 30 | connectionDate: TrueTime.now().toLocal(), 31 | ); 32 | } 33 | 34 | // required for data of user to save to local storage 35 | Map toSaveMap() { 36 | return { 37 | 'connectorId': connectorId, 38 | 'id': connectionId, 39 | "date": connectionDate!.toUtc().toIso8601String(), 40 | }; 41 | } 42 | 43 | @override 44 | List get props => [connectorId, connectionId, connectionDate]; 45 | } 46 | -------------------------------------------------------------------------------- /lib/model/invitation_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | import '../utils/true_time.dart'; 5 | import 'connection_model.dart'; 6 | import 'invitee_model.dart'; 7 | import 'inviter_model.dart'; 8 | 9 | class ConnectionModel extends Equatable { 10 | final List connectors; 11 | final DocumentSnapshot? lastDoc; 12 | 13 | const ConnectionModel({required this.connectors, this.lastDoc}); 14 | 15 | @override 16 | List get props => [connectors, lastDoc]; 17 | } 18 | 19 | class InviteeModel extends Equatable { 20 | final List invitees; 21 | final DocumentSnapshot? lastDoc; 22 | 23 | const InviteeModel({required this.invitees, this.lastDoc}); 24 | 25 | @override 26 | List get props => [invitees, lastDoc]; 27 | } 28 | 29 | class InviterModel extends Equatable { 30 | final List inviters; 31 | final DocumentSnapshot? lastDoc; 32 | 33 | const InviterModel({required this.inviters, this.lastDoc}); 34 | 35 | @override 36 | List get props => [inviters, lastDoc]; 37 | } 38 | 39 | class InvitationModel { 40 | final String senderId; 41 | final String receiverId; 42 | final bool isAccepted; 43 | const InvitationModel({ 44 | required this.senderId, 45 | required this.receiverId, 46 | this.isAccepted = false, 47 | }); 48 | 49 | Map toMap() { 50 | return { 51 | 'id': getInvitationId(senderId, receiverId), 52 | "senderId": senderId, 53 | "receiverId": receiverId, 54 | "isAccepted": isAccepted, 55 | "sentDate": TrueTime.now().toUtc().toIso8601String(), 56 | "members": [senderId, receiverId], 57 | }; 58 | } 59 | 60 | Map toInviteLinkMap(String id) { 61 | return { 62 | 'id': id, 63 | "senderId": senderId, 64 | "receiverId": "", 65 | "isAccepted": false, 66 | "sentDate": TrueTime.now().toUtc().toIso8601String(), 67 | "members": [senderId], 68 | }; 69 | } 70 | 71 | static Map toUpdateMap(bool isAccepted) { 72 | return {"isAccepted": isAccepted}; 73 | } 74 | 75 | static String getInvitationId(String senderId, String receiverId) { 76 | if (senderId.compareTo(receiverId) == -1) { 77 | return "${senderId}_${receiverId}"; 78 | } else { 79 | return "${receiverId}_${senderId}"; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/model/invitee_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | // The person that invitation is sent to or that receives invitation 4 | // is called invitee. 5 | 6 | class Invitee extends Equatable { 7 | final String inviteeId; 8 | final String invitationId; 9 | final DateTime? sentDate; 10 | 11 | const Invitee({ 12 | required this.inviteeId, 13 | required this.invitationId, 14 | this.sentDate, 15 | }); 16 | 17 | factory Invitee.fromMap(Map data) { 18 | return Invitee( 19 | inviteeId: data['receiverId'] as String, 20 | invitationId: data['id'] as String, 21 | sentDate: DateTime.parse(data['sentDate'] as String).toLocal(), 22 | ); 23 | } 24 | 25 | @override 26 | List get props => [inviteeId, invitationId, sentDate]; 27 | 28 | // required for data of user to save to local storage 29 | Map toSaveMap() { 30 | return { 31 | 'receiverId': inviteeId, 32 | 'id': invitationId, 33 | "sentDate": sentDate!.toUtc().toIso8601String(), 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/model/inviter_model.dart: -------------------------------------------------------------------------------- 1 | // The person that sent invitation is called inviter 2 | 3 | import 'package:equatable/equatable.dart'; 4 | 5 | class Inviter extends Equatable { 6 | final String inviterId; 7 | final String invitationId; 8 | final DateTime? receivedDate; 9 | 10 | const Inviter({ 11 | required this.inviterId, 12 | required this.invitationId, 13 | this.receivedDate, 14 | }); 15 | 16 | factory Inviter.fromMap(Map data) { 17 | return Inviter( 18 | inviterId: data['senderId'] as String, 19 | invitationId: data['id'] as String, 20 | receivedDate: DateTime.parse(data['sentDate'] as String).toLocal(), 21 | ); 22 | } 23 | 24 | @override 25 | List get props => [inviterId, invitationId, receivedDate]; 26 | 27 | // required for data of user to save to local storage 28 | Map toSaveMap() { 29 | return { 30 | 'senderId': inviterId, 31 | 'id': invitationId, 32 | "sentDate": receivedDate!.toUtc().toIso8601String(), 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/model/onboarding.dart: -------------------------------------------------------------------------------- 1 | import '../utils/image_utils.dart'; 2 | 3 | class OnBoardingModel { 4 | const OnBoardingModel(this.image, this.title, this.subTitle); 5 | 6 | final String image; 7 | final String title; 8 | final String subTitle; 9 | 10 | static List onboardingSlides(bool isDark) { 11 | return [ 12 | OnBoardingModel( 13 | isDark ? ImageUtils.slide1Dark : ImageUtils.slide1Light, 14 | 'Chat and communicate', 15 | 'Communicate with your friends and find new ones based on common interests', 16 | ), 17 | OnBoardingModel( 18 | isDark ? ImageUtils.slide2Dark : ImageUtils.slide2Light, 19 | 'Smartphone or a laptop', 20 | 'Stay up to date with all events, using any device convenient for ' 21 | 'you - we are multiplatform!', 22 | ), 23 | OnBoardingModel( 24 | isDark ? ImageUtils.slide3Dark : ImageUtils.slide3Light, 25 | 'Content that you’ll like', 26 | 'Enjoy the content that we select just for you, based on your interests ' 27 | 'and hobbies, so as not to make you sad!', 28 | ) 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/model/post_model.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/model/pure_user_extra.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import 'pure_user_model.dart'; 4 | 5 | class PureUserExtraModel extends Equatable { 6 | final int totalConnection; 7 | final List connections; 8 | 9 | const PureUserExtraModel({ 10 | required this.connections, 11 | required this.totalConnection, 12 | }); 13 | 14 | factory PureUserExtraModel.fromMap(Map data) { 15 | final List connections = []; 16 | final connectionsMap = data["connections"] as Map; 17 | 18 | for (final connection in connectionsMap.entries) { 19 | final status = PureUser.getStatus(connection.value as int); 20 | if (status == ConnectionStatus.Connected) connections.add(connection.key); 21 | } 22 | 23 | return PureUserExtraModel( 24 | totalConnection: data["connectionCounter"] as int, 25 | connections: connections, 26 | ); 27 | } 28 | 29 | @override 30 | List get props => [totalConnection, connections]; 31 | } 32 | -------------------------------------------------------------------------------- /lib/model/route/message_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:pure/blocs/bloc.dart'; 3 | import 'package:pure/model/pure_user_model.dart'; 4 | 5 | class MessageRoute extends Equatable { 6 | final String chatId; 7 | final PureUser receipient; 8 | final bool hasPresenceActivated; 9 | final UserPresenceCubit? state; 10 | 11 | const MessageRoute({ 12 | required this.chatId, 13 | required this.receipient, 14 | this.hasPresenceActivated = false, 15 | this.state, 16 | }); 17 | 18 | @override 19 | List get props => [chatId, receipient, hasPresenceActivated, state]; 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/user_presence_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../utils/true_time.dart'; 4 | 5 | class UserPresenceModel extends Equatable { 6 | final bool isOnline; 7 | final DateTime lastSeen; 8 | 9 | const UserPresenceModel({required this.isOnline, required this.lastSeen}); 10 | 11 | factory UserPresenceModel.fromMap(Map data) { 12 | return UserPresenceModel( 13 | isOnline: data['isOnline'] as bool, 14 | lastSeen: DateTime.parse(data['lastSeen'] as String), 15 | ); 16 | } 17 | 18 | static UserPresenceModel onError() { 19 | return UserPresenceModel(isOnline: false, lastSeen: TrueTime.now()); 20 | } 21 | 22 | static Map onlineData() { 23 | return { 24 | 'isOnline': true, 25 | "lastSeen": TrueTime.now().toUtc().toIso8601String() 26 | }; 27 | } 28 | 29 | static Map offlineData() { 30 | return { 31 | 'isOnline': false, 32 | "lastSeen": TrueTime.now().toUtc().toIso8601String() 33 | }; 34 | } 35 | 36 | @override 37 | List get props => [isOnline, lastSeen]; 38 | } 39 | -------------------------------------------------------------------------------- /lib/repositories/algolia_application.dart: -------------------------------------------------------------------------------- 1 | import 'package:algolia/algolia.dart'; 2 | 3 | import '../utils/flavors.dart'; 4 | 5 | class AlgoliaApplication { 6 | static Algolia get algolia { 7 | switch (F.appFlavor) { 8 | case Flavor.prod: 9 | return Algolia.init( 10 | applicationId: '008HWZTMCQ', 11 | apiKey: 'f0a0ebe81ded9c39aefe9666ec3e81f6', 12 | ); 13 | case Flavor.staging: 14 | return Algolia.init( 15 | applicationId: 'EOKG5FZ08Q', 16 | apiKey: '2c8b4ea038196af30f8c8539fb15501d', 17 | ); 18 | default: 19 | return Algolia.init( 20 | applicationId: 'R0547NV5EC', 21 | apiKey: '536c516e3a6d13732b71017fa635dcfa', 22 | ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/repositories/connection.dart: -------------------------------------------------------------------------------- 1 | import '../utils/exception.dart'; 2 | import '../utils/request_messages.dart'; 3 | 4 | abstract class ConnectionRepo { 5 | Future checkConnection(); 6 | Future checkConnectivity(); 7 | } 8 | 9 | class ConnectionRepoImpl implements ConnectionRepo { 10 | // The test to actually see if there is a connection 11 | @override 12 | Future checkConnection() async { 13 | // removes network connection because it affects requests time... 14 | // replaces it by adding connection timeout 15 | return true; 16 | } 17 | 18 | // checks connectivity status 19 | @override 20 | Future checkConnectivity() async { 21 | if (!await checkConnection()) { 22 | return throw NetworkException( 23 | message: ErrorMessages.internetconnectionMessage); 24 | } 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/services/remote_storage_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:firebase_storage/firebase_storage.dart'; 4 | import 'package:pure/utils/app_utils.dart'; 5 | 6 | import '../utils/global_utils.dart'; 7 | 8 | abstract class RemoteStorage { 9 | Future uploadProfileImage(String userId, File file); 10 | Future uploadChatFile(String chatId, File file, String fileExt); 11 | } 12 | 13 | class RemoteStorageImpl implements RemoteStorage { 14 | RemoteStorageImpl({this.firebaseStorage}) { 15 | firebaseStorage = firebaseStorage ?? FirebaseStorage.instance; 16 | } 17 | 18 | FirebaseStorage? firebaseStorage; 19 | 20 | /// upload image file to firebase storage and return the file imageurl 21 | @override 22 | Future uploadProfileImage(String userId, File file) async { 23 | final storageReference = 24 | firebaseStorage!.ref().child('profile').child('$userId.png'); 25 | await storageReference 26 | .putFile(file) 27 | .timeout(GlobalUtils.imageUploadtimeOutInDuration); 28 | final url = await storageReference.getDownloadURL(); 29 | return url; 30 | } 31 | 32 | @override 33 | Future uploadChatFile( 34 | String chatId, File file, String fileExt) async { 35 | final fileId = generateRandomId(); 36 | final storageReference = 37 | firebaseStorage!.ref("Chats").child(chatId).child("$fileId$fileExt"); 38 | await storageReference 39 | .putFile(file) 40 | .timeout(GlobalUtils.imageUploadtimeOutInDuration); 41 | final url = await storageReference.getDownloadURL(); 42 | return url; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/services/upload.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | abstract class Upload { 4 | Future uploadImage(String id, File imageFile); 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/adaptive_icons.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class AdaptiveIcons { 7 | const AdaptiveIcons(); 8 | 9 | // share icon 10 | static IconData get share => 11 | Platform.isIOS ? CupertinoIcons.share : Icons.share_outlined; 12 | 13 | // delete icon 14 | static IconData get delete => 15 | Platform.isIOS ? CupertinoIcons.delete : Icons.delete_forever_outlined; 16 | 17 | // edit icon 18 | static IconData get edit => Icons.edit; 19 | 20 | // settings icon 21 | static IconData get settings => Icons.settings_outlined; 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/app_extension.dart: -------------------------------------------------------------------------------- 1 | // Extension on String -------------------------------> 2 | // Sentence case 3 | extension StringMethod on String { 4 | String toSentenceCase() => this[0].toUpperCase() + substring(1).toLowerCase(); 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/app_permission.dart: -------------------------------------------------------------------------------- 1 | import 'package:adaptive_dialog/adaptive_dialog.dart'; 2 | import 'package:app_settings/app_settings.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:permission_handler/permission_handler.dart'; 5 | 6 | class AppPermission { 7 | // check if camera permission is granted 8 | static Future checkCameraPermission(BuildContext context) async { 9 | final status = await Permission.camera.request(); 10 | 11 | if (status == PermissionStatus.denied || 12 | status == PermissionStatus.permanentlyDenied) { 13 | const title = 'Enable Camera Access'; 14 | const message = 'Go to settings to enable camera access'; 15 | await _showPermissionRequestDialog( 16 | context: context, 17 | title: title, 18 | message: message, 19 | ); 20 | } 21 | } 22 | 23 | // check if camera permission is granted 24 | static Future checkPhotoPermission(BuildContext context) async { 25 | final status = await Permission.photos.request(); 26 | if (status == PermissionStatus.denied || 27 | status == PermissionStatus.permanentlyDenied) { 28 | const title = 'Enable Photos Access'; 29 | const message = 'Go to settings to enable photo access'; 30 | await _showPermissionRequestDialog( 31 | context: context, 32 | title: title, 33 | message: message, 34 | ); 35 | } 36 | } 37 | 38 | static Future checkMicrophonePermission(BuildContext context) async { 39 | final status = await Permission.microphone.request(); 40 | if (status == PermissionStatus.denied || 41 | status == PermissionStatus.permanentlyDenied) { 42 | const title = 'Enable Microphone Access'; 43 | const message = 'Go to settings to enable microphone access'; 44 | await _showPermissionRequestDialog( 45 | context: context, 46 | title: title, 47 | message: message, 48 | ); 49 | } 50 | } 51 | 52 | // check if camera permission is granted 53 | static Future checkVideoPermission(BuildContext context) async { 54 | await checkCameraPermission(context); 55 | await checkMicrophonePermission(context); 56 | } 57 | 58 | static Future _showPermissionRequestDialog( 59 | {required BuildContext context, 60 | required String title, 61 | required String message}) async { 62 | final result = await showOkCancelAlertDialog( 63 | barrierDismissible: false, 64 | context: context, 65 | title: title, 66 | message: message, 67 | okLabel: 'Settings', 68 | ); 69 | 70 | if (result == OkCancelResult.ok) { 71 | // open app settings for permission to be granted 72 | await AppSettings.openAppSettings(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/utils/app_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timeago/timeago.dart' as timeago; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | import '../model/pure_user_model.dart'; 7 | import '../repositories/push_notification.dart'; 8 | import '../services/user_service.dart'; 9 | import '../views/widgets/snackbars.dart'; 10 | 11 | // Generate a v1 (time-based) identifier 12 | String generateDatabaseId() { 13 | return const Uuid().v1(); 14 | } 15 | 16 | String generateRandomId() { 17 | return const Uuid().v1(); 18 | } 19 | 20 | // updates the user fcm token in the remote database 21 | Future updateUserFCMToken(String userId) async { 22 | final notification = PushNotificationImpl(); 23 | final token = await notification.getToken(); 24 | final deviceId = await notification.getDeviceId(); 25 | if (deviceId != null && token != null) { 26 | // updates the token at the server side 27 | UserServiceImpl().updateUserFCMToken(userId, deviceId, token); 28 | } 29 | } 30 | 31 | String getFormattedTime(DateTime date) { 32 | return timeago.format(date, allowFromNow: true); 33 | } 34 | 35 | // This function returns list of user connection 36 | 37 | List getConnections(Map data) { 38 | final List connections = []; 39 | // gets all the user identifier of the users am connected with in a Map. 40 | // The key is the userId while the value is the ConnectionStatus 41 | // users am connected with has ConnectionStatus to be equal to Connected 42 | 43 | for (final data in data.entries) 44 | if (data.value == ConnectionStatus.Connected) connections.add(data.key); 45 | 46 | return connections.toList(); 47 | } 48 | 49 | Future launchIfCan(BuildContext context, String url) async { 50 | final result = await canLaunch(url); 51 | if (result) 52 | launch(url); 53 | else { 54 | final message = "Please install a browser that can open the link"; 55 | showFailureFlash(context, message); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/utils/exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | 3 | import 'request_messages.dart'; 4 | 5 | class CacheException implements Exception { 6 | CacheException({this.code, this.message, this.details}); 7 | 8 | /// An error code. 9 | final int? code; 10 | 11 | /// A human-readable error message, possibly null. 12 | final String? message; 13 | 14 | /// Error details, possibly null. 15 | final dynamic details; 16 | } 17 | 18 | class MaximumUploadExceededException implements Exception { 19 | MaximumUploadExceededException({this.code, this.message, this.details}); 20 | 21 | /// An error code. 22 | final int? code; 23 | 24 | /// A human-readable error message, possibly null. 25 | final String? message; 26 | 27 | /// Error details, possibly null. 28 | final dynamic details; 29 | } 30 | 31 | class ServerException implements Exception { 32 | ServerException({this.code, this.message, this.details}); 33 | 34 | /// An error code. 35 | final int? code; 36 | 37 | /// A human-readable error message, possibly null. 38 | final String? message; 39 | 40 | /// Error details, possibly null. 41 | final dynamic details; 42 | } 43 | 44 | class NetworkException implements Exception { 45 | NetworkException({this.code, this.message, this.details}); 46 | 47 | /// An error code. 48 | final int? code; 49 | 50 | /// A human-readable error message, possibly null. 51 | final String? message; 52 | 53 | /// Error details, possibly null. 54 | final dynamic details; 55 | } 56 | 57 | String authenticationException(FirebaseAuthException e, 58 | {bool isChangePassword = false}) { 59 | switch (e.code) { 60 | case 'user-not-found': 61 | return 'User with the email entered not found'; 62 | case 'wrong-password': 63 | return isChangePassword 64 | ? 'The current password is incorrect' 65 | : 'The password is incorrect'; 66 | case 'invalid-email': 67 | return 'This email does not exist in our database'; 68 | case 'weak-password': 69 | return 'The password provided is too weak'; 70 | case 'email-already-in-use': 71 | return 'User with this email already exist'; 72 | case 'invalid-phone-number': 73 | return "Please provide a valid mobile no"; 74 | case 'A network error (such as timeout, interrupted connection or ' 75 | 'unreachable host) has occurred.': 76 | return ErrorMessages.internetconnectionMessage; 77 | default: 78 | return ErrorMessages.generalMessage; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/utils/flavors.dart: -------------------------------------------------------------------------------- 1 | enum Flavor { dev, staging, prod } 2 | 3 | class F { 4 | static late Flavor appFlavor; 5 | 6 | static String get title { 7 | switch (appFlavor) { 8 | case Flavor.dev: 9 | return 'Pure Dev'; 10 | case Flavor.staging: 11 | return 'Pure Beta'; 12 | case Flavor.prod: 13 | return 'Pure'; 14 | } 15 | } 16 | 17 | static String get appId { 18 | switch (appFlavor) { 19 | case Flavor.dev: 20 | return 'com.annulus.pure.dev'; 21 | case Flavor.staging: 22 | return 'com.annulus.pure.stg'; 23 | case Flavor.prod: 24 | return 'com.annulus.pure'; 25 | } 26 | } 27 | 28 | static String? get dynamicLinkUriPrefix { 29 | switch (appFlavor) { 30 | case Flavor.dev: 31 | return 'https://puredev.page.link'; 32 | case Flavor.staging: 33 | return 'https://purebeta.page.link'; 34 | case Flavor.prod: 35 | return 'https://pure.page.link'; 36 | } 37 | } 38 | 39 | // only for IOS 40 | static String get appStoreId { 41 | switch (appFlavor) { 42 | case Flavor.dev: 43 | return '0123456789'; 44 | case Flavor.staging: 45 | return '0123456789'; 46 | case Flavor.prod: 47 | return '0123456789'; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils/global_utils.dart: -------------------------------------------------------------------------------- 1 | class GlobalUtils { 2 | // local storage keys 3 | static String get onBoardingSharedPrefKey => 'onBoardingSharedPrefKey'; 4 | static String get userSharedPrefKey => 'userDataPrefKey'; 5 | static String get sentInvitationPrefKey => 'sentInvitationPrefKey'; 6 | static String get receivedInvitationPrefKey => 'receivedInvitationPrefKey'; 7 | static String get connectionsPrefKey => 'connectionsPrefKey'; 8 | 9 | // Firebase collection and document names 10 | static String get userCollection => 'Users'; 11 | static String get invitationCollection => 'Invitations'; 12 | static String get connectionCollection => 'Connections'; 13 | static String get messageCollection => 'Messages'; 14 | static String get chatCollection => 'Chats'; 15 | static String get receiptCollection => 'Receipts'; 16 | static String get invitationLinkCollection => 'InvitationLinks'; 17 | static String get userExtCollection => 'UsersExt'; 18 | 19 | static Duration get timeOutInDuration => 20 | const Duration(milliseconds: 15 * 1000); 21 | static Duration get updateTimeOutInDuration => 22 | const Duration(milliseconds: 10 * 1000); 23 | static Duration get shortTimeOutInDuration => 24 | const Duration(milliseconds: 5 * 1000); 25 | 26 | static Duration get imageUploadtimeOutInDuration => 27 | const Duration(milliseconds: 40 * 1000); 28 | 29 | static int get maxImageUploadSizeInByte => 5 * 1024 * 1024; 30 | 31 | static int get maxFileUploadSizeInByte => 10 * 1024 * 1024; 32 | 33 | // limit of invitee list to fetch at once from the server. 34 | static const int inviteeListLimit = 20; 35 | static const int messagesLimit = 20; 36 | static const int cachedMessagesLimit = 50; 37 | static const int cachedChatsLimit = 30; 38 | static const int chatsLimit = 20; 39 | static const int LastFetchedchatsLimit = 30; 40 | static const int LastFetchedMessagesLimit = 500; 41 | static const int inviterListLimit = 20; 42 | 43 | // Topic name for notifications 44 | static const pureTopic = "pureNotifications"; 45 | 46 | // Regular Expression 47 | // -------------------------------------------------------------------------- 48 | static final String phoneRegExp = 49 | r"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$"; 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils/navigate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void push( 4 | {required BuildContext context, 5 | bool rootNavigator = false, 6 | required Widget page}) { 7 | Navigator.of(context, rootNavigator: rootNavigator).push( 8 | MaterialPageRoute(builder: (context) => page), 9 | ); 10 | } 11 | 12 | void pushNamed( 13 | {required BuildContext context, 14 | bool rootNavigator = false, 15 | required String page}) { 16 | Navigator.of(context, rootNavigator: rootNavigator).pushNamed(page); 17 | } 18 | 19 | void pushReplacementNamed( 20 | {required BuildContext context, 21 | bool rootNavigator = false, 22 | required String page}) { 23 | Navigator.of(context, rootNavigator: rootNavigator) 24 | .pushReplacementNamed(page); 25 | } 26 | 27 | void pushReplacement({required BuildContext context, required Widget page}) { 28 | Navigator.of(context).pushReplacement( 29 | MaterialPageRoute(builder: (context) => page)); 30 | } 31 | 32 | void pushAndRemoveUntil( 33 | {required BuildContext context, 34 | bool rootNavigator = false, 35 | required Widget page}) { 36 | Navigator.of(context, rootNavigator: rootNavigator).pushAndRemoveUntil( 37 | MaterialPageRoute(builder: (context) => page), 38 | (Route route) => false, 39 | ); 40 | } 41 | 42 | void pushNamedAndRemoveUntil( 43 | {required BuildContext context, 44 | required String page, 45 | bool rootNavigator = false}) { 46 | Navigator.of(context, rootNavigator: rootNavigator) 47 | .pushNamedAndRemoveUntil(page, (route) => false); 48 | } 49 | 50 | void pop({required BuildContext context, bool rootNavigator = false}) { 51 | Navigator.of(context, rootNavigator: rootNavigator).pop(); 52 | } 53 | -------------------------------------------------------------------------------- /lib/utils/palette.dart: -------------------------------------------------------------------------------- 1 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class Palette { 5 | // Fonts 6 | 7 | static const String migraFontFamily = "Migra"; 8 | static const String sanFontFamily = "PublicSans"; 9 | 10 | // primaryColor 11 | static const Color tintColor = const Color(0xFFE1A043); 12 | static const Color greenColor = const Color(0xFF36CC45); 13 | 14 | /// color for text theme 15 | static const Color whiteColor = Color(0xFFEBFAFF); 16 | static const Color surfaceColor = Color(0xFFF7F7F7); 17 | static const Color titleColor = Color(0xFF07455B); 18 | 19 | static final lightTheme = FlexThemeData.light( 20 | fontFamily: "PublicSans", 21 | scaffoldBackground: const Color(0xFFFFFFFF), 22 | appBarBackground: const Color(0xFFFFFFFF), 23 | surface: const Color(0xFFFFFFFF), 24 | dialogBackground: const Color(0xFFF0F0F0), 25 | colors: FlexSchemeColor.from( 26 | primary: Palette.tintColor, 27 | primaryVariant: const Color(0xFF000000), 28 | secondary: const Color(0xFFF2F3F5), 29 | secondaryVariant: const Color(0xFF71747A), 30 | ), 31 | ); 32 | 33 | static final darkTheme = FlexThemeData.dark( 34 | fontFamily: "PublicSans", 35 | scaffoldBackground: const Color(0xFF0A0A0A), 36 | appBarBackground: const Color(0xFF0A0A0A), 37 | surface: const Color(0xFF0A0A0A), 38 | dialogBackground: const Color(0xFF242424), 39 | colors: FlexSchemeColor.from( 40 | primary: Palette.tintColor, 41 | primaryVariant: const Color(0xFFFFFFFF), 42 | secondary: const Color(0xFF1A1A1A), 43 | secondaryVariant: const Color(0xFF80807E), 44 | ), 45 | ); 46 | 47 | static TextStyle appBarStyle({bool isLightMode = true}) { 48 | return TextStyle( 49 | fontSize: 24.0, 50 | fontWeight: FontWeight.bold, 51 | fontFamily: "Migra", 52 | color: isLightMode ? Colors.black : Colors.white, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/utils/request_messages.dart: -------------------------------------------------------------------------------- 1 | class ErrorMessages { 2 | // Setter 3 | static String connectionMessage = 'Poor internet connection'; 4 | static String internetconnectionMessage = 5 | 'Please check your internet connection'; 6 | 7 | static String timeoutMessage = 'Connection timeout. Please try again'; 8 | static String generalMessage = 'Oops! something went wrong'; 9 | static String generalMessage2 = 'The operation couldn’t be completed'; 10 | } 11 | 12 | class SuccessMessages { 13 | // Setter 14 | static String signUpMessage = 'Account created successfully'; 15 | static String profileUpdateMessage = 'Profile changes saved'; 16 | } 17 | 18 | class Messages { 19 | static String cameraPermissionDesc = 'Pure requires access to the Camera' 20 | ' to enable its use to take a photo. You can grant this permission ' 21 | 'within the App Settings'; 22 | 23 | static String photosPermissionDesc = 'Pure requires access to the Photo' 24 | ' Library. You can grant this permission within the App Settings'; 25 | } 26 | -------------------------------------------------------------------------------- /lib/utils/true_time.dart: -------------------------------------------------------------------------------- 1 | import 'package:ntp/ntp.dart'; 2 | 3 | import '../repositories/local_storage.dart'; 4 | 5 | class TrueTime { 6 | factory TrueTime() => _instance; 7 | 8 | TrueTime._internal(); 9 | 10 | static final TrueTime _instance = TrueTime._internal(); 11 | static const _trueTime = "TrueTimePref"; 12 | static final _localStorage = LocalStorageImpl(); 13 | 14 | static int _offset = 0; 15 | 16 | static DateTime now() => DateTime.now().add(Duration(milliseconds: _offset)); 17 | 18 | static Future initialize() async { 19 | _offset = await _localStorage.getIntData(_trueTime) ?? 0; 20 | try { 21 | _offset = await NTP.getNtpOffset().timeout(Duration(seconds: 10)); 22 | _localStorage.saveIntData(_trueTime, _offset); 23 | } catch (e) {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/utils/validators.dart: -------------------------------------------------------------------------------- 1 | import 'package:pure/utils/global_utils.dart'; 2 | import 'package:email_validator/email_validator.dart'; 3 | 4 | class Validators { 5 | static String? Function(String?) validateInput({String? error}) { 6 | return (String? value) { 7 | if (value == null || value.isEmpty) { 8 | return error ?? 'Field is required'; 9 | } 10 | return null; 11 | }; 12 | } 13 | 14 | static String? Function(String?) validatePassword() { 15 | return (String? value) { 16 | const invalidLength = 5; 17 | if (value == null || value.length <= invalidLength) { 18 | return 'Password must be at least 6 characters long'; 19 | } 20 | return null; 21 | }; 22 | } 23 | 24 | static String? Function(String?) validatePassword2() { 25 | return (String? value) { 26 | if (value == null || value.isEmpty) { 27 | return 'Password is required'; 28 | } 29 | return null; 30 | }; 31 | } 32 | 33 | static String? Function(String?) validateEmail() { 34 | return (String? value) { 35 | if (!EmailValidator.validate(value!)) { 36 | return 'Please enter a valid email address'; 37 | } 38 | return null; 39 | }; 40 | } 41 | 42 | static bool validateEmail2(String value) { 43 | return EmailValidator.validate(value); 44 | } 45 | 46 | static String? Function(String?) validatePhone() { 47 | return (String? value) { 48 | final RegExp regExp = new RegExp(GlobalUtils.phoneRegExp); 49 | if (!regExp.hasMatch(value!)) { 50 | return 'Not a valid phone no'; 51 | } 52 | return null; 53 | }; 54 | } 55 | 56 | // checks if the current password is the same with the confirm password 57 | static String? confirmPassword(String password, String confirmPassword) { 58 | if (password != confirmPassword) { 59 | return 'Passwords do not match'; 60 | } 61 | return null; 62 | } 63 | 64 | // slip or mooring number validator 65 | static String? Function(String?) validateSlipOrMooring() { 66 | return (String? value) { 67 | final RegExp regExp = RegExp(r'\d'); 68 | if (!regExp.hasMatch(value!)) { 69 | return 'Enter a valid slip or mooring number'; 70 | } 71 | return null; 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/views/screens/authentication/reset_password_success_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | 5 | import '../../widgets/custom_button.dart'; 6 | import 'widgets/intro_section.dart'; 7 | 8 | class ResetPasswordSuccessScreen extends StatelessWidget { 9 | const ResetPasswordSuccessScreen({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final textTheme = Theme.of(context).textTheme; 14 | 15 | return Scaffold( 16 | appBar: AppBar(), 17 | body: Padding( 18 | padding: const EdgeInsets.symmetric(horizontal: 28), 19 | child: Column( 20 | children: [ 21 | SizedBox(height: 1.sh * 0.25, child: const IntroSection()), 22 | SizedBox(height: 1.sh * 0.05), 23 | Text( 24 | 'We sent a reset password link to your mailbox!', 25 | textAlign: TextAlign.center, 26 | style: textTheme.headline6!.copyWith( 27 | fontWeight: FontWeight.bold, 28 | height: 1.5, 29 | ), 30 | ), 31 | const SizedBox(height: 20), 32 | Text( 33 | 'Check your mailbox and set up a new password to log into your ' 34 | 'Pure account', 35 | textAlign: TextAlign.center, 36 | style: textTheme.subtitle1!.copyWith( 37 | fontWeight: FontWeight.w600, 38 | height: 1.5, 39 | ), 40 | ), 41 | const SizedBox(height: 80), 42 | CustomButton( 43 | title: 'GO BACK TO LOGIN', 44 | width: 1.sw * 0.5, 45 | onPressed: () => GoRouter.of(context).pop(context), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/views/screens/authentication/widgets/auth_bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../../../blocs/bloc.dart'; 7 | import '../../../../repositories/connection.dart'; 8 | import '../../../../repositories/local_storage.dart'; 9 | import '../../../../services/auth_service.dart'; 10 | import '../../../../services/user_service.dart'; 11 | 12 | class AuthBlocProvider extends StatelessWidget { 13 | const AuthBlocProvider({Key? key, required this.child}) : super(key: key); 14 | final Widget child; 15 | 16 | static final connection = ConnectionRepoImpl(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return BlocProvider( 21 | create: (_) => AuthUserCubit( 22 | AuthServiceImpl(auth: FirebaseAuth.instance, connection: connection), 23 | UserServiceImpl( 24 | firestore: FirebaseFirestore.instance, 25 | localStorage: LocalStorageImpl(), 26 | connection: ConnectionRepoImpl(), 27 | ), 28 | ), 29 | child: child, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/views/screens/authentication/widgets/intro_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../utils/palette.dart'; 4 | 5 | class IntroSection extends StatelessWidget { 6 | final String? title; 7 | final double? fontSize; 8 | const IntroSection({Key? key, this.title, this.fontSize}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Center( 13 | child: Text( 14 | title ?? "Pure", 15 | textAlign: TextAlign.center, 16 | style: TextStyle( 17 | fontSize: fontSize ?? 64, 18 | fontFamily: Palette.migraFontFamily, 19 | fontWeight: FontWeight.bold, 20 | ), 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/views/screens/authentication/widgets/social_signin_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../blocs/bloc.dart'; 5 | import '../../../../utils/image_utils.dart'; 6 | 7 | class GoogleSignInButton extends StatelessWidget { 8 | const GoogleSignInButton({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return SizedBox( 13 | child: MaterialButton( 14 | height: 54, 15 | elevation: 5, 16 | color: Theme.of(context).colorScheme.secondary, 17 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), 18 | onPressed: () => 19 | BlocProvider.of(context).signInWithGoogle(), 20 | child: Row( 21 | mainAxisAlignment: MainAxisAlignment.center, 22 | children: [ 23 | Image.asset( 24 | ImageUtils.google, 25 | height: 24, 26 | width: 24, 27 | color: Theme.of(context).colorScheme.primaryVariant, 28 | ), 29 | const SizedBox(width: 16), 30 | Text( 31 | 'Continue with Google', 32 | textAlign: TextAlign.center, 33 | style: TextStyle( 34 | fontSize: 18, 35 | fontWeight: FontWeight.w600, 36 | color: Theme.of(context).colorScheme.primaryVariant, 37 | ), 38 | ), 39 | ], 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | 46 | class AppleSignInButton extends StatelessWidget { 47 | const AppleSignInButton({Key? key}) : super(key: key); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return SizedBox( 52 | child: MaterialButton( 53 | height: 54, 54 | elevation: 5, 55 | color: Theme.of(context).colorScheme.secondaryVariant, 56 | shape: RoundedRectangleBorder( 57 | borderRadius: BorderRadius.circular(10), 58 | ), 59 | onPressed: () => 60 | BlocProvider.of(context).signInWithApple(), 61 | child: Row( 62 | mainAxisAlignment: MainAxisAlignment.center, 63 | children: [ 64 | Image.asset( 65 | ImageUtils.apple, 66 | height: 24, 67 | width: 24, 68 | color: Theme.of(context).colorScheme.surface, 69 | ), 70 | const SizedBox(width: 16), 71 | Text( 72 | 'Continue with Apple', 73 | textAlign: TextAlign.center, 74 | style: TextStyle( 75 | fontSize: 18, 76 | fontWeight: FontWeight.w600, 77 | color: Theme.of(context).colorScheme.surface, 78 | ), 79 | ), 80 | ], 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/views/screens/chats/chat_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../blocs/bloc.dart'; 5 | import '../../../model/pure_user_model.dart'; 6 | import '../../../services/chat/chat_service.dart'; 7 | import '../../../services/search_service.dart'; 8 | import '../../../utils/navigate.dart'; 9 | import 'group/search_friend_chat.dart'; 10 | import 'widget/chat_list_widget.dart'; 11 | 12 | class ChatScreen extends StatefulWidget { 13 | const ChatScreen({Key? key}) : super(key: key); 14 | 15 | @override 16 | State createState() => _ChatScreenState(); 17 | } 18 | 19 | class _ChatScreenState extends State { 20 | @override 21 | void initState() { 22 | super.initState(); 23 | context.read().fetchChats(CurrentUser.currentUserId); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return BlocProvider( 29 | create: (_) => LoadMoreChatsCubit(ChatServiceImp()), 30 | child: Scaffold( 31 | appBar: AppBar( 32 | elevation: 1, 33 | title: const Text('Chats'), 34 | actions: [ 35 | TextButton( 36 | onPressed: () => pushToCreateNewGroupScreen(), 37 | child: Text( 38 | "New Group", 39 | style: TextStyle( 40 | fontSize: 17.0, 41 | fontWeight: FontWeight.w500, 42 | letterSpacing: 0.05, 43 | ), 44 | ), 45 | ), 46 | ], 47 | ), 48 | body: ChatList(), 49 | ), 50 | ); 51 | } 52 | 53 | Future pushToCreateNewGroupScreen() async { 54 | push( 55 | context: context, 56 | rootNavigator: true, 57 | page: MultiBlocProvider( 58 | providers: [ 59 | BlocProvider(create: (_) => GroupCubit(ChatServiceImp())), 60 | BlocProvider(create: (_) => SearchFriendBloc(SearchServiceImpl())), 61 | ], 62 | child: SearchFriendChat(), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/views/screens/chats/messages/group_chat_message_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../blocs/bloc.dart'; 5 | import '../../../../model/chat/chat_model.dart'; 6 | import '../../../../model/pure_user_model.dart'; 7 | import '../../../../repositories/push_notification.dart'; 8 | import '../../../../services/chat/message_service.dart'; 9 | import 'widgets/chat_app_bar.dart'; 10 | import 'widgets/message_screen_widget.dart'; 11 | 12 | class GroupChatMessageScreen extends StatefulWidget { 13 | final ChatModel chatModel; 14 | const GroupChatMessageScreen({Key? key, required this.chatModel}) 15 | : super(key: key); 16 | 17 | @override 18 | State createState() => _GroupChatMessageScreenState(); 19 | } 20 | 21 | class _GroupChatMessageScreenState extends State { 22 | @override 23 | void initState() { 24 | super.initState(); 25 | // subscribe to notifification from this chat messages 26 | PushNotificationImpl.subscribeToTopic(widget.chatModel.chatId); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | resizeToAvoidBottomInset: false, 33 | appBar: AppBar( 34 | leadingWidth: 40.0, 35 | elevation: 1.0, 36 | leading: Padding( 37 | padding: const EdgeInsets.only(left: 8.0), 38 | child: BackButton( 39 | color: Theme.of(context).colorScheme.primaryVariant, 40 | ), 41 | ), 42 | title: GroupMessageAppBarTitle(chat: widget.chatModel), 43 | ), 44 | body: MultiBlocProvider( 45 | providers: [ 46 | BlocProvider( 47 | lazy: false, 48 | create: (_) => MessageCubit(MessageServiceImp()) 49 | ..fetchMessages( 50 | widget.chatModel.chatId, CurrentUser.currentUserId), 51 | ), 52 | BlocProvider(create: (_) => NewMessagesCubit(MessageServiceImp())), 53 | BlocProvider( 54 | create: (_) => LoadMoreMessageCubit(MessageServiceImp()), 55 | ), 56 | ], 57 | child: MessageBody(chatId: widget.chatModel.chatId, isGroupChat: true), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/views/screens/chats/messages/messages_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../blocs/bloc.dart'; 5 | import '../../../../model/pure_user_model.dart'; 6 | import '../../../../model/route/message_route.dart'; 7 | import '../../../../repositories/push_notification.dart'; 8 | import '../../../../services/chat/message_service.dart'; 9 | import '../../../../services/user_service.dart'; 10 | import 'widgets/chat_app_bar.dart'; 11 | import 'widgets/message_screen_widget.dart'; 12 | 13 | class MessagesScreen extends StatefulWidget { 14 | final MessageRoute msgRoute; 15 | 16 | const MessagesScreen({Key? key, required this.msgRoute}) : super(key: key); 17 | 18 | @override 19 | State createState() => _MessagesScreenState(); 20 | } 21 | 22 | class _MessagesScreenState extends State { 23 | final messageServiceImpl = MessageServiceImp(); 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | // subscribe to notifification from this chat messages 29 | PushNotificationImpl.subscribeToTopic(widget.msgRoute.chatId); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar( 36 | leadingWidth: 40.0, 37 | elevation: 1.0, 38 | leading: Padding( 39 | padding: const EdgeInsets.only(left: 8.0), 40 | child: BackButton( 41 | color: Theme.of(context).colorScheme.primaryVariant, 42 | ), 43 | ), 44 | title: MessageAppBarTitle( 45 | chatId: widget.msgRoute.chatId, 46 | receipient: widget.msgRoute.receipient, 47 | hasPresenceActivated: widget.msgRoute.hasPresenceActivated, 48 | ), 49 | ), 50 | body: MultiBlocProvider( 51 | providers: [ 52 | BlocProvider( 53 | lazy: false, 54 | create: (_) => MessageCubit(messageServiceImpl) 55 | ..fetchMessages( 56 | widget.msgRoute.chatId, CurrentUser.currentUserId), 57 | ), 58 | BlocProvider(create: (_) => NewMessagesCubit(messageServiceImpl)), 59 | BlocProvider(create: (_) => LoadMoreMessageCubit(messageServiceImpl)), 60 | if (!widget.msgRoute.hasPresenceActivated) 61 | BlocProvider( 62 | create: (_) => UserPresenceCubit(UserServiceImpl()) 63 | ..getUserPresence(widget.msgRoute.receipient.id), 64 | ), 65 | ], 66 | child: MessageBody( 67 | chatId: widget.msgRoute.chatId, 68 | receipientName: widget.msgRoute.receipient.firstName, 69 | ), 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/views/screens/chats/messages/widgets/load_more_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../../blocs/bloc.dart'; 5 | import '../../../../widgets/failure_widget.dart'; 6 | import '../../../../widgets/progress_indicator.dart'; 7 | 8 | class LoadMoreMessagesWidget extends StatelessWidget { 9 | final void Function()? onTap; 10 | const LoadMoreMessagesWidget({Key? key, required this.onTap}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Center( 16 | child: Padding( 17 | padding: const EdgeInsets.all(16.0), 18 | child: BlocBuilder( 19 | builder: (context, state) { 20 | if (state is LoadingMessages) { 21 | return SizedBox( 22 | width: 20.0, 23 | height: 20.0, 24 | child: CustomProgressIndicator(size: 16.0)); 25 | } else if (state is MessagesFailed) { 26 | return LoadMoreErrorWidget( 27 | onTap: onTap, 28 | message: "Failed to load more", 29 | ); 30 | } 31 | return Offstage(); 32 | }, 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/views/screens/chats/messages/widgets/new_message_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../../blocs/bloc.dart'; 5 | 6 | class NewMessageWidget extends StatelessWidget { 7 | final ScrollController controller; 8 | final void Function()? onNewMessagePressed; 9 | const NewMessageWidget( 10 | {Key? key, required this.controller, required this.onNewMessagePressed}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return BlocBuilder( 16 | buildWhen: (prev, current) => current is MessagesLoaded, 17 | builder: (context, state) { 18 | if (messagesNotEmpty(state) && isOutOfMinScroll) 19 | return Align( 20 | alignment: Alignment.bottomCenter, 21 | child: Padding( 22 | padding: const EdgeInsets.only(bottom: 50.0), 23 | child: MaterialButton( 24 | shape: StadiumBorder(), 25 | color: Colors.red, 26 | onPressed: this.onNewMessagePressed, 27 | child: Wrap( 28 | crossAxisAlignment: WrapCrossAlignment.center, 29 | children: [ 30 | Icon(Icons.south, size: 20.0), 31 | Padding( 32 | padding: const EdgeInsets.only(left: 8.0), 33 | child: Text("New message"), 34 | ), 35 | ], 36 | ), 37 | ), 38 | ), 39 | ); 40 | 41 | return Offstage(); 42 | }, 43 | ); 44 | } 45 | 46 | bool messagesNotEmpty(MessageState state) { 47 | return (controller.hasClients && 48 | state is MessagesLoaded && 49 | state.messagesModel.messages.isNotEmpty); 50 | } 51 | 52 | bool get isOutOfMinScroll { 53 | final minScroll = controller.position.minScrollExtent; 54 | final currentScroll = controller.offset; 55 | final isNotView = currentScroll - minScroll >= 300; 56 | return (isNotView && !controller.position.outOfRange); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/views/screens/chats/messages/widgets/tagged_user_profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../../model/pure_user_model.dart'; 4 | import '../../../../widgets/avatar.dart'; 5 | 6 | class TaggedUsers extends StatelessWidget { 7 | final List members; 8 | final Function(String) onUserPressed; 9 | const TaggedUsers( 10 | {Key? key, required this.members, required this.onUserPressed}) 11 | : super(key: key); 12 | 13 | final _style = const TextStyle( 14 | fontSize: 12, 15 | fontWeight: FontWeight.w600, 16 | letterSpacing: 0.05, 17 | ); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return DraggableScrollableSheet( 22 | minChildSize: 0.4, 23 | initialChildSize: 0.4, 24 | builder: (context, scrollController) { 25 | return DecoratedBox( 26 | decoration: BoxDecoration( 27 | color: Theme.of(context).dialogBackgroundColor, 28 | borderRadius: BorderRadius.vertical(top: Radius.circular(20)), 29 | ), 30 | child: ListView.separated( 31 | itemCount: members.length, 32 | controller: scrollController, 33 | separatorBuilder: (_, __) => Divider(height: 0), 34 | itemBuilder: (context, index) { 35 | final member = members[index]; 36 | return ListTile( 37 | leading: Avartar2(imageURL: member.photoURL), 38 | title: RichText( 39 | maxLines: 1, 40 | text: TextSpan( 41 | children: [ 42 | TextSpan( 43 | text: member.fullName, 44 | style: _style.copyWith( 45 | color: Theme.of(context).colorScheme.primaryVariant, 46 | ), 47 | ), 48 | TextSpan( 49 | text: " ${member.getAtUsername}", 50 | style: _style.copyWith( 51 | fontSize: 14.0, 52 | fontWeight: FontWeight.w500, 53 | color: Theme.of(context).colorScheme.secondaryVariant, 54 | ), 55 | ), 56 | ], 57 | ), 58 | ), 59 | onTap: () => onUserPressed.call("${member.username} "), 60 | ); 61 | }, 62 | ), 63 | ); 64 | }, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/views/screens/chats/messages/widgets/tagged_user_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../../blocs/bloc.dart'; 5 | import '../../../../../model/pure_user_model.dart'; 6 | import '../../../../../utils/chat_utils.dart'; 7 | import 'tagged_user_profile.dart'; 8 | 9 | class TaggedUserSheet extends StatelessWidget { 10 | final ValueNotifier userTaggingNotifier; 11 | final TextEditingController controller; 12 | const TaggedUserSheet({ 13 | Key? key, 14 | required this.controller, 15 | required this.userTaggingNotifier, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Align( 21 | alignment: Alignment.bottomCenter, 22 | child: BlocBuilder( 23 | builder: (context, state) { 24 | if (state is GroupMembers) { 25 | return ValueListenableBuilder( 26 | valueListenable: userTaggingNotifier, 27 | builder: (context, value, _) { 28 | if (value == null) return Offstage(); 29 | final members = state.members.toList(); 30 | final users = getTaggedUsers(members, value); 31 | if (users.isEmpty) 32 | return Offstage(); 33 | else 34 | return TaggedUsers( 35 | members: users, 36 | onUserPressed: (username) => 37 | onTaggedUserSelected(value, username), 38 | ); 39 | }, 40 | ); 41 | } 42 | return Offstage(); 43 | }, 44 | ), 45 | ); 46 | } 47 | 48 | List getTaggedUsers(List members, String value) { 49 | members.removeWhere((element) => element.id == CurrentUser.currentUserId); 50 | return members.toList().where((member) { 51 | return member.username.toLowerCase().contains(value.toLowerCase()); 52 | }).toList(); 53 | } 54 | 55 | void onTaggedUserSelected(String input, String selected) { 56 | replaceUserTagOnSelected(controller, input, selected); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/views/screens/chats/widget/load_more_chats_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../blocs/bloc.dart'; 5 | import '../../../widgets/failure_widget.dart'; 6 | import '../../../widgets/progress_indicator.dart'; 7 | 8 | class LoadMoreChatsWidget extends StatelessWidget { 9 | final void Function()? onTap; 10 | const LoadMoreChatsWidget({Key? key, required this.onTap}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Center( 15 | child: Padding( 16 | padding: const EdgeInsets.all(16.0), 17 | child: BlocBuilder( 18 | builder: (context, state) { 19 | if (state is LoadingChats) { 20 | return SizedBox( 21 | width: 20.0, 22 | height: 20.0, 23 | child: CustomProgressIndicator(size: 16.0)); 24 | } else if (state is ChatsFailed) { 25 | return LoadMoreErrorWidget( 26 | onTap: onTap, 27 | message: "Failed to load more", 28 | ); 29 | } 30 | return Offstage(); 31 | }, 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/views/screens/chats/widget/unread_message_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../blocs/chats/chats/unread_message_cubit.dart'; 5 | import '../../../../services/chat/chat_service.dart'; 6 | 7 | class UnreadMessageProvider extends StatelessWidget { 8 | final Widget child; 9 | const UnreadMessageProvider({Key? key, required this.child}) 10 | : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return BlocProvider( 15 | lazy: false, 16 | create: (_) => UnreadMessageCubit(ChatServiceImp()), 17 | child: child, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/views/screens/connections/search/widgets/search_small.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../../../../blocs/bloc.dart'; 5 | import '../../../../widgets/progress_indicator.dart'; 6 | import 'search_user_profile.dart'; 7 | 8 | class SearchSmallConnection extends StatelessWidget { 9 | final Function()? onSearchAllResultTapped; 10 | const SearchSmallConnection({Key? key, this.onSearchAllResultTapped}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Padding( 16 | padding: EdgeInsets.symmetric(vertical: 16.0), 17 | child: SingleChildScrollView( 18 | child: BlocBuilder( 19 | builder: (context, state) { 20 | if (state is SearchConnEmpty) return Offstage(); 21 | 22 | if (state is SearchConnSuccess) { 23 | final users = state.users; 24 | return Column( 25 | children: [ 26 | // show user profile 27 | ListView.builder( 28 | padding: 29 | EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), 30 | itemCount: users.length, 31 | shrinkWrap: true, 32 | physics: NeverScrollableScrollPhysics(), 33 | itemBuilder: (context, index) { 34 | final user = users[index]; 35 | return ShortUserProfile(user: user); 36 | }, 37 | ), 38 | Divider(height: 0.0), 39 | // See all results 40 | TextButton( 41 | onPressed: onSearchAllResultTapped, 42 | child: Text( 43 | "See all results", 44 | style: const TextStyle( 45 | fontSize: 16.5, 46 | letterSpacing: 0.20, 47 | fontWeight: FontWeight.w500, 48 | ), 49 | ), 50 | ) 51 | ], 52 | ); 53 | } else if (state is SearchConnFailure) { 54 | return Center( 55 | child: Text( 56 | state.message, 57 | style: const TextStyle( 58 | fontSize: 15.0, 59 | fontWeight: FontWeight.w500, 60 | ), 61 | ), 62 | ); 63 | } 64 | 65 | return Center(child: CustomProgressIndicator(size: 20.0)); 66 | }, 67 | ), 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/views/screens/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../utils/palette.dart'; 4 | import '../../widgets/page_transition.dart'; 5 | import 'posts/create_post_screen.dart'; 6 | 7 | class HomePage extends StatefulWidget { 8 | const HomePage({Key? key}) : super(key: key); 9 | 10 | @override 11 | State createState() => _HomePageState(); 12 | } 13 | 14 | class _HomePageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar( 19 | elevation: 1, 20 | title: Text("Home"), 21 | ), 22 | body: Center( 23 | child: const Text( 24 | "Coming Soon", 25 | style: TextStyle(fontSize: 24), 26 | ), 27 | ), 28 | floatingActionButton: FloatingActionButton( 29 | backgroundColor: Palette.tintColor, 30 | child: Icon( 31 | Icons.post_add, 32 | color: Theme.of(context).colorScheme.secondary, 33 | ), 34 | onPressed: () => _onCreatePostTapped(), 35 | ), 36 | ); 37 | } 38 | 39 | void _onCreatePostTapped() { 40 | Navigator.of(context, rootNavigator: true).push( 41 | PageTransition( 42 | child: CreatePostScreen(), 43 | type: PageTransitionType.bottomToTop, 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/views/screens/notifications/notifications_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'widgets/no_notification_widget.dart'; 4 | 5 | class NotificationsScreen extends StatelessWidget { 6 | const NotificationsScreen({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar(elevation: 1, title: const Text('Notifications')), 12 | body: NoNotificationWidget(), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/views/screens/notifications/widgets/no_notification_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../utils/palette.dart'; 4 | import '../../../../utils/image_utils.dart'; 5 | 6 | class NoNotificationWidget extends StatelessWidget { 7 | const NoNotificationWidget({Key? key}) : super(key: key); 8 | 9 | final _style = const TextStyle( 10 | fontSize: 16.0, 11 | fontWeight: FontWeight.w600, 12 | letterSpacing: 0.15, 13 | ); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Center( 18 | child: Column( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | // images 22 | Image.asset( 23 | Theme.of(context).brightness == Brightness.light 24 | ? ImageUtils.notifyLight 25 | : ImageUtils.notifyDark, 26 | ), 27 | 28 | const SizedBox(height: 20.0), 29 | Text( 30 | "Nothing here yet...", 31 | textAlign: TextAlign.center, 32 | style: _style.copyWith( 33 | fontSize: 24, 34 | fontFamily: Palette.migraFontFamily, 35 | fontWeight: FontWeight.w600, 36 | ), 37 | ), 38 | const SizedBox(height: 4.0), 39 | Text( 40 | "You’ll be notifed once you get an update", 41 | textAlign: TextAlign.center, 42 | style: _style.copyWith( 43 | fontSize: 14, 44 | fontWeight: FontWeight.w400, 45 | color: Theme.of(context).colorScheme.secondaryVariant, 46 | ), 47 | ), 48 | ], 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/views/screens/onboarding/onboading_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import '../../../utils/palette.dart'; 5 | 6 | class OnBoardingBody extends StatelessWidget { 7 | const OnBoardingBody({ 8 | Key? key, 9 | required this.image, 10 | required this.title, 11 | required this.subTitle, 12 | }) : super(key: key); 13 | 14 | final String image; 15 | final String title; 16 | final String subTitle; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Column( 21 | children: [ 22 | Image.asset(image), 23 | SizedBox(height: 1.sh * 0.07), 24 | Text( 25 | title, 26 | textAlign: TextAlign.center, 27 | style: TextStyle( 28 | fontSize: 32, 29 | fontFamily: Palette.migraFontFamily, 30 | fontWeight: FontWeight.w500, 31 | ), 32 | ), 33 | const SizedBox(height: 8), 34 | SizedBox( 35 | width: 1.sw * 0.8, 36 | child: Text( 37 | subTitle, 38 | textAlign: TextAlign.center, 39 | style: TextStyle( 40 | fontSize: 16, 41 | fontWeight: FontWeight.w400, 42 | height: 1.3, 43 | letterSpacing: 0.5, 44 | color: Theme.of(context).colorScheme.secondaryVariant, 45 | ), 46 | ), 47 | ), 48 | const Spacer(flex: 2), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/views/screens/settings/widgets/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../model/pure_user_model.dart'; 4 | import '../../../widgets/avatar.dart'; 5 | import '../../../widgets/page_transition.dart'; 6 | import '../../photo_view_screen.dart'; 7 | 8 | class MyProfileSection extends StatelessWidget { 9 | final PureUser user; 10 | const MyProfileSection({Key? key, required this.user}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Padding( 15 | padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), 16 | child: Row( 17 | children: [ 18 | Avartar(size: 48.0, imageURL: user.photoURL), 19 | const SizedBox(width: 16.0), 20 | Expanded( 21 | child: Column( 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | crossAxisAlignment: CrossAxisAlignment.stretch, 24 | children: [ 25 | Text( 26 | user.fullName, 27 | style: const TextStyle( 28 | fontSize: 22.0, 29 | fontWeight: FontWeight.w600, 30 | ), 31 | ), 32 | const SizedBox(height: 4.0), 33 | Text( 34 | "@${user.username}", 35 | style: TextStyle( 36 | fontSize: 16.0, 37 | fontWeight: FontWeight.w400, 38 | color: Theme.of(context).colorScheme.secondaryVariant, 39 | ), 40 | ), 41 | ], 42 | ), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | 50 | class ProfileSection extends StatelessWidget { 51 | final PureUser user; 52 | const ProfileSection({Key? key, required this.user}) : super(key: key); 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Padding( 57 | padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), 58 | child: Column( 59 | children: [ 60 | InkWell( 61 | borderRadius: BorderRadius.circular(500), 62 | child: Avartar(size: 48.0, imageURL: user.photoURL), 63 | onTap: () => viewProfilePhoto(context), 64 | ), 65 | const SizedBox(height: 16.0), 66 | Text( 67 | user.fullName, 68 | style: const TextStyle( 69 | fontSize: 22.0, 70 | fontWeight: FontWeight.w600, 71 | ), 72 | ), 73 | ], 74 | ), 75 | ); 76 | } 77 | 78 | void viewProfilePhoto(BuildContext context) { 79 | if (user.photoURL.isNotEmpty) { 80 | Navigator.of(context).push( 81 | PageTransition( 82 | child: ViewProfilePhoto(imageURL: user.photoURL), 83 | type: PageTransitionType.bottomToTop, 84 | ), 85 | ); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/views/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import 'package:go_router/go_router.dart'; 6 | 7 | import '../../blocs/bloc.dart'; 8 | import '../../model/pure_user_model.dart'; 9 | import '../../utils/image_utils.dart'; 10 | 11 | class SplashScreen extends StatefulWidget { 12 | const SplashScreen({Key? key}) : super(key: key); 13 | 14 | @override 15 | _SplashScreenState createState() => _SplashScreenState(); 16 | } 17 | 18 | class _SplashScreenState extends State { 19 | @override 20 | void initState() { 21 | super.initState(); 22 | initialize(); 23 | } 24 | 25 | Future initialize() async { 26 | await context.read().isUserBoarded(); 27 | final onboardingState = context.read().state; 28 | if (onboardingState is NotBoarded) { 29 | context.goNamed("board"); 30 | } else { 31 | await context.read().authenticateUser(); 32 | final authState = context.read().state; 33 | if (authState is UnAuthenticated) { 34 | context.goNamed("social"); 35 | } else if (authState is Authenticated) { 36 | CurrentUser.setUserId = authState.user.id; 37 | context.go("/home/0"); 38 | } 39 | } 40 | initializeLoadingAttributes(context); 41 | } 42 | 43 | void initializeLoadingAttributes(BuildContext context) { 44 | EasyLoading.instance 45 | ..indicatorType = EasyLoadingIndicatorType.fadingCircle 46 | ..loadingStyle = EasyLoadingStyle.custom 47 | ..indicatorColor = Colors.white 48 | ..textColor = Colors.white 49 | ..userInteractions = false 50 | ..backgroundColor = Theme.of(context).primaryColor; 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return Scaffold( 56 | body: Center( 57 | child: Image.asset( 58 | ImageUtils.logo, 59 | fit: BoxFit.contain, 60 | height: 45.h, 61 | width: 150.w, 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/views/widgets/avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../blocs/bloc.dart'; 6 | import '../../utils/image_utils.dart'; 7 | 8 | class Avartar extends StatelessWidget { 9 | final String imageURL; 10 | final String? localURL; 11 | final double size; 12 | final double? ringSize; 13 | final bool hidePresence; 14 | const Avartar({ 15 | Key? key, 16 | required this.imageURL, 17 | required this.size, 18 | this.localURL, 19 | this.ringSize, 20 | this.hidePresence = true, 21 | }) : super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Stack( 26 | children: [ 27 | CircleAvatar( 28 | radius: size, 29 | backgroundColor: Theme.of(context).colorScheme.secondary, 30 | backgroundImage: AssetImage(localURL ?? ImageUtils.user), 31 | foregroundImage: 32 | imageURL.isEmpty ? null : CachedNetworkImageProvider(imageURL), 33 | ), 34 | if (hidePresence == false) 35 | BlocBuilder( 36 | builder: (context, state) { 37 | return Positioned( 38 | bottom: size * 0.05, 39 | right: size * 0.3, 40 | child: AnimatedOpacity( 41 | opacity: 42 | state is UserPresenceSuccess && state.presence.isOnline 43 | ? 1.0 44 | : 0.0, 45 | duration: Duration(milliseconds: 500), 46 | child: CircleAvatar( 47 | radius: size * 0.15, 48 | backgroundColor: Colors.white, 49 | child: CircleAvatar( 50 | radius: size * 0.12, 51 | backgroundColor: Colors.green, 52 | ), 53 | ), 54 | ), 55 | ); 56 | }, 57 | ) 58 | ], 59 | ); 60 | } 61 | } 62 | 63 | class Avartar2 extends StatelessWidget { 64 | final String imageURL; 65 | final String? localURL; 66 | final double? size; 67 | 68 | final bool hidePresence; 69 | const Avartar2({ 70 | Key? key, 71 | required this.imageURL, 72 | this.size, 73 | this.localURL, 74 | this.hidePresence = true, 75 | }) : super(key: key); 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return CircleAvatar( 80 | radius: size, 81 | backgroundColor: Theme.of(context).colorScheme.secondary, 82 | backgroundImage: AssetImage(localURL ?? ImageUtils.user), 83 | foregroundImage: 84 | imageURL.isEmpty ? null : CachedNetworkImageProvider(imageURL), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/views/widgets/custom_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import '../../utils/palette.dart'; 5 | 6 | class CustomOutlinedButton extends StatelessWidget { 7 | const CustomOutlinedButton({ 8 | Key? key, 9 | required this.onPressed, 10 | required this.title, 11 | this.width, 12 | this.height, 13 | this.shape, 14 | this.backgroundColor, 15 | this.side, 16 | this.style, 17 | }) : super(key: key); 18 | final String title; 19 | final double? width; 20 | final double? height; 21 | final OutlinedBorder? shape; 22 | final BorderSide? side; 23 | final Color? backgroundColor; 24 | final TextStyle? style; 25 | final void Function()? onPressed; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return SizedBox( 30 | width: width ?? 1.sw * 0.34, 31 | height: height ?? 45, 32 | child: OutlinedButton( 33 | style: OutlinedButton.styleFrom( 34 | backgroundColor: backgroundColor, 35 | side: side, 36 | shape: shape, 37 | ), 38 | onPressed: onPressed, 39 | child: Text( 40 | title, 41 | style: style ?? 42 | TextStyle( 43 | fontSize: 15.5, 44 | fontWeight: FontWeight.w500, 45 | color: Theme.of(context).colorScheme.primaryVariant, 46 | ), 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | 53 | class CustomButton extends StatelessWidget { 54 | const CustomButton({ 55 | Key? key, 56 | required this.onPressed, 57 | required this.title, 58 | this.width, 59 | this.height, 60 | this.shape, 61 | this.backgroundColor, 62 | this.side, 63 | this.style, 64 | }) : super(key: key); 65 | final String title; 66 | final double? width; 67 | final double? height; 68 | final OutlinedBorder? shape; 69 | final BorderSide? side; 70 | final Color? backgroundColor; 71 | final TextStyle? style; 72 | final void Function()? onPressed; 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return SizedBox( 77 | width: width ?? 1.sw * 0.34, 78 | height: height ?? 45, 79 | child: ElevatedButton( 80 | onPressed: onPressed, 81 | style: ElevatedButton.styleFrom( 82 | primary: backgroundColor ?? Palette.tintColor, 83 | side: side, 84 | shape: shape, 85 | ), 86 | child: Text( 87 | title, 88 | style: style ?? 89 | TextStyle( 90 | fontSize: 15.5, 91 | fontWeight: FontWeight.w500, 92 | color: Theme.of(context).colorScheme.secondary, 93 | ), 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/views/widgets/custom_keep_alive.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomKeepAlive extends StatefulWidget { 4 | final Widget child; 5 | 6 | const CustomKeepAlive({ 7 | required Key key, 8 | required this.child, 9 | }) : super(key: key); 10 | 11 | @override 12 | State createState() => _CustomKeepAliveState(); 13 | } 14 | 15 | class _CustomKeepAliveState extends State 16 | with AutomaticKeepAliveClientMixin { 17 | @override 18 | bool get wantKeepAlive => true; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | super.build(context); 23 | return widget.child; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/views/widgets/custom_multi_bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../blocs/bloc.dart'; 7 | import '../../repositories/connection.dart'; 8 | import '../../repositories/local_storage.dart'; 9 | import '../../services/user_service.dart'; 10 | 11 | class CustomMultiBlocProvider extends StatelessWidget { 12 | const CustomMultiBlocProvider({Key? key, required this.child}) 13 | : super(key: key); 14 | final Widget child; 15 | 16 | static final _localStorage = LocalStorageImpl(); 17 | static final _userService = UserServiceImpl( 18 | firestore: FirebaseFirestore.instance, 19 | localStorage: _localStorage, 20 | connection: ConnectionRepoImpl(), 21 | ); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return MultiBlocProvider( 26 | providers: [ 27 | BlocProvider(create: (_) => OnBoardingCubit(LocalStorageImpl())), 28 | BlocProvider( 29 | create: (_) => 30 | AuthCubit(FirebaseAuth.instance, _localStorage, _userService), 31 | ), 32 | ], 33 | child: child, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/views/widgets/editable_text_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../blocs/bloc.dart'; 5 | import '../../model/pure_user_model.dart'; 6 | 7 | class EditableTextController extends TextEditingController { 8 | EditableTextController({String? text}) : super(text: text); 9 | 10 | final Map patternMatchMap = { 11 | RegExp(r"\B@[a-zA-Z0-9]+\b"): TextStyle( 12 | fontWeight: FontWeight.w500, 13 | color: Colors.blue, 14 | ), 15 | }; 16 | 17 | @override 18 | TextSpan buildTextSpan( 19 | {required BuildContext context, 20 | TextStyle? style, 21 | required bool withComposing}) { 22 | List children = []; 23 | List usernames = []; 24 | 25 | final state = BlocProvider.of(context).state; 26 | if (state is GroupMembers) { 27 | final members = state.members.toList(); 28 | members.removeWhere((element) => element.id == CurrentUser.currentUserId); 29 | usernames = members.map((e) => e.username.toLowerCase()).toList(); 30 | } 31 | 32 | // Validating with REGEX 33 | 34 | text.splitMapJoin( 35 | patternMatchMap.keys.first, 36 | onNonMatch: (String span) { 37 | children.add(TextSpan(text: span, style: style)); 38 | return span.toString(); 39 | }, 40 | onMatch: (Match m) { 41 | if (usernames.contains((m[0]!.split("@")).last.toLowerCase())) { 42 | children.add( 43 | TextSpan(text: m[0], style: patternMatchMap.values.first), 44 | ); 45 | } else { 46 | children.add(TextSpan(text: m[0], style: style)); 47 | } 48 | 49 | return ""; 50 | }, 51 | ); 52 | return TextSpan(style: style, children: children); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/views/widgets/error_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorPage extends StatelessWidget { 4 | final String error; 5 | const ErrorPage({Key? key, required this.error}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | body: Center( 11 | child: Text(error), 12 | ), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/views/widgets/failure_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RefreshFailureWidget extends StatelessWidget { 4 | const RefreshFailureWidget({Key? key, required this.onTap}) : super(key: key); 5 | final void Function()? onTap; 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Row( 10 | mainAxisAlignment: MainAxisAlignment.center, 11 | children: [ 12 | const Text( 13 | 'Oops! something went wrong.', 14 | style: TextStyle( 15 | fontSize: 15.5, 16 | fontWeight: FontWeight.w600, 17 | letterSpacing: 0.4, 18 | ), 19 | ), 20 | TextButton( 21 | onPressed: onTap, 22 | child: const Text( 23 | 'Try again', 24 | style: TextStyle(fontSize: 14.0, color: Colors.red), 25 | ), 26 | ), 27 | ], 28 | ); 29 | } 30 | } 31 | 32 | class LoadMoreErrorWidget extends StatelessWidget { 33 | final void Function()? onTap; 34 | final String message; 35 | const LoadMoreErrorWidget( 36 | {Key? key, required this.onTap, required this.message}) 37 | : super(key: key); 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Align( 42 | alignment: Alignment.center, 43 | child: Padding( 44 | padding: const EdgeInsets.all(16.0), 45 | child: Center( 46 | child: Column( 47 | children: [ 48 | Text( 49 | message, 50 | style: TextStyle( 51 | fontSize: 15.0, 52 | fontWeight: FontWeight.w600, 53 | letterSpacing: 0.5, 54 | ), 55 | ), 56 | TextButton( 57 | onPressed: onTap, 58 | child: Text( 59 | 'Try again', 60 | style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500), 61 | ), 62 | ), 63 | ], 64 | ), 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/views/widgets/grouped_list/group_list_order.dart: -------------------------------------------------------------------------------- 1 | /// Used to define the order of a [GroupedListView]. 2 | enum GroupedListOrder { ASC, DESC } 3 | -------------------------------------------------------------------------------- /lib/views/widgets/message_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:pure/utils/palette.dart'; 4 | 5 | class MessageDisplay extends StatelessWidget { 6 | final String? title; 7 | final String? image; 8 | final String? description; 9 | final String? buttonTitle; 10 | final double? fontSize; 11 | final void Function()? onPressed; 12 | const MessageDisplay({ 13 | Key? key, 14 | this.title, 15 | this.image, 16 | this.description, 17 | this.buttonTitle, 18 | this.onPressed, 19 | this.fontSize, 20 | }) : super(key: key); 21 | 22 | final _style = const TextStyle( 23 | fontSize: 16.0, 24 | fontWeight: FontWeight.w600, 25 | letterSpacing: 0.15, 26 | ); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Center( 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | // images 35 | if (image != null) 36 | Image.asset( 37 | image!, 38 | width: 1.sw * 0.6, 39 | height: 1.sw * 0.6, 40 | ), 41 | const SizedBox(height: 16.0), 42 | Text( 43 | title ?? "No results found", 44 | textAlign: TextAlign.center, 45 | style: _style.copyWith( 46 | fontSize: fontSize ?? 24.0, 47 | fontFamily: Palette.migraFontFamily, 48 | fontWeight: FontWeight.w500, 49 | ), 50 | ), 51 | const SizedBox(height: 8.0), 52 | SizedBox( 53 | width: 1.0.sw * 0.7, 54 | child: Text( 55 | description ?? "Try shortening or rephrasing your search", 56 | textAlign: TextAlign.center, 57 | style: _style.copyWith( 58 | fontSize: 15.0, 59 | fontWeight: FontWeight.w400, 60 | color: Theme.of(context).colorScheme.secondaryVariant, 61 | ), 62 | ), 63 | ), 64 | const SizedBox(height: 8.0), 65 | if (onPressed != null) 66 | OutlinedButton( 67 | style: OutlinedButton.styleFrom(shape: StadiumBorder()), 68 | onPressed: onPressed ?? () => Navigator.of(context).pop(), 69 | child: Text( 70 | buttonTitle ?? "Edit search", 71 | style: _style.copyWith( 72 | fontSize: 17, 73 | color: Palette.tintColor, 74 | fontWeight: FontWeight.w500, 75 | ), 76 | ), 77 | ), 78 | ], 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/views/widgets/nav_bar_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:badges/badges.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import '../../blocs/bloc.dart'; 5 | import '../../utils/image_utils.dart'; 6 | 7 | class NotificationCouterWidget extends StatelessWidget { 8 | const NotificationCouterWidget({Key? key}) : super(key: key); 9 | 10 | Widget build(BuildContext context) { 11 | return Stack( 12 | children: [ 13 | const Padding( 14 | padding: EdgeInsets.symmetric(horizontal: 20.0), 15 | child: ImageIcon(AssetImage(ImageUtils.message)), 16 | ), 17 | Positioned( 18 | top: -2.0, 19 | right: 10.0, 20 | child: BlocBuilder( 21 | builder: (context, state) { 22 | if (state > 0) 23 | return Badge( 24 | badgeColor: Colors.red, 25 | elevation: 0.0, 26 | animationDuration: Duration.zero, 27 | padding: EdgeInsets.all(6.0), 28 | badgeContent: Text( 29 | state.toString(), 30 | style: const TextStyle( 31 | fontSize: 13, 32 | fontWeight: FontWeight.w400, 33 | letterSpacing: 0.05, 34 | color: Colors.white, 35 | ), 36 | ), 37 | ); 38 | return Offstage(); 39 | }, 40 | ), 41 | ) 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/views/widgets/progress_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class CustomProgressIndicator extends StatelessWidget { 7 | const CustomProgressIndicator( 8 | {Key? key, this.defaultColor = false, this.size}) 9 | : super(key: key); 10 | 11 | final bool defaultColor; 12 | final double? size; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final _size = size ?? (Platform.isIOS ? 50.0 : 35.0); 17 | return SizedBox( 18 | width: _size, 19 | height: _size, 20 | child: Platform.isIOS 21 | ? const CupertinoActivityIndicator() 22 | : CircularProgressIndicator( 23 | strokeWidth: _size >= 30 ? 4.0 : 2.8, 24 | valueColor: AlwaysStoppedAnimation( 25 | Theme.of(context).primaryColor, 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | class RefreshLoadingWidget extends StatelessWidget { 33 | final bool isTransparent; 34 | const RefreshLoadingWidget({Key? key, this.isTransparent = false}) 35 | : super(key: key); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Column( 40 | children: [ 41 | Padding( 42 | padding: const EdgeInsets.all(12.0), 43 | child: Row( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | children: [ 46 | SizedBox( 47 | width: 20.0, 48 | height: 20.0, 49 | child: Platform.isIOS 50 | ? CupertinoActivityIndicator() 51 | : CircularProgressIndicator(strokeWidth: 2.5), 52 | ), 53 | const SizedBox(width: 16.0), 54 | Text( 55 | "Loading", 56 | style: TextStyle( 57 | fontSize: 15.0, 58 | fontWeight: FontWeight.w500, 59 | letterSpacing: 0.5, 60 | ), 61 | ), 62 | ], 63 | ), 64 | ), 65 | ], 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/views/widgets/push_notification_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_messaging/firebase_messaging.dart'; 2 | 3 | class NotificationNavigation { 4 | static void updateNotificationScreen(RemoteMessage message) { 5 | // final data = message.data; 6 | } 7 | 8 | // Takes the user to the appropriate screen when notification is tapped 9 | static void navigateToScreenOnMessageOpenApp(RemoteMessage message) { 10 | // final data = message.data; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/views/widgets/textfield_label_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TextFieldLabelWidget extends StatelessWidget { 4 | const TextFieldLabelWidget( 5 | {Key? key, required this.label, this.required = true}) 6 | : super(key: key); 7 | 8 | final String label; 9 | final bool required; 10 | 11 | static final _style = TextStyle( 12 | fontSize: 13.0, 13 | fontWeight: FontWeight.w600, 14 | letterSpacing: 0.20, 15 | height: 1.2, 16 | ); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return RichText( 21 | text: TextSpan( 22 | children: [ 23 | TextSpan(text: label, style: _style), 24 | if (required) 25 | TextSpan( 26 | text: ' *', 27 | style: _style.copyWith(fontSize: 16.0, color: Colors.red), 28 | ), 29 | ], 30 | ), 31 | ); 32 | } 33 | } 34 | 35 | class TextFieldLabelDescWidget extends StatelessWidget { 36 | const TextFieldLabelDescWidget( 37 | {Key? key, required this.label, this.required = true, required this.desc}) 38 | : super(key: key); 39 | 40 | final String label; 41 | final String desc; 42 | final bool required; 43 | 44 | static final _style = TextStyle( 45 | fontSize: 13.0, 46 | fontWeight: FontWeight.w600, 47 | letterSpacing: 0.20, 48 | height: 1.2, 49 | ); 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Column( 54 | crossAxisAlignment: CrossAxisAlignment.stretch, 55 | children: [ 56 | RichText( 57 | text: TextSpan( 58 | children: [ 59 | TextSpan(text: label, style: _style), 60 | if (required) 61 | TextSpan( 62 | text: ' *', 63 | style: _style.copyWith(fontSize: 16.0, color: Colors.red), 64 | ), 65 | ], 66 | ), 67 | ), 68 | const SizedBox(height: 2.0), 69 | Text( 70 | desc, 71 | style: _style.copyWith( 72 | fontWeight: FontWeight.w400, 73 | fontSize: 13.0, 74 | color: Colors.grey.shade700, 75 | letterSpacing: 0.5, 76 | ), 77 | ) 78 | ], 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/views/widgets/user_profile_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../blocs/bloc.dart'; 5 | import '../../services/chat/chat_service.dart'; 6 | import '../../services/user_service.dart'; 7 | 8 | class UserPresenceProvider extends StatelessWidget { 9 | final String userId; 10 | final Widget child; 11 | const UserPresenceProvider( 12 | {Key? key, required this.userId, required this.child}) 13 | : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return BlocProvider( 18 | lazy: false, 19 | create: (context) => 20 | UserPresenceCubit(UserServiceImpl())..getUserPresence(userId), 21 | child: child, 22 | ); 23 | } 24 | } 25 | 26 | class ProfileProvider extends StatelessWidget { 27 | final String userId; 28 | final Widget child; 29 | const ProfileProvider({Key? key, required this.userId, required this.child}) 30 | : super(key: key); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return BlocProvider( 35 | lazy: false, 36 | create: (_) => ProfileCubit(UserServiceImpl())..getProfile(userId), 37 | child: child, 38 | ); 39 | } 40 | } 41 | 42 | class GroupMembersProvider extends StatelessWidget { 43 | final List members; 44 | final Widget child; 45 | const GroupMembersProvider( 46 | {Key? key, required this.members, required this.child}) 47 | : super(key: key); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return BlocProvider( 52 | lazy: false, 53 | create: (_) => 54 | GroupCubit(ChatServiceImp())..getGroupMembersProfile(members), 55 | child: child, 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pure 2 | description: A Social Network Messenger. 3 | version: 1.0.0+1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.14.0 <3.0.0" 8 | 9 | dependencies: 10 | 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | 16 | adaptive_dialog: ^1.1.0 17 | algolia: ^1.0.4 18 | app_settings: ^4.1.1 19 | badges: ^2.0.1 20 | bubble: ^1.2.1 21 | cupertino_icons: ^1.0.3 22 | device_info_plus: ^2.2.0 23 | dio: ^4.0.0 24 | email_validator: ^2.0.1 25 | file_picker: ^4.2.3 26 | flash: ^2.0.1 27 | flex_color_scheme: ^4.1.1 28 | float_column: ^1.2.1 29 | flutter_easyloading: ^3.0.0 30 | flutter_link_previewer: ^2.6.1 31 | flutter_linkify: ^5.0.2 32 | font_awesome_flutter: ^9.2.0 33 | go_router: ^2.5.7 34 | intl: ^0.17.0 35 | linkify: ^4.1.0 36 | ntp: ^2.0.0 37 | open_file: ^3.2.1 38 | palette_generator: ^0.3.2 39 | permission_handler: ^8.3.0 40 | photo_view: ^0.13.0 41 | share_plus: ^2.2.0 42 | shared_preferences: ^2.0.6 43 | shimmer: ^2.0.0 44 | random_color: ^1.0.6-nullsafety 45 | smooth_page_indicator: ^0.3.0-nullsafety.0 46 | stream_transform: ^2.0.0 47 | timeago: ^3.1.0 48 | url_launcher: ^6.0.17 49 | uuid: ^3.0.4 50 | video_player: ^2.2.10 51 | video_trimmer: ^1.0.0 52 | 53 | 54 | # Authentication 55 | sign_in_with_apple: ^3.3.0 56 | google_sign_in: ^5.2.1 57 | 58 | # Screen Utilities 59 | flutter_screenutil: ^5.0.0+2 60 | 61 | # Images 62 | image_picker: ^0.8.4 63 | cached_network_image: ^3.1.0 64 | image_cropper: ^1.4.1 65 | flutter_image_compress: ^1.1.0 66 | 67 | # State Management 68 | flutter_bloc: ^8.0.0 69 | equatable: ^2.0.3 70 | 71 | # Firebase 72 | cloud_firestore: ^3.1.0 73 | firebase_auth: ^3.2.0 74 | firebase_core: ^1.10.0 75 | firebase_database: ^8.1.0 76 | firebase_dynamic_links: ^3.0.1 77 | firebase_messaging: ^11.1.0 78 | firebase_storage: ^10.1.0 79 | firebase_crashlytics: ^2.4.4 80 | 81 | dev_dependencies: 82 | bloc_test: ^9.0.0 83 | flutter_test: 84 | sdk: flutter 85 | mocktail: ^0.2.0 86 | very_good_analysis: ^2.4.0 87 | 88 | flutter: 89 | uses-material-design: true 90 | generate: true 91 | 92 | 93 | assets: 94 | - assets/images/ 95 | 96 | fonts: 97 | - family: Migra 98 | fonts: 99 | - asset: assets/fonts/Migra-Bold.otf 100 | weight: 800 101 | 102 | - family: PublicSans 103 | fonts: 104 | - asset: assets/fonts/PublicSans-Light.otf 105 | weight: 300 106 | - asset: assets/fonts/PublicSans-Regular.otf 107 | weight: 400 108 | - asset: assets/fonts/PublicSans-Medium.otf 109 | weight: 500 110 | - asset: assets/fonts/PublicSans-SemiBold.otf 111 | weight: 600 112 | - asset: assets/fonts/PublicSans-Bold.otf 113 | weight: 700 114 | 115 | -------------------------------------------------------------------------------- /test/app_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | --------------------------------------------------------------------------------