├── .DS_Store ├── .github ├── dependabot.yml ├── install_secret_script.sh └── workflows │ ├── analyze.yml │ ├── android_build.yml │ └── ios_deploy.yml ├── .gitignore ├── .run └── main.dart.run.xml ├── CONTRIBUTING.md ├── README.md ├── app ├── .firebaserc ├── .gitignore ├── .metadata ├── README.md ├── VERSION ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── ic_launcher-playstore.png │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── canopas │ │ │ │ │ └── yourspace │ │ │ │ │ ├── GeofenceBroadcastReceiver.kt │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ ├── app_logo.png │ │ │ │ ├── app_splash_icon.xml │ │ │ │ ├── launch_background.xml │ │ │ │ └── splash_inset.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── images │ │ ├── app_name_logo.svg │ │ ├── connection.svg │ │ ├── ic_30_battery_icon.svg │ │ ├── ic_50_battery_icon.svg │ │ ├── ic_about_us.svg │ │ ├── ic_add_member.svg │ │ ├── ic_add_user_icon.svg │ │ ├── ic_calendar_icon.svg │ │ ├── ic_close_icon.svg │ │ ├── ic_contact_support.svg │ │ ├── ic_distance_icon.png │ │ ├── ic_down_arrow_icon.svg │ │ ├── ic_edit_profile.svg │ │ ├── ic_empty_battery_icon.svg │ │ ├── ic_feed_location-pin.svg │ │ ├── ic_feed_location_icon.png │ │ ├── ic_flag_icon.svg │ │ ├── ic_full_bettery_icon.svg │ │ ├── ic_geofence_icon.svg │ │ ├── ic_google_logo.svg │ │ ├── ic_journey_empty_timeline_image.svg │ │ ├── ic_location-feed_icon.svg │ │ ├── ic_location.svg │ │ ├── ic_location_off.svg │ │ ├── ic_map_type.svg │ │ ├── ic_message.svg │ │ ├── ic_no_connection_icon.svg │ │ ├── ic_normal_map.png │ │ ├── ic_place_marker_icon.png │ │ ├── ic_places_gym_icon.svg │ │ ├── ic_places_home_icon.svg │ │ ├── ic_places_library_icon.svg │ │ ├── ic_places_park_icon.svg │ │ ├── ic_places_school_icon.svg │ │ ├── ic_places_work_icon.svg │ │ ├── ic_plus_icon.svg │ │ ├── ic_privacy_policy.svg │ │ ├── ic_regenerate_invitation_code.svg │ │ ├── ic_relocate_icon.svg │ │ ├── ic_remove.svg │ │ ├── ic_satellite_map.png │ │ ├── ic_search_icon.svg │ │ ├── ic_send_message.svg │ │ ├── ic_setting.svg │ │ ├── ic_share_two_location.svg │ │ ├── ic_sign_out.svg │ │ ├── ic_subscription_check_icon.svg │ │ ├── ic_subscription_icon.svg │ │ ├── ic_subscription_uncheck_icon.svg │ │ ├── ic_terrain_map.png │ │ ├── ic_time_line_history_icon.svg │ │ ├── ic_timeline_end_location_flag_icon.png │ │ ├── ic_timeline_journey_icon.svg │ │ ├── ic_timeline_location_pin_icon.svg │ │ ├── ic_timeline_start_location_icon.png │ │ ├── ic_unknown_battery_icon.svg │ │ ├── intro_1.svg │ │ ├── intro_2.svg │ │ ├── intro_3.svg │ │ └── intro_bg.jpg │ ├── locales │ │ └── app_en.arb │ └── map │ │ └── map_theme_night.json ├── firebase.json ├── functions │ └── node_modules │ │ └── .package-lock.json ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── 1024.png │ │ │ │ ├── 120 1.png │ │ │ │ ├── 120.png │ │ │ │ ├── 152.png │ │ │ │ ├── 167.png │ │ │ │ ├── 180.png │ │ │ │ ├── 20.png │ │ │ │ ├── 29 1.png │ │ │ │ ├── 29.png │ │ │ │ ├── 40 1.png │ │ │ │ ├── 40 2.png │ │ │ │ ├── 40.png │ │ │ │ ├── 58 1.png │ │ │ │ ├── 58.png │ │ │ │ ├── 60.png │ │ │ │ ├── 76.png │ │ │ │ ├── 80 1.png │ │ │ │ ├── 80.png │ │ │ │ ├── 87.png │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Grouptrack (1).png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── GeofencePlugin.swift │ │ ├── LocationManager.swift │ │ ├── Runner-Bridging-Header.h │ │ └── Runner.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── l10n.yaml ├── lib │ ├── domain │ │ ├── extenstions │ │ │ ├── api_error_extension.dart │ │ │ ├── context_extenstions.dart │ │ │ ├── date_formatter.dart │ │ │ ├── lat_lng_extenstion.dart │ │ │ ├── time_ago_extenstions.dart │ │ │ └── widget_extensions.dart │ │ └── fcm │ │ │ └── notification_handler.dart │ ├── gen │ │ └── assets.gen.dart │ ├── main.dart │ └── ui │ │ ├── app.dart │ │ ├── components │ │ ├── action_bottom_sheet.dart │ │ ├── alert.dart │ │ ├── app_logo.dart │ │ ├── app_page.dart │ │ ├── dashed_divider.dart │ │ ├── error_snakebar.dart │ │ ├── no_internet_screen.dart │ │ ├── on_visible_callback.dart │ │ ├── permission_dialog.dart │ │ ├── profile_picture.dart │ │ ├── resume_detector.dart │ │ └── user_battery_status.dart │ │ └── flow │ │ ├── auth │ │ ├── component │ │ │ └── sign_in_method_button.dart │ │ ├── sign_in_method_screen.dart │ │ ├── sign_in_method_viewmodel.dart │ │ └── sign_in_method_viewmodel.freezed.dart │ │ ├── geofence │ │ ├── add │ │ │ ├── addnew │ │ │ │ ├── add_new_place_screen.dart │ │ │ │ ├── add_new_place_view_model.dart │ │ │ │ └── add_new_place_view_model.freezed.dart │ │ │ ├── components │ │ │ │ ├── place_added_dialog.dart │ │ │ │ └── place_marker.dart │ │ │ ├── locate │ │ │ │ ├── locate_on_map_screen.dart │ │ │ │ ├── locate_on_map_view_model.dart │ │ │ │ └── locate_on_map_view_model.freezed.dart │ │ │ └── placename │ │ │ │ ├── choose_place_name_screen.dart │ │ │ │ ├── choose_place_name_view_model.dart │ │ │ │ └── choose_place_name_view_model.freezed.dart │ │ ├── edit │ │ │ ├── edit_place_screen.dart │ │ │ ├── edit_place_view_model.dart │ │ │ └── edit_place_view_model.freezed.dart │ │ └── places │ │ │ ├── places_list_screen.dart │ │ │ ├── places_list_view_model.dart │ │ │ └── places_list_view_model.freezed.dart │ │ ├── home │ │ ├── components │ │ │ └── home_top_bar.dart │ │ ├── home_screen.dart │ │ ├── home_screen_viewmodel.dart │ │ ├── home_screen_viewmodel.freezed.dart │ │ └── map │ │ │ ├── components │ │ │ ├── marker_generator.dart │ │ │ ├── selected_member_detail_view.dart │ │ │ └── space_user_footer.dart │ │ │ ├── map_screen.dart │ │ │ ├── map_view_model.dart │ │ │ └── map_view_model.freezed.dart │ │ ├── intro │ │ ├── intro_page_item.dart │ │ └── intro_screen.dart │ │ ├── journey │ │ ├── calender │ │ │ ├── horizontal_calendar_view.dart │ │ │ ├── horizontal_calendar_view_model.dart │ │ │ ├── horizontal_calendar_view_model.freezed.dart │ │ │ └── three_page_scroller.dart │ │ ├── components │ │ │ ├── dotted_line_view.dart │ │ │ └── journey_map.dart │ │ ├── detail │ │ │ ├── user_journey_detail_screen.dart │ │ │ ├── user_journey_detail_view_model.dart │ │ │ └── user_journey_detail_view_model.freezed.dart │ │ └── timeline │ │ │ ├── journey_timeline_screen.dart │ │ │ ├── journey_timeline_view_model.dart │ │ │ └── journey_timeline_view_model.freezed.dart │ │ ├── message │ │ ├── chat │ │ │ ├── chat_screen.dart │ │ │ ├── chat_view_model.dart │ │ │ └── chat_view_model.freezed.dart │ │ ├── thread_list_screen.dart │ │ ├── thread_list_view_model.dart │ │ └── thread_list_view_model.freezed.dart │ │ ├── navigation │ │ ├── routes.dart │ │ └── routes.g.dart │ │ ├── onboard │ │ ├── connection_screen.dart │ │ ├── pick_name_screen.dart │ │ ├── pick_name_view_model.dart │ │ └── pick_name_view_model.freezed.dart │ │ ├── permission │ │ ├── enable_permission_view.dart │ │ ├── enable_permission_view_model.dart │ │ └── enable_permission_view_model.freezed.dart │ │ ├── setting │ │ ├── contact_support │ │ │ ├── contact_support_screen.dart │ │ │ ├── contact_support_view_model.dart │ │ │ └── contact_support_view_model.freezed.dart │ │ ├── profile │ │ │ ├── profile_screen.dart │ │ │ ├── profile_view_model.dart │ │ │ └── profile_view_model.freezed.dart │ │ ├── setting_screen.dart │ │ ├── setting_view_model.dart │ │ ├── setting_view_model.freezed.dart │ │ ├── space │ │ │ ├── admin │ │ │ │ ├── change_admin_screen.dart │ │ │ │ ├── change_admin_view_model.dart │ │ │ │ └── change_admin_view_model.freezed.dart │ │ │ ├── edit_space_screen.dart │ │ │ ├── edit_space_view_model.dart │ │ │ └── edit_space_view_model.freezed.dart │ │ └── subscription │ │ │ ├── subscription_screen.dart │ │ │ ├── subscription_view_model.dart │ │ │ └── subscription_view_model.freezed.dart │ │ └── space │ │ ├── create │ │ ├── create_space_screen.dart │ │ ├── create_space_view_model.dart │ │ └── create_space_view_model.freezed.dart │ │ ├── invite │ │ └── invite_code_screen.dart │ │ └── join │ │ ├── join_space_screen.dart │ │ ├── join_space_view_model.dart │ │ └── join_space_view_model.freezed.dart ├── pubspec.lock └── pubspec.yaml ├── build_watch ├── data ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build.yaml ├── lib │ ├── api │ │ ├── auth │ │ │ ├── api_user_service.dart │ │ │ ├── auth_models.dart │ │ │ ├── auth_models.freezed.dart │ │ │ └── auth_models.g.dart │ │ ├── location │ │ │ ├── journey │ │ │ │ ├── api_journey_service.dart │ │ │ │ ├── journey.dart │ │ │ │ ├── journey.freezed.dart │ │ │ │ └── journey.g.dart │ │ │ ├── location.dart │ │ │ ├── location.freezed.dart │ │ │ └── location.g.dart │ │ ├── message │ │ │ ├── api_message_service.dart │ │ │ ├── message_models.dart │ │ │ ├── message_models.freezed.dart │ │ │ ├── message_models.g.dart │ │ │ └── server_timestamp_converter.dart │ │ ├── network │ │ │ └── client.dart │ │ ├── place │ │ │ ├── api_place.dart │ │ │ ├── api_place.freezed.dart │ │ │ └── api_place.g.dart │ │ ├── space │ │ │ ├── api_space_invitation_service.dart │ │ │ ├── api_space_service.dart │ │ │ ├── space_models.dart │ │ │ ├── space_models.freezed.dart │ │ │ └── space_models.g.dart │ │ ├── subscription │ │ │ ├── subscription_models.dart │ │ │ └── subscription_models.freezed.dart │ │ └── support │ │ │ └── api_support_service.dart │ ├── config.dart │ ├── converter │ │ └── time_converter.dart │ ├── domain │ │ ├── journey_lat_lng_entension.dart │ │ └── location_data_extension.dart │ ├── feature_flags.dart │ ├── log │ │ ├── log_format.dart │ │ └── logger.dart │ ├── network │ │ └── error.dart │ ├── repository │ │ ├── geofence_repository.dart │ │ ├── journey_generator.dart │ │ └── journey_repository.dart │ ├── service │ │ ├── auth_service.dart │ │ ├── device_service.dart │ │ ├── geofence_service.dart │ │ ├── location_manager.dart │ │ ├── location_service.dart │ │ ├── message_service.dart │ │ ├── network_service.dart │ │ ├── permission_service.dart │ │ ├── place_service.dart │ │ └── space_service.dart │ ├── storage │ │ ├── app_preferences.dart │ │ ├── location_caches.dart │ │ └── preferences_provider.dart │ └── utils │ │ └── location_converters.dart └── pubspec.yaml ├── docs └── terms-of-services.md ├── screenshots ├── cover_image.png ├── create_space.gif ├── cta_banner.png ├── cta_btn.png ├── location_tracking.gif ├── message.gif └── place.gif └── style ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── assets └── fonts │ ├── inter_black_italic.ttf │ ├── inter_bold.ttf │ ├── inter_italic.ttf │ ├── inter_light.ttf │ ├── inter_medium.ttf │ ├── inter_regular.ttf │ ├── inter_semi_bold.ttf │ └── kalam_bold.ttf ├── lib ├── animation │ └── on_tap_scale.dart ├── button │ ├── action_button.dart │ ├── bottom_sticky_overlay.dart │ ├── icon_primary_button.dart │ ├── large_icon_button.dart │ ├── primary_button.dart │ └── secondary_button.dart ├── extenstions │ ├── column_builder.dart │ ├── context_extenstions.dart │ └── date_extenstions.dart ├── indicator │ └── progress_indicator.dart ├── style.dart ├── text │ ├── app_text_dart.dart │ └── app_text_field.dart └── theme │ ├── colors.dart │ └── theme.dart ├── pubspec.yaml └── test └── style_test.dart /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/.DS_Store -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pub" 9 | directory: "/app/" 10 | schedule: 11 | interval: "monthly" 12 | open-pull-requests-limit: 3 13 | 14 | -------------------------------------------------------------------------------- /.github/install_secret_script.sh: -------------------------------------------------------------------------------- 1 | env: 2 | FIREBASE_OPTIONS_BASE64: ${{ secrets.FIREBASE_OPTIONS_BASE64 }} 3 | GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }} 4 | GOOGLE_SERVICES_INFO_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_INFO_JSON_BASE64 }} 5 | INFO_PLIST_BASE64: ${{ secrets.INFO_PLIST_BASE64 }} 6 | CONFIG_DART_BASE64: ${{ secrets.CONFIG_DART_BASE64 }} 7 | 8 | echo $FIREBASE_OPTIONS_BASE64 | base64 -di > lib/firebase_options.dart 9 | echo $GOOGLE_SERVICES_JSON_BASE64 | base64 -di > android/app/google-services.json 10 | echo $GOOGLE_SERVICES_INFO_JSON_BASE64 | base64 -di > ios/Runner/GoogleService-Info.plist 11 | echo $INFO_PLIST_BASE64 | base64 -di > ios/Runner/Info.plist 12 | cd ../data 13 | echo $CONFIG_DART_BASE64 | base64 -di > lib/config.dart 14 | cd ../app -------------------------------------------------------------------------------- /.github/workflows/analyze.yml: -------------------------------------------------------------------------------- 1 | name: Dart Format & Analyze 2 | 3 | on: push 4 | 5 | jobs: 6 | analyze: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | name: Checkout 12 | 13 | - uses: subosito/flutter-action@v2.12.0 14 | name: Set up Flutter SDK 15 | with: 16 | channel: 'stable' 17 | cache: true 18 | 19 | - name: Retrieve the secret and decode it to file 20 | env: 21 | MAPS_API_KEY: ${{ secrets.MAPS_API_KEY }} 22 | FIREBASE_OPTIONS_BASE64: ${{ secrets.FIREBASE_OPTIONS_BASE64 }} 23 | INFO_PLIST_BASE64: ${{ secrets.INFO_PLIST_BASE64 }} 24 | CONFIG_DART_BASE64: ${{ secrets.CONFIG_DART_BASE64 }} 25 | 26 | run: | 27 | cd app 28 | echo $FIREBASE_OPTIONS_BASE64 | base64 -di > lib/firebase_options.dart 29 | echo $INFO_PLIST_BASE64 | base64 -di > ios/Runner/Info.plist 30 | cd ../data 31 | echo $CONFIG_DART_BASE64 | base64 -di > lib/config.dart 32 | cd .. 33 | 34 | - name: Install dependencies 35 | run: | 36 | cd data && flutter clean && flutter pub get 37 | cd ../style && flutter clean && flutter pub get 38 | cd ../app && flutter clean && flutter pub get 39 | cd .. 40 | 41 | - name: Lint test 42 | run: | 43 | cd data && flutter analyze --fatal-infos 44 | cd ../style && flutter analyze --fatal-infos 45 | cd ../app && flutter analyze --fatal-infos 46 | cd .. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | firebase-debug.log -------------------------------------------------------------------------------- /.run/main.dart.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you so much for your interest in contributing! All types of contributions are encouraged and valued. The Project Team looks forward to your contributions. 4 | 5 | ## Filing Issues 6 | 7 | When in doubt, file an issue. We'd rather close a few duplicate issues than let a problem go unnoticed. Similarly, if you support a particular feature request, please let us know by commenting on the issue or [subscribing](https://help.github.com/articles/subscribing-to-conversations/) to the issue. 8 | 9 | If you are reporting a bug, please help speed up problem diagnosis by providing as much information as possible. Ideally, that would include a small sample project (or gist) that reproduces the problem. 10 | 11 | ## Contributing Code 12 | 13 | We actively welcome your pull requests. You can find instructions on building the project in [README.md](https://github.com/canopas/group-track-flutter). 14 | 15 | 1. Fork the repo and create your branch from `main`. 16 | 2. If you've added code that should be tested, add tests. 17 | 3. Make sure your code lints. 18 | 19 | ## Labels 20 | 21 | Labels on issues are managed by contributors; you don't have to worry about them. Here's a list of what they mean: 22 | 23 | - **bug**: Feature that should work, but doesn't. 24 | - **enhancement**: Minor tweak/addition to existing behavior. 25 | - **feature**: New behavior, bigger than enhancement. 26 | - **question**: No need for any fix, usually a usage problem. 27 | - **reproducible**: Has enough information to very easily reproduce, mostly in the form of a small project in a GitHub repo. 28 | - **repro-needed**: We need some code to be able to reproduce and debug locally; otherwise, there's not much we can do. 29 | - **duplicate**: There's another issue that already covers/tracks this. 30 | - **wontfix**: Working as intended, or won't be fixed due to compatibility or other reasons. 31 | - **invalid**: There isn't enough information to make a verdict, or unrelated. 32 | - **non-library**: Issue is not in the core library code, but rather in documentation, samples, build process, or releases. 33 | 34 | ## License 35 | 36 | By contributing to GroupTrack, you agree that your contributions will be licensed under its Apache License, Version 2.0. See the LICENSE file for details. 37 | -------------------------------------------------------------------------------- /app/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "yourspace-regional" 4 | }, 5 | "targets": {}, 6 | "etags": { 7 | "yourspace-regional": { 8 | "extensionInstances": {} 9 | } 10 | }, 11 | "dataconnectEmulatorConfig": {} 12 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | **/firebase_options.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /app/.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: "41456452f29d64e8deb623a3c927524bcf9f111b" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 17 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 18 | - platform: android 19 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 20 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 21 | - platform: ios 22 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 23 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # App 2 | 3 | The `App` is the main module of YourSpace. It contains the core Flutter application implementation for YourSpace. 4 | 5 | ## Features 6 | 7 | - **User Interface:** Delivers the complete user interface for the YourSpace app. 8 | - **Native Integration:** Includes native code for platform-specific functionalities, such as background location tracking and geofencing. 9 | - **Components:** Contains reusable components such as buttons, views and extenstions. -------------------------------------------------------------------------------- /app/VERSION: -------------------------------------------------------------------------------- 1 | 1.0 -------------------------------------------------------------------------------- /app/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | analyzer: 11 | errors: 12 | constant_identifier_names: ignore 13 | file_names: ignore 14 | include: package:flutter_lints/flutter.yaml 15 | 16 | linter: 17 | # The lint rules applied to this project can be customized in the 18 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 19 | # included above or to enable additional rules. A list of all available lints 20 | # and their documentation is published at https://dart.dev/lints. 21 | # 22 | # Instead of disabling a lint rule for the entire project in the 23 | # section below, it can also be suppressed for a single line of code 24 | # or a specific dart file by using the `// ignore: name_of_lint` and 25 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 26 | # producing the lint. 27 | rules: 28 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 29 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 30 | 31 | # Additional information about this file can be found at 32 | # https://dart.dev/guides/language/analysis-options 33 | -------------------------------------------------------------------------------- /app/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | **/google-services.json 15 | -------------------------------------------------------------------------------- /app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/android/app/src/main/kotlin/com/canopas/yourspace/GeofenceBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.canopas.yourspace 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.util.Log 7 | import com.google.android.gms.location.Geofence 8 | import com.google.android.gms.location.GeofenceStatusCodes 9 | import com.google.android.gms.location.GeofencingEvent 10 | import io.flutter.embedding.engine.FlutterEngineCache 11 | import io.flutter.plugin.common.MethodChannel 12 | 13 | class GeofenceBroadcastReceiver : BroadcastReceiver() { 14 | override fun onReceive(context: Context?, intent: Intent) { 15 | val geofencingEvent = GeofencingEvent.fromIntent(intent) 16 | 17 | if (geofencingEvent == null) { 18 | Log.e("GeofenceReceiver", "Geofencing event is null") 19 | return 20 | } 21 | 22 | if (geofencingEvent.hasError()) { 23 | val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode) 24 | Log.e("GeofenceReceiver", "Geofence error: $errorMessage") 25 | return 26 | } 27 | 28 | try { 29 | val geofenceTransition = geofencingEvent.geofenceTransition 30 | 31 | if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || 32 | geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT 33 | ) { 34 | val triggeringGeofences = geofencingEvent.triggeringGeofences 35 | Log.d("GeofenceReceiver", "Geofence Alert received") 36 | 37 | triggeringGeofences?.forEach { geofence -> 38 | val placeId = geofence.requestId 39 | val method = if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) 40 | "onEnterGeofence" 41 | else 42 | "onExitGeofence" 43 | 44 | handleGeofenceEvent(method, placeId) 45 | } 46 | } else { 47 | Log.e("GeofenceReceiver", "Geofence transition error: $geofenceTransition") 48 | } 49 | } catch (e: Exception) { 50 | Log.e("GeofenceReceiver", "Error while processing geofence alert: $e") 51 | } 52 | } 53 | 54 | private fun handleGeofenceEvent(method: String, placeId: String) { 55 | val flutterEngine = FlutterEngineCache.getInstance().get("geofence_engine") 56 | if (flutterEngine == null) { 57 | Log.d("GeofenceReceiver", "Geofence flutter engine is null") 58 | return 59 | } 60 | 61 | val methodChannel = 62 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "geofence_plugin") 63 | methodChannel.invokeMethod(method, mapOf("identifier" to placeId)) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/drawable/app_logo.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/app_splash_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/splash_inset.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1679AB 4 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.9.0' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:8.2.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.4.2' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | tasks.register("clean", Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version '8.3.2' apply false 27 | id "com.google.gms.google-services" version "4.4.2" apply false 28 | id "com.google.firebase.crashlytics" version "3.0.2" apply false 29 | } 30 | 31 | include ":app" 32 | -------------------------------------------------------------------------------- /app/assets/images/ic_30_battery_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_50_battery_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_about_us.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ic_add_member.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/ic_add_user_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_calendar_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ic_close_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_contact_support.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ic_distance_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_distance_icon.png -------------------------------------------------------------------------------- /app/assets/images/ic_down_arrow_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_edit_profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_empty_battery_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_feed_location-pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ic_feed_location_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_feed_location_icon.png -------------------------------------------------------------------------------- /app/assets/images/ic_flag_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ic_full_bettery_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_geofence_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_google_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/assets/images/ic_location-feed_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/ic_location_off.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/assets/images/ic_map_type.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/ic_message.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/assets/images/ic_normal_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_normal_map.png -------------------------------------------------------------------------------- /app/assets/images/ic_place_marker_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_place_marker_icon.png -------------------------------------------------------------------------------- /app/assets/images/ic_places_gym_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_places_home_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_places_library_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_places_park_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_places_school_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_places_work_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_plus_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_privacy_policy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_regenerate_invitation_code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ic_relocate_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/ic_satellite_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_satellite_map.png -------------------------------------------------------------------------------- /app/assets/images/ic_search_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_sign_out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_subscription_check_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/ic_subscription_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_subscription_uncheck_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/ic_terrain_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_terrain_map.png -------------------------------------------------------------------------------- /app/assets/images/ic_time_line_history_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/ic_timeline_end_location_flag_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_timeline_end_location_flag_icon.png -------------------------------------------------------------------------------- /app/assets/images/ic_timeline_journey_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/ic_timeline_location_pin_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/assets/images/ic_timeline_start_location_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/ic_timeline_start_location_icon.png -------------------------------------------------------------------------------- /app/assets/images/ic_unknown_battery_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/assets/images/intro_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/assets/images/intro_bg.jpg -------------------------------------------------------------------------------- /app/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": [ 3 | { 4 | "source": "functions", 5 | "codebase": "default", 6 | "ignore": [ 7 | "node_modules", 8 | ".git", 9 | "firebase-debug.log", 10 | "firebase-debug.*.log", 11 | "*.local" 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | firebase_app_id_file.json 36 | **/GoogleService-Info.plist 37 | **/Info.plist 38 | **/ExportOptions.plist -------------------------------------------------------------------------------- /app/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.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 => '11.4.0' 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | target 'RunnerTests' do 38 | inherit! :search_paths 39 | end 40 | end 41 | 42 | post_install do |installer| 43 | installer.pods_project.targets.each do |target| 44 | flutter_additional_ios_build_settings(target) 45 | 46 | # Start of the permission_handler configuration 47 | target.build_configurations.each do |config| 48 | 49 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 50 | '$(inherited)', 51 | 52 | ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 53 | 'PERMISSION_LOCATION=1', 54 | 'PERMISSION_LOCATION_WHENINUSE=0', 55 | 56 | ## dart: PermissionGroup.notification 57 | 'PERMISSION_NOTIFICATIONS=1', 58 | 59 | ] 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/120 1.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/29 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/29 1.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 1.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/40 2.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/58 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/58 1.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/80 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/80 1.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Grouptrack (1).png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Grouptrack (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Grouptrack (1).png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /app/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 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/ios/Runner/GeofencePlugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeofencePlugin.swift 3 | // Runner 4 | // 5 | // Created by Ishita on 27/08/24. 6 | // 7 | 8 | import Foundation 9 | import CoreLocation 10 | import Flutter 11 | import UIKit 12 | 13 | class GeofenceService: NSObject, CLLocationManagerDelegate { 14 | private var locationManager: CLLocationManager! 15 | private var channel: FlutterMethodChannel? 16 | 17 | init(channel: FlutterMethodChannel? = nil) { 18 | super.init() 19 | self.locationManager = CLLocationManager() 20 | self.channel = channel 21 | self.locationManager.delegate = self 22 | } 23 | 24 | func startMonitoring(center: CLLocationCoordinate2D, radius: CLLocationDistance, identifier: String) { 25 | let region = CLCircularRegion(center: center, radius: radius, identifier: identifier) 26 | region.notifyOnEntry = true 27 | region.notifyOnExit = true 28 | self.locationManager.startMonitoring(for: region) 29 | } 30 | 31 | func stopMonitoring(identifier: String) { 32 | for region in locationManager.monitoredRegions { 33 | if let circularRegion = region as? CLCircularRegion, circularRegion.identifier == identifier { 34 | self.locationManager.stopMonitoring(for: region) 35 | } 36 | } 37 | } 38 | 39 | func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { 40 | if let circularRegion = region as? CLCircularRegion { 41 | channel?.invokeMethod("onEnterGeofence", arguments: ["identifier": circularRegion.identifier]) 42 | } 43 | } 44 | 45 | func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { 46 | if let circularRegion = region as? CLCircularRegion { 47 | channel?.invokeMethod("onExitGeofence", arguments: ["identifier": circularRegion.identifier]) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /app/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.applesignin 8 | 9 | Default 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: assets/locales 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart -------------------------------------------------------------------------------- /app/lib/domain/extenstions/api_error_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/network/error.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; 4 | 5 | 6 | extension AppErrorExtensions on Object { 7 | String l10nMessage(BuildContext context) { 8 | switch (runtimeType) { 9 | case const (NoInternetConnectionError): 10 | return context.l10n.errorNoConnection; 11 | case const (GenericError): 12 | return context.l10n.errorGeneric; 13 | case const (String): 14 | return this as String; 15 | case const (StringError): 16 | return (this as StringError).error; 17 | case const (ApiError): 18 | return (this as ApiError).message ?? context.l10n.errorGeneric; 19 | default: 20 | return context.l10n.errorGeneric; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/lib/domain/extenstions/context_extenstions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | 4 | extension BuildContextExtensions on BuildContext { 5 | AppLocalizations get l10n => AppLocalizations.of(this)!; 6 | } 7 | -------------------------------------------------------------------------------- /app/lib/domain/extenstions/time_ago_extenstions.dart: -------------------------------------------------------------------------------- 1 | extension TimeAgoExtension on int? { 2 | String timeAgo() { 3 | if (this == null) return ""; 4 | 5 | final DateTime now = DateTime.now(); 6 | final DateTime date = DateTime.fromMillisecondsSinceEpoch(this!); 7 | 8 | final Duration diff = now.difference(date); 9 | 10 | if (diff.inSeconds < 60) { 11 | return "just now"; 12 | } else if (diff.inMinutes < 60) { 13 | return "${diff.inMinutes} minutes ago"; 14 | } else if (diff.inHours < 24) { 15 | return "${diff.inHours} hours ago"; 16 | } else if (diff.inDays < 7) { 17 | return "${diff.inDays} days ago"; 18 | } else if (diff.inDays < 30) { 19 | return "${diff.inDays ~/ 7} weeks ago"; 20 | } else if (diff.inDays < 365) { 21 | return "${diff.inDays ~/ 30} months ago"; 22 | } else { 23 | return "${diff.inDays ~/ 365} years ago"; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/lib/domain/extenstions/widget_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | void runPostFrame(Function() block) { 4 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 5 | block(); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /app/lib/ui/components/app_logo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | 4 | import '../../gen/assets.gen.dart'; 5 | 6 | class AppLogo extends StatelessWidget { 7 | final Color? contentColor; 8 | 9 | const AppLogo({super.key, this.contentColor}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Column( 14 | crossAxisAlignment: CrossAxisAlignment.center, 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | SvgPicture.asset(Assets.images.appNameLogo), 18 | ], 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/lib/ui/components/dashed_divider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class DashedLineVerticalPainter extends CustomPainter { 5 | final Color color; 6 | DashedLineVerticalPainter({required this.color}); 7 | 8 | @override 9 | void paint(Canvas canvas, Size size) { 10 | double dashHeight = 5, dashSpace = 3, startY = 0; 11 | final paint = Paint() 12 | ..color = color 13 | ..strokeWidth = size.width; 14 | while (startY < size.height) { 15 | canvas.drawLine(Offset(0, startY), Offset(0, startY + dashHeight), paint); 16 | startY += dashHeight + dashSpace; 17 | } 18 | } 19 | 20 | @override 21 | bool shouldRepaint(CustomPainter oldDelegate) => false; 22 | } -------------------------------------------------------------------------------- /app/lib/ui/components/error_snakebar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:data/network/error.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | import 'package:yourspace_flutter/domain/extenstions/api_error_extension.dart'; 8 | 9 | void showErrorSnackBar(BuildContext context, Object error) { 10 | HapticFeedback.mediumImpact(); 11 | final message = ApiError.fromError(error).l10nMessage(context); 12 | showSnackBar(context, message, length: SnackBarLength.long); 13 | } 14 | 15 | void showSnackBar( 16 | BuildContext context, 17 | String text, { 18 | SnackBarLength length = SnackBarLength.short, 19 | }) { 20 | if (Platform.isIOS) { 21 | Fluttertoast.showToast( 22 | msg: text, 23 | timeInSecForIosWeb: length.seconds, 24 | gravity: ToastGravity.BOTTOM, 25 | ); 26 | } else { 27 | final snackBar = SnackBar( 28 | content: Text(text), 29 | behavior: SnackBarBehavior.floating, 30 | duration: Duration(seconds: length.seconds), 31 | ); 32 | ScaffoldMessenger.of(context).removeCurrentSnackBar(); 33 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 34 | } 35 | } 36 | 37 | enum SnackBarLength { 38 | short(1), 39 | long(2); 40 | 41 | final int seconds; 42 | 43 | const SnackBarLength(this.seconds); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /app/lib/ui/components/no_internet_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:connectivity_plus/connectivity_plus.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | import 'package:style/button/primary_button.dart'; 5 | import 'package:style/extenstions/context_extenstions.dart'; 6 | import 'package:style/text/app_text_dart.dart'; 7 | import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; 8 | 9 | import '../../gen/assets.gen.dart'; 10 | 11 | class NoInternetScreen extends StatefulWidget { 12 | final Function() onPressed; 13 | 14 | const NoInternetScreen({super.key, required this.onPressed}); 15 | 16 | @override 17 | State createState() => _NoInternetScreenState(); 18 | } 19 | 20 | class _NoInternetScreenState extends State { 21 | @override 22 | Widget build(BuildContext context) { 23 | return Center( 24 | child: Padding( 25 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 40), 26 | child: SingleChildScrollView( 27 | child: Column( 28 | mainAxisAlignment: MainAxisAlignment.center, 29 | children: [ 30 | SvgPicture.asset(Assets.images.icNoConnectionIcon), 31 | const SizedBox(height: 40), 32 | Text( 33 | context.l10n.on_internet_error_title, 34 | style: AppTextStyle.header1 35 | .copyWith(color: context.colorScheme.textPrimary), 36 | ), 37 | const SizedBox(height: 16), 38 | Text( 39 | context.l10n.on_internet_error_sub_title, 40 | style: AppTextStyle.subtitle2 41 | .copyWith(color: context.colorScheme.textSecondary), 42 | textAlign: TextAlign.center, 43 | ), 44 | const SizedBox(height: 40), 45 | PrimaryButton( 46 | context.l10n.common_retry, 47 | edgeInsets: const EdgeInsets.symmetric( 48 | vertical: 14.0, horizontal: 42.0), 49 | expanded: false, 50 | onPressed: () { 51 | widget.onPressed(); 52 | }, 53 | ), 54 | ], 55 | ), 56 | ), 57 | ), 58 | ); 59 | } 60 | } 61 | 62 | Future checkInternetConnectivity() async { 63 | final result = await Connectivity().checkConnectivity(); 64 | return result.first == ConnectivityResult.none; 65 | } 66 | -------------------------------------------------------------------------------- /app/lib/ui/components/on_visible_callback.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class OnVisibleCallback extends StatefulWidget { 4 | final void Function() onVisible; 5 | final Widget child; 6 | 7 | const OnVisibleCallback({ 8 | super.key, 9 | required this.onVisible, 10 | required this.child, 11 | }); 12 | 13 | @override 14 | State createState() => _OnCreateState(); 15 | } 16 | 17 | class _OnCreateState extends State { 18 | @override 19 | void initState() { 20 | widget.onVisible(); 21 | super.initState(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return widget.child; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/lib/ui/components/permission_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:style/button/primary_button.dart'; 3 | import 'package:style/extenstions/context_extenstions.dart'; 4 | import 'package:style/text/app_text_dart.dart'; 5 | import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; 6 | 7 | class PermissionDialog extends StatelessWidget { 8 | final String title; 9 | final String subTitle1; 10 | final String? subTitle2; 11 | final String? dismissBtn; 12 | final String? confirmBtn; 13 | final VoidCallback onDismiss; 14 | final VoidCallback goToSettings; 15 | 16 | const PermissionDialog({ 17 | super.key, 18 | required this.title, 19 | required this.subTitle1, 20 | this.subTitle2, 21 | this.dismissBtn, 22 | this.confirmBtn, 23 | required this.onDismiss, 24 | required this.goToSettings, 25 | }); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return AlertDialog( 30 | shape: RoundedRectangleBorder( 31 | borderRadius: BorderRadius.circular(16), 32 | ), 33 | title: Text( 34 | title, 35 | textAlign: TextAlign.center, 36 | style: AppTextStyle.header3, 37 | ), 38 | content: Column( 39 | mainAxisSize: MainAxisSize.min, 40 | children: [ 41 | Text( 42 | subTitle1, 43 | textAlign: TextAlign.center, 44 | style: AppTextStyle.body1 45 | .copyWith(color: context.colorScheme.textDisabled), 46 | ), 47 | if (subTitle2 != null) ...[ 48 | const SizedBox(height: 24), 49 | Text( 50 | subTitle2!, 51 | textAlign: TextAlign.center, 52 | style: AppTextStyle.body2 53 | .copyWith(color: context.colorScheme.textDisabled), 54 | ), 55 | ], 56 | ], 57 | ), 58 | actions: [ 59 | PrimaryButton(confirmBtn ?? context.l10n.common_go_to_setting, 60 | onPressed: goToSettings), 61 | if (dismissBtn != null) ...[ 62 | const SizedBox(height: 16), 63 | OutlinedPrimaryButton( 64 | dismissBtn!, 65 | onPressed: onDismiss, 66 | foreground: context.colorScheme.primary, 67 | ), 68 | ], 69 | ], 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/lib/ui/components/profile_picture.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:style/extenstions/context_extenstions.dart'; 4 | import 'package:style/indicator/progress_indicator.dart'; 5 | import 'package:style/text/app_text_dart.dart'; 6 | 7 | class ProfileImage extends StatefulWidget { 8 | final double size; 9 | final String profileImageUrl; 10 | final String firstLetter; 11 | final TextStyle? style; 12 | final Color? backgroundColor; 13 | final Color? borderColor; 14 | 15 | const ProfileImage({ 16 | super.key, 17 | this.size = 64, 18 | required this.profileImageUrl, 19 | required this.firstLetter, 20 | this.style, 21 | this.backgroundColor, 22 | this.borderColor, 23 | }); 24 | 25 | @override 26 | State createState() => _ProfileImageState(); 27 | } 28 | 29 | class _ProfileImageState extends State { 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return SizedBox( 34 | width: widget.size, 35 | height: widget.size, 36 | child: ClipRRect( 37 | borderRadius: BorderRadius.circular(widget.size / 2), 38 | child: widget.profileImageUrl.isNotEmpty 39 | ? CachedNetworkImage( 40 | imageUrl: widget.profileImageUrl, 41 | placeholder: (context, url) => const AppProgressIndicator( 42 | size: AppProgressIndicatorSize.small), 43 | errorWidget: (context, url, error) => const Icon(Icons.error), 44 | fit: BoxFit.cover, 45 | ) 46 | : Container( 47 | decoration: BoxDecoration( 48 | color: widget.backgroundColor ?? 49 | context.colorScheme.containerInverseHigh, 50 | border: Border.all(width: 0.5, color: widget.borderColor ?? Colors.transparent), 51 | borderRadius: BorderRadius.circular(widget.size / 2), 52 | ), 53 | child: Center( 54 | child: Text( 55 | widget.firstLetter, 56 | style: widget.style ?? AppTextStyle.subtitle2 57 | .copyWith(color: context.colorScheme.textInversePrimary)), 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/lib/ui/components/resume_detector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | import 'package:visibility_detector/visibility_detector.dart'; 4 | 5 | class ResumeDetector extends StatefulWidget { 6 | final Function() onResume; 7 | final Widget child; 8 | 9 | const ResumeDetector({ 10 | super.key, 11 | required this.onResume, 12 | required this.child, 13 | }); 14 | 15 | @override 16 | State createState() => _ResumeDetectorState(); 17 | } 18 | 19 | class _ResumeDetectorState extends State { 20 | final String _key = const Uuid().v4(); 21 | 22 | var _lastNotifiedTime = DateTime.now(); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return VisibilityDetector( 27 | key: Key(_key), 28 | child: widget.child, 29 | onVisibilityChanged: (info) { 30 | if (info.visibleFraction == 1) { 31 | if (DateTime.now().difference(_lastNotifiedTime).inMilliseconds > 32 | 1000) { 33 | widget.onResume(); 34 | _lastNotifiedTime = DateTime.now(); 35 | } 36 | } 37 | }, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/lib/ui/components/user_battery_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/api/auth/auth_models.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_svg/svg.dart'; 5 | import 'package:style/extenstions/context_extenstions.dart'; 6 | import 'package:style/text/app_text_dart.dart'; 7 | 8 | import '../../gen/assets.gen.dart'; 9 | 10 | class UserBatteryStatus extends StatelessWidget { 11 | final ApiUser user; 12 | 13 | const UserBatteryStatus({super.key, required this.user}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final batteryPct = user.battery_pct ?? 0; 18 | String icon; 19 | Color color; 20 | 21 | if (batteryPct > 70) { 22 | icon = Assets.images.icFullBetteryIcon; 23 | color = context.colorScheme.positive; 24 | } else if (batteryPct > 50) { 25 | icon = Assets.images.ic50BatteryIcon; 26 | color = context.colorScheme.positive; 27 | } else if (batteryPct > 30) { 28 | icon = Assets.images.ic30BatteryIcon; 29 | color = context.colorScheme.positive; 30 | } else if (batteryPct >= 1) { 31 | icon = Assets.images.icEmptyBatteryIcon; 32 | color = context.colorScheme.alert; 33 | } else { 34 | icon = Assets.images.icUnknownBatteryIcon; 35 | color = context.colorScheme.textDisabled; 36 | } 37 | 38 | return Row( 39 | children: [ 40 | SvgPicture.asset( 41 | icon, 42 | colorFilter: ColorFilter.mode(color, BlendMode.srcATop), 43 | ), 44 | Visibility( 45 | visible: batteryPct >= 1, 46 | child: Text( 47 | '${batteryPct.toInt()}%', 48 | style: AppTextStyle.caption 49 | .copyWith(color: context.colorScheme.textPrimary), 50 | ), 51 | ) 52 | ], 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/lib/ui/flow/auth/component/sign_in_method_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:style/animation/on_tap_scale.dart'; 3 | import 'package:style/extenstions/context_extenstions.dart'; 4 | import 'package:style/indicator/progress_indicator.dart'; 5 | import 'package:style/text/app_text_dart.dart'; 6 | 7 | class SignInMethodButton extends StatelessWidget { 8 | final Function()? onTap; 9 | final String title; 10 | final bool isLoading; 11 | final Widget? icon; 12 | final Color? backgroundColor; 13 | final Color? foregroundColor; 14 | 15 | const SignInMethodButton({ 16 | super.key, 17 | this.onTap, 18 | this.backgroundColor, 19 | this.foregroundColor, 20 | required this.title, 21 | this.icon, 22 | this.isLoading = false, 23 | }); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return OnTapScale( 28 | onTap: onTap, 29 | enabled: onTap != null && !isLoading, 30 | child: Container( 31 | alignment: Alignment.center, 32 | width: double.infinity, 33 | constraints: const BoxConstraints(minHeight: 48), 34 | padding: const EdgeInsets.symmetric(horizontal: 12), 35 | decoration: BoxDecoration( 36 | color: backgroundColor ?? context.colorScheme.primary, 37 | borderRadius: BorderRadius.circular(24), 38 | ), 39 | child: Visibility( 40 | visible: isLoading, 41 | replacement: Row( 42 | mainAxisAlignment: MainAxisAlignment.center, 43 | children: [ 44 | icon ?? const SizedBox(), 45 | Visibility( 46 | visible: icon != null, 47 | child: const SizedBox(width: 8), 48 | ), 49 | Text( 50 | title, 51 | style: AppTextStyle.button.copyWith( 52 | color: foregroundColor ?? context.colorScheme.onPrimary, 53 | ), 54 | ), 55 | ], 56 | ), 57 | child: AppProgressIndicator( 58 | color: foregroundColor, 59 | size: AppProgressIndicatorSize.small, 60 | ), 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/lib/ui/flow/geofence/add/addnew/add_new_place_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:data/api/place/api_place.dart'; 4 | import 'package:data/log/logger.dart'; 5 | import 'package:data/service/location_manager.dart'; 6 | import 'package:data/service/place_service.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | import 'package:freezed_annotation/freezed_annotation.dart'; 9 | import 'package:geolocator/geolocator.dart'; 10 | 11 | import '../../../../components/no_internet_screen.dart'; 12 | 13 | part 'add_new_place_view_model.freezed.dart'; 14 | 15 | final addNewPlaceStateProvider = StateNotifierProvider.autoDispose< 16 | AddNewPlaceViewNotifier, AddNewPlaceState>((ref) { 17 | return AddNewPlaceViewNotifier( 18 | ref.read(placeServiceProvider), 19 | ref.read(locationManagerProvider), 20 | ); 21 | }); 22 | 23 | class AddNewPlaceViewNotifier extends StateNotifier { 24 | final PlaceService placeService; 25 | final LocationManager locationManager; 26 | 27 | AddNewPlaceViewNotifier( 28 | this.placeService, 29 | this.locationManager, 30 | ) : super(const AddNewPlaceState()); 31 | 32 | Timer? _debounce; 33 | Position? _position; 34 | 35 | void onPlaceNameChanged(String value) { 36 | if (_debounce?.isActive ?? false) _debounce!.cancel(); 37 | 38 | _debounce = Timer(const Duration(milliseconds: 500), () async { 39 | final isNetworkOff = await checkInternetConnectivity(); 40 | state = state.copyWith(isNetworkOff: isNetworkOff); 41 | if (isNetworkOff) return; 42 | 43 | fidePlace(value); 44 | }); 45 | } 46 | 47 | void fidePlace(String value) async { 48 | if (value.isEmpty) { 49 | state = state.copyWith(places: []); 50 | return; 51 | } 52 | try { 53 | state = state.copyWith(loading: true); 54 | final position = await locationManager.getLastLocation(); 55 | 56 | if (position != null && 57 | position.latitude != _position?.latitude && 58 | position.longitude != _position?.longitude) { 59 | _position = position; 60 | } 61 | final places = await placeService.searchNearbyPlaces( 62 | value, 63 | _position?.latitude, 64 | _position?.longitude, 65 | ); 66 | state = state.copyWith(places: places, loading: false, error: null); 67 | } catch (error, stack) { 68 | state = state.copyWith(error: error, loading: false); 69 | logger.e( 70 | 'AddNewPlaceViewNotifier: Error while finding place', 71 | error: error, 72 | stackTrace: stack, 73 | ); 74 | } 75 | } 76 | } 77 | 78 | @freezed 79 | class AddNewPlaceState with _$AddNewPlaceState { 80 | const factory AddNewPlaceState({ 81 | @Default(false) loading, 82 | @Default(false) isNetworkOff, 83 | @Default([]) List places, 84 | Object? error, 85 | }) = _AddNewPlaceState; 86 | } 87 | -------------------------------------------------------------------------------- /app/lib/ui/flow/geofence/add/components/place_marker.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | import 'package:style/extenstions/context_extenstions.dart'; 5 | 6 | import '../../../../../gen/assets.gen.dart'; 7 | 8 | class PlaceMarker extends StatelessWidget { 9 | final double radius; 10 | 11 | const PlaceMarker({super.key, required this.radius}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Stack( 16 | alignment: Alignment.center, 17 | children: [ 18 | ClipRect( 19 | child: OverflowBox( 20 | maxHeight: radius, 21 | maxWidth: radius, 22 | child: AnimatedContainer( 23 | duration: const Duration(milliseconds: 300), 24 | width: radius, 25 | height: radius, 26 | decoration: BoxDecoration( 27 | borderRadius: BorderRadius.circular(radius), 28 | color: context.colorScheme.primary.withAlpha((0.5 * 255).toInt()), 29 | ), 30 | ), 31 | ), 32 | ), 33 | Container( 34 | width: 40, 35 | height: 40, 36 | decoration: BoxDecoration( 37 | borderRadius: BorderRadius.circular(30), 38 | color: context.colorScheme.onPrimary, 39 | ), 40 | child: Padding( 41 | padding: const EdgeInsets.all(4), 42 | child: SvgPicture.asset( 43 | Assets.images.icLocationFeedIcon, 44 | colorFilter: ColorFilter.mode( 45 | context.colorScheme.primary, 46 | BlendMode.srcATop, 47 | ), 48 | ), 49 | ), 50 | ), 51 | ], 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/lib/ui/flow/intro/intro_page_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:style/extenstions/context_extenstions.dart'; 4 | import 'package:style/text/app_text_dart.dart'; 5 | import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; 6 | 7 | import '../../../gen/assets.gen.dart'; 8 | 9 | const maxImageSize = 400.0; 10 | 11 | class IntroPageItem { 12 | final String title; 13 | final String subtitle; 14 | final String image; 15 | 16 | IntroPageItem({ 17 | required this.title, 18 | required this.subtitle, 19 | required this.image, 20 | }); 21 | 22 | static List generate(BuildContext context) { 23 | return [ 24 | IntroPageItem( 25 | title: context.l10n.intro_1_title, 26 | subtitle: context.l10n.intro_1_subTitle, 27 | image: Assets.images.intro1), 28 | IntroPageItem( 29 | title: context.l10n.intro_2_title, 30 | subtitle: context.l10n.intro_2_subTitle, 31 | image: Assets.images.intro2), 32 | IntroPageItem( 33 | title: context.l10n.intro_3_title, 34 | subtitle: context.l10n.intro_3_subTitle, 35 | image: Assets.images.intro3), 36 | ]; 37 | } 38 | } 39 | 40 | class IntroPageWidget extends StatelessWidget { 41 | final IntroPageItem item; 42 | 43 | const IntroPageWidget({super.key, required this.item}); 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | final screenSize = MediaQuery.of(context).size.width; 48 | final size = screenSize > maxImageSize ? maxImageSize : screenSize - 32; 49 | 50 | return Padding( 51 | padding: const EdgeInsets.symmetric(horizontal: 16), 52 | child: Center( 53 | child: SingleChildScrollView( 54 | child: Column( 55 | crossAxisAlignment: CrossAxisAlignment.center, 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | children: [ 58 | const SizedBox(height: 24), 59 | Text( 60 | item.title, 61 | style: AppTextStyle.header1 62 | .copyWith(color: context.colorScheme.textPrimary), 63 | ), 64 | const SizedBox(height: 24), 65 | SvgPicture.asset(item.image, width: size, height: size), 66 | const SizedBox(height: 16), 67 | Text( 68 | item.subtitle, 69 | textAlign: TextAlign.center, 70 | style: AppTextStyle.subtitle1 71 | .copyWith(color: context.colorScheme.textSecondary), 72 | ), 73 | const SizedBox(height: 24), 74 | ], 75 | ), 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/lib/ui/flow/journey/calender/horizontal_calendar_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:style/extenstions/date_extenstions.dart'; 4 | 5 | part 'horizontal_calendar_view_model.freezed.dart'; 6 | 7 | final horizontalCalendarViewStateProvider = StateNotifierProvider.autoDispose< 8 | HorizontalCalendarViewModel, CalendarViewState>((ref) { 9 | return HorizontalCalendarViewModel(); 10 | }); 11 | 12 | class HorizontalCalendarViewModel extends StateNotifier { 13 | HorizontalCalendarViewModel() 14 | : super( 15 | CalendarViewState( 16 | weekStartDate: DateTime.now().startOfDay, 17 | selectedDate: DateTime.now().startOfDay, 18 | ), 19 | ) { 20 | setCurrentWeekStartDate(); 21 | } 22 | 23 | void setCurrentWeekStartDate() { 24 | final currentDate = state.selectedDate; 25 | // Monday 26 | final dayOfWeek = currentDate.weekday; 27 | final daysToSubtract = dayOfWeek == 1 ? 0 : dayOfWeek - 1; 28 | final currentWeekStartDate = 29 | currentDate.subtract(Duration(days: daysToSubtract)); 30 | state = state.copyWith(weekStartDate: currentWeekStartDate); 31 | } 32 | 33 | void onSwipeWeek(int direction) { 34 | final currentWeekStartDate = state.weekStartDate; 35 | final newWeekStartDate = 36 | currentWeekStartDate.add(Duration(days: direction * 7)); 37 | if (newWeekStartDate.isAfter(DateTime.now().startOfDay)) { 38 | return; 39 | } 40 | state = state.copyWith(weekStartDate: newWeekStartDate); 41 | setContainsToday(); 42 | } 43 | 44 | void setSelectedDate(DateTime? date, {bool isPickerDate = false}) { 45 | state = state.copyWith( 46 | selectedDate: date?.startOfDay ?? DateTime.now().startOfDay); 47 | if (isPickerDate && date != null) { 48 | onSelectDateFromDatePicker(date); 49 | } 50 | } 51 | 52 | void onSelectDateFromDatePicker(DateTime date) { 53 | state = state.copyWith(weekStartDate: date.startOfWeek); 54 | setContainsToday(); 55 | } 56 | 57 | void setContainsToday() { 58 | final DateTime today = DateTime.now(); 59 | final DateTime endOfWeek = state.weekStartDate.add(const Duration(days: 6)); 60 | 61 | final containsToday = 62 | today.isAfter(state.weekStartDate) && today.isBefore(endOfWeek); 63 | state = state.copyWith(containsToday: containsToday); 64 | } 65 | 66 | void goToToday() { 67 | setSelectedDate(DateTime.now()); 68 | setCurrentWeekStartDate(); 69 | setContainsToday(); 70 | } 71 | } 72 | 73 | @freezed 74 | class CalendarViewState with _$CalendarViewState { 75 | const factory CalendarViewState({ 76 | required DateTime selectedDate, 77 | required DateTime weekStartDate, 78 | @Default(true) bool containsToday, 79 | }) = _CalendarViewState; 80 | } 81 | -------------------------------------------------------------------------------- /app/lib/ui/flow/onboard/connection_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:style/button/bottom_sticky_overlay.dart'; 4 | import 'package:style/button/primary_button.dart'; 5 | import 'package:style/button/secondary_button.dart'; 6 | import 'package:style/extenstions/context_extenstions.dart'; 7 | import 'package:style/text/app_text_dart.dart'; 8 | import 'package:yourspace_flutter/domain/extenstions/context_extenstions.dart'; 9 | import 'package:yourspace_flutter/ui/components/app_page.dart'; 10 | import 'package:yourspace_flutter/ui/flow/navigation/routes.dart'; 11 | 12 | import '../../../gen/assets.gen.dart'; 13 | 14 | class ConnectionScreen extends StatefulWidget { 15 | const ConnectionScreen({super.key}); 16 | 17 | @override 18 | State createState() => _ConnectionScreenState(); 19 | } 20 | 21 | class _ConnectionScreenState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | return AppPage( 25 | body: _body(context), 26 | ); 27 | } 28 | 29 | Widget _body(BuildContext context) { 30 | return Center( 31 | child: Stack(children: [ 32 | ListView( 33 | children: [ 34 | const SizedBox(height: 40), 35 | Padding( 36 | padding: const EdgeInsets.symmetric(horizontal: 16), 37 | child: Text( 38 | context.l10n.connection_share_title, 39 | style: AppTextStyle.header3.copyWith( 40 | color: context.colorScheme.textPrimary, 41 | ), 42 | textAlign: TextAlign.center, 43 | ), 44 | ), 45 | const SizedBox(height: 40), 46 | SvgPicture.asset(Assets.images.connection), 47 | const SizedBox(height: 40), 48 | Padding( 49 | padding: const EdgeInsets.symmetric(horizontal: 16), 50 | child: Text( 51 | context.l10n.connection_share_subtitle, 52 | style: AppTextStyle.subtitle1.copyWith( 53 | color: context.colorScheme.textSecondary, 54 | ), 55 | textAlign: TextAlign.center, 56 | ), 57 | ) 58 | ]), 59 | _continueAndSkipButton(context), 60 | ]), 61 | ); 62 | } 63 | 64 | Widget _continueAndSkipButton(BuildContext context) { 65 | return BottomStickyOverlay( 66 | child: Column(children: [ 67 | PrimaryButton( 68 | context.l10n.connection_continue_title, 69 | onPressed: () { 70 | const JoinSpaceRoute(fromOnboard: true).go(context); 71 | }, 72 | ), 73 | const SizedBox(height: 16), 74 | SecondaryButton( 75 | context.l10n.common_skip, 76 | onPressed: () { 77 | HomeRoute().go(context); 78 | }, 79 | ) 80 | ]), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/lib/ui/flow/onboard/pick_name_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/api/auth/auth_models.dart'; 2 | import 'package:data/log/logger.dart'; 3 | import 'package:data/service/auth_service.dart'; 4 | import 'package:data/storage/app_preferences.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:freezed_annotation/freezed_annotation.dart'; 8 | 9 | part 'pick_name_view_model.freezed.dart'; 10 | 11 | final pickNameStateNotifierProvider = StateNotifierProvider.autoDispose< 12 | PickNameStateNotifier, PickNameState>((ref) { 13 | return PickNameStateNotifier( 14 | ref.watch(authServiceProvider), 15 | ref.watch(currentUserPod), 16 | ); 17 | }); 18 | 19 | class PickNameStateNotifier extends StateNotifier { 20 | final AuthService _authService; 21 | final ApiUser? user; 22 | 23 | PickNameStateNotifier(this._authService, this.user) 24 | : super(PickNameState(firstName: TextEditingController(), lastName: TextEditingController(), user: user!)); 25 | 26 | void enableNextButton() { 27 | state = state.copyWith(enableBtn: state.firstName.text.isNotEmpty && state.firstName.text.length >= 3); 28 | } 29 | 30 | Future saveUser(ApiUser user) async { 31 | try { 32 | state = state.copyWith(savingUser: true, error: null); 33 | final newUser = user.copyWith(first_name: state.firstName.text, last_name: state.lastName.text); 34 | _authService.updateCurrentUser(newUser); 35 | state = state.copyWith(savingUser: false, saved: true, error: null); 36 | } catch (error, stack) { 37 | state = state.copyWith(savingUser: false, error: error); 38 | logger.e( 39 | 'PickNameStateNotifier: error while save user', 40 | error: error, 41 | stackTrace: stack, 42 | ); 43 | } 44 | } 45 | } 46 | 47 | @freezed 48 | class PickNameState with _$PickNameState { 49 | const factory PickNameState({ 50 | @Default(false) bool enableBtn, 51 | @Default(false) bool savingUser, 52 | @Default(false) bool saved, 53 | Object? error, 54 | required TextEditingController firstName, 55 | required TextEditingController lastName, 56 | required ApiUser user, 57 | }) = _PickNameState; 58 | } 59 | -------------------------------------------------------------------------------- /app/lib/ui/flow/setting/space/admin/change_admin_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/api/auth/auth_models.dart'; 2 | import 'package:data/api/space/space_models.dart'; 3 | import 'package:data/log/logger.dart'; 4 | import 'package:data/service/space_service.dart'; 5 | import 'package:data/storage/app_preferences.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:freezed_annotation/freezed_annotation.dart'; 8 | 9 | part 'change_admin_view_model.freezed.dart'; 10 | 11 | final changeAdminViewStateProvider = StateNotifierProvider.autoDispose< 12 | ChangAdminViewNotifier, ChangeAdminViewState>( 13 | (ref) => ChangAdminViewNotifier( 14 | ref.read(spaceServiceProvider), 15 | ref.read(currentUserPod), 16 | ), 17 | ); 18 | 19 | class ChangAdminViewNotifier extends StateNotifier { 20 | final SpaceService spaceService; 21 | final ApiUser? user; 22 | 23 | ChangAdminViewNotifier(this.spaceService, this.user) 24 | : super(const ChangeAdminViewState()) { 25 | state = state.copyWith(currentUserId: user?.id ?? ''); 26 | } 27 | 28 | void updateSpaceAdmin(ApiSpace space) async { 29 | try { 30 | state = state.copyWith(adminIdChanged: false, saving: true); 31 | await spaceService.updateSpace(space.copyWith(admin_id: state.newAdminId)); 32 | state = state.copyWith(adminIdChanged: true, error: null, saving: false); 33 | } catch (error, stack) { 34 | state = state.copyWith(error: error); 35 | logger.e('ChangeAdminViewNotifier: error while update space admin id', 36 | error: error, stackTrace: stack); 37 | } 38 | } 39 | 40 | void updateNewAdminId(String id) { 41 | state = state.copyWith(newAdminId: id, allowSave: true); 42 | } 43 | } 44 | 45 | @freezed 46 | class ChangeAdminViewState with _$ChangeAdminViewState { 47 | const factory ChangeAdminViewState({ 48 | @Default(false) bool allowSave, 49 | @Default(false) bool saving, 50 | @Default(false) bool adminIdChanged, 51 | @Default('') String newAdminId, 52 | @Default('') String currentUserId, 53 | Object? error, 54 | }) = _ChangeAdminViewState; 55 | } 56 | -------------------------------------------------------------------------------- /app/lib/ui/flow/setting/subscription/subscription_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:data/api/subscription/subscription_models.dart'; 4 | 5 | part 'subscription_view_model.freezed.dart'; 6 | 7 | const freePlan = "free_plan"; 8 | const proPlan = "pro_plan"; 9 | 10 | final subscriptionViewProvider = StateNotifierProvider.autoDispose< 11 | SubscriptionViewNotifier, SubscriptionState>( 12 | (ref) => SubscriptionViewNotifier(), 13 | ); 14 | 15 | class SubscriptionViewNotifier extends StateNotifier { 16 | SubscriptionViewNotifier() : super(const SubscriptionState()) { 17 | _setData(); 18 | } 19 | 20 | void _setData() { 21 | final plans = [ 22 | const SubscriptionPlan( 23 | id: freePlan, 24 | name: "Free", 25 | planDetail: "100 call/month", 26 | planInfo: "Basic plan"), 27 | const SubscriptionPlan( 28 | id: proPlan, 29 | name: "Pro", 30 | planDetail: "\u{20B9}200 /month", 31 | planInfo: "Unlimited or higher threshold") 32 | ]; 33 | state = state.copyWith(plans: plans, selectedPlan: plans.first); 34 | } 35 | 36 | void onSelectPlan(SubscriptionPlan plan) { 37 | state = state.copyWith(selectedPlan: plan); 38 | } 39 | } 40 | 41 | @freezed 42 | class SubscriptionState with _$SubscriptionState { 43 | const factory SubscriptionState({ 44 | @Default(false) bool loading, 45 | SubscriptionPlan? selectedPlan, 46 | @Default([]) List plans, 47 | }) = _SubscriptionState; 48 | } 49 | -------------------------------------------------------------------------------- /app/lib/ui/flow/space/create/create_space_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/log/logger.dart'; 2 | import 'package:data/service/space_service.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | 7 | part 'create_space_view_model.freezed.dart'; 8 | 9 | final createSpaceViewStateProvider = StateNotifierProvider.autoDispose< 10 | CreateSpaceViewNotifier, CreateSpaceViewState>((ref) { 11 | return CreateSpaceViewNotifier( 12 | ref.read(spaceServiceProvider), 13 | ); 14 | }); 15 | 16 | class CreateSpaceViewNotifier extends StateNotifier { 17 | final SpaceService spaceService; 18 | 19 | CreateSpaceViewNotifier(this.spaceService) 20 | : super( 21 | CreateSpaceViewState(spaceName: TextEditingController()), 22 | ); 23 | 24 | Future createSpace() async { 25 | try { 26 | state = state.copyWith(isCreating: true, invitationCode: '', error: null); 27 | final invitationCode = 28 | await spaceService.createSpaceAndGetInviteCode(state.spaceName.text); 29 | state = state.copyWith(isCreating: false, invitationCode: invitationCode); 30 | } catch (error, stack) { 31 | state = state.copyWith(error: error, isCreating: false); 32 | logger.e( 33 | 'CreateSpaceViewNotifier: $error - error while creating new space', 34 | error: error, 35 | stackTrace: stack, 36 | ); 37 | } 38 | } 39 | 40 | void updateSelectedSpaceName(String message) { 41 | if (message != state.selectedSpaceName) { 42 | state = state.copyWith( 43 | selectedSpaceName: message, 44 | spaceName: TextEditingController(text: message), 45 | allowSave: message.isNotEmpty, 46 | ); 47 | } else { 48 | state = state.copyWith( 49 | selectedSpaceName: '', 50 | spaceName: TextEditingController(text: ''), 51 | allowSave: false); 52 | } 53 | } 54 | 55 | void onChange() { 56 | state = state.copyWith(allowSave: state.spaceName.text.isNotEmpty); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | state.spaceName.dispose(); 62 | super.dispose(); 63 | } 64 | } 65 | 66 | @freezed 67 | class CreateSpaceViewState with _$CreateSpaceViewState { 68 | const factory CreateSpaceViewState({ 69 | @Default(false) bool allowSave, 70 | @Default(false) bool isCreating, 71 | @Default('') String selectedSpaceName, 72 | @Default('') String invitationCode, 73 | required TextEditingController spaceName, 74 | Object? error, 75 | }) = _CreateSpaceViewState; 76 | } 77 | -------------------------------------------------------------------------------- /build_watch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | function cleanup { 5 | pkill -P $$ 6 | } 7 | trap cleanup EXIT 8 | 9 | # Function to keep running a command until it succeeds 10 | function keep_running { 11 | while true; do 12 | dart run build_runner watch --delete-conflicting-outputs; 13 | echo "Command failed with no zero exit code. Respawning.." 14 | sleep 1 15 | done 16 | } 17 | 18 | # Navigate to each project directory and run the watcher in the background 19 | (cd app && keep_running) & 20 | (cd data && keep_running) & 21 | 22 | # Wait for all background processes to finish 23 | wait 24 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | 33 | # Config file 34 | ./lib/config.dart 35 | -------------------------------------------------------------------------------- /data/.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: "41456452f29d64e8deb623a3c927524bcf9f111b" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /data/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /data/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | The YourSpace data module is the central hub for managing all data operations within the YourSpace 4 | app. This module efficiently handles both local storage and remote data, ensuring a smooth and 5 | consistent flow of information. It provides services for creating, reading, updating, and deleting ( 6 | CRUD) data across Firebase and local databases (SQLite). 7 | 8 | ## Features 9 | 10 | - **Data Operations:** Supports full CRUD operations for data stored locally and remotely, enabling flexible data handling and synchronization. 11 | - **Data Sources:** Integrates with Firebase services for real-time data synchronization and cloud storage. 12 | 13 | ### API 14 | 15 | - **Service:** Manages data operations and provides methods for data retrieval and updates. 16 | - **Data Models:** Defines all the data classes used in the app. -------------------------------------------------------------------------------- /data/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | errors: 3 | depend_on_referenced_packages: ignore 4 | non_constant_identifier_names: ignore 5 | include: package:flutter_lints/flutter.yaml 6 | 7 | # Additional information about this file can be found at 8 | # https://dart.dev/guides/language/analysis-options 9 | -------------------------------------------------------------------------------- /data/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | explicit_to_json: true -------------------------------------------------------------------------------- /data/lib/api/location/journey/journey.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'journey.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$LocationJourneyImpl _$$LocationJourneyImplFromJson( 10 | Map json) => 11 | _$LocationJourneyImpl( 12 | id: json['id'] as String?, 13 | user_id: json['user_id'] as String, 14 | from_latitude: (json['from_latitude'] as num).toDouble(), 15 | from_longitude: (json['from_longitude'] as num).toDouble(), 16 | to_latitude: (json['to_latitude'] as num?)?.toDouble(), 17 | to_longitude: (json['to_longitude'] as num?)?.toDouble(), 18 | routes: (json['routes'] as List?) 19 | ?.map((e) => JourneyRoute.fromJson(e as Map)) 20 | .toList() ?? 21 | const [], 22 | route_distance: (json['route_distance'] as num?)?.toDouble(), 23 | route_duration: (json['route_duration'] as num?)?.toInt(), 24 | created_at: (json['created_at'] as num?)?.toInt(), 25 | update_at: (json['update_at'] as num?)?.toInt(), 26 | type: json['type'] as String?, 27 | ); 28 | 29 | Map _$$LocationJourneyImplToJson( 30 | _$LocationJourneyImpl instance) => 31 | { 32 | 'id': instance.id, 33 | 'user_id': instance.user_id, 34 | 'from_latitude': instance.from_latitude, 35 | 'from_longitude': instance.from_longitude, 36 | 'to_latitude': instance.to_latitude, 37 | 'to_longitude': instance.to_longitude, 38 | 'routes': instance.routes.map((e) => e.toJson()).toList(), 39 | 'route_distance': instance.route_distance, 40 | 'route_duration': instance.route_duration, 41 | 'created_at': instance.created_at, 42 | 'update_at': instance.update_at, 43 | 'type': instance.type, 44 | }; 45 | 46 | _$JourneyRouteImpl _$$JourneyRouteImplFromJson(Map json) => 47 | _$JourneyRouteImpl( 48 | latitude: (json['latitude'] as num).toDouble(), 49 | longitude: (json['longitude'] as num).toDouble(), 50 | ); 51 | 52 | Map _$$JourneyRouteImplToJson(_$JourneyRouteImpl instance) => 53 | { 54 | 'latitude': instance.latitude, 55 | 'longitude': instance.longitude, 56 | }; 57 | -------------------------------------------------------------------------------- /data/lib/api/location/location.dart: -------------------------------------------------------------------------------- 1 | //ignore_for_file: constant_identifier_names 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:geolocator/geolocator.dart'; 6 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 7 | 8 | part 'location.freezed.dart'; 9 | part 'location.g.dart'; 10 | 11 | const USER_STATE_STEADY = 0; 12 | const USER_STATE_MOVING = 1; 13 | 14 | @freezed 15 | class ApiLocation with _$ApiLocation { 16 | const ApiLocation._(); 17 | 18 | const factory ApiLocation({ 19 | required String id, 20 | required String user_id, 21 | required double latitude, 22 | required double longitude, 23 | int? user_state, 24 | int? created_at, 25 | }) = _ApiLocation; 26 | 27 | factory ApiLocation.fromJson(Map data) => 28 | _$ApiLocationFromJson(data); 29 | 30 | factory ApiLocation.fromFireStore( 31 | DocumentSnapshot> snapshot, 32 | SnapshotOptions? options) { 33 | Map? data = snapshot.data(); 34 | return ApiLocation.fromJson(data!); 35 | } 36 | 37 | Map toFireStore(ApiLocation space) => space.toJson(); 38 | } 39 | 40 | class LocationData { 41 | final double latitude; 42 | final double longitude; 43 | final DateTime timestamp; 44 | 45 | LocationData({ 46 | required this.latitude, 47 | required this.longitude, 48 | required this.timestamp, 49 | }); 50 | 51 | double distanceTo(LocationData other) { 52 | return Geolocator.distanceBetween(latitude, longitude, other.latitude, other.longitude); 53 | } 54 | } 55 | 56 | class MapTypeInfo { 57 | final MapType mapType; 58 | final int index; 59 | 60 | MapTypeInfo(this.mapType, this.index); 61 | } -------------------------------------------------------------------------------- /data/lib/api/location/location.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'location.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$ApiLocationImpl _$$ApiLocationImplFromJson(Map json) => 10 | _$ApiLocationImpl( 11 | id: json['id'] as String, 12 | user_id: json['user_id'] as String, 13 | latitude: (json['latitude'] as num).toDouble(), 14 | longitude: (json['longitude'] as num).toDouble(), 15 | user_state: (json['user_state'] as num?)?.toInt(), 16 | created_at: (json['created_at'] as num?)?.toInt(), 17 | ); 18 | 19 | Map _$$ApiLocationImplToJson(_$ApiLocationImpl instance) => 20 | { 21 | 'id': instance.id, 22 | 'user_id': instance.user_id, 23 | 'latitude': instance.latitude, 24 | 'longitude': instance.longitude, 25 | 'user_state': instance.user_state, 26 | 'created_at': instance.created_at, 27 | }; 28 | -------------------------------------------------------------------------------- /data/lib/api/message/server_timestamp_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | class ServerTimestampConverter implements JsonConverter { 5 | const ServerTimestampConverter(); 6 | 7 | @override 8 | DateTime? fromJson(Timestamp? timestamp) => timestamp?.toDate(); 9 | 10 | @override 11 | Timestamp? toJson(DateTime? date) => date != null ? Timestamp.fromDate(date) : null; 12 | } -------------------------------------------------------------------------------- /data/lib/api/network/client.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | 4 | final firestoreProvider = Provider((ref) => FirebaseFirestore.instance); 5 | -------------------------------------------------------------------------------- /data/lib/api/place/api_place.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import '../../converter/time_converter.dart'; 5 | 6 | part 'api_place.freezed.dart'; 7 | part 'api_place.g.dart'; 8 | 9 | @freezed 10 | class ApiPlace with _$ApiPlace { 11 | const ApiPlace._(); 12 | 13 | const factory ApiPlace({ 14 | required String id, 15 | required String created_by, 16 | required String space_id, 17 | required String name, 18 | required double latitude, 19 | required double longitude, 20 | @Default(200.0) double radius, 21 | @TimeStampJsonConverter() DateTime? created_at, 22 | @Default([]) List space_member_ids, 23 | }) = _ApiPlace; 24 | 25 | factory ApiPlace.fromJson(Map data) => 26 | _$ApiPlaceFromJson(data); 27 | 28 | factory ApiPlace.fromFireStore( 29 | DocumentSnapshot> snapshot, 30 | SnapshotOptions? options) { 31 | Map? data = snapshot.data(); 32 | return ApiPlace.fromJson(data!); 33 | } 34 | 35 | Map toFireStore(ApiPlace space) => space.toJson(); 36 | } 37 | 38 | @freezed 39 | class ApiPlaceMemberSetting with _$ApiPlaceMemberSetting { 40 | const ApiPlaceMemberSetting._(); 41 | 42 | const factory ApiPlaceMemberSetting({ 43 | required String user_id, 44 | required String place_id, 45 | @Default(false) bool alert_enable, 46 | @Default([]) List arrival_alert_for, 47 | @Default([]) List leave_alert_for, 48 | }) = _ApiPlaceMemberSetting; 49 | 50 | factory ApiPlaceMemberSetting.fromJson(Map data) => 51 | _$ApiPlaceMemberSettingFromJson(data); 52 | 53 | factory ApiPlaceMemberSetting.fromFireStore( 54 | DocumentSnapshot> snapshot, 55 | SnapshotOptions? options) { 56 | Map? data = snapshot.data(); 57 | return ApiPlaceMemberSetting.fromJson(data!); 58 | } 59 | 60 | Map toFireStore(ApiPlaceMemberSetting space) => 61 | space.toJson(); 62 | } 63 | 64 | @freezed 65 | class ApiNearbyPlace with _$ApiNearbyPlace { 66 | const ApiNearbyPlace._(); 67 | 68 | const factory ApiNearbyPlace({ 69 | required String id, 70 | required String name, 71 | required String formatted_address, 72 | required double lat, 73 | required double lng, 74 | }) = _ApiNearbyPlace; 75 | 76 | factory ApiNearbyPlace.fromJson(Map data) => 77 | _$ApiNearbyPlaceFromJson(data); 78 | } 79 | -------------------------------------------------------------------------------- /data/lib/api/space/space_models.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'space_models.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$ApiSpaceImpl _$$ApiSpaceImplFromJson(Map json) => 10 | _$ApiSpaceImpl( 11 | id: json['id'] as String, 12 | admin_id: json['admin_id'] as String, 13 | name: json['name'] as String, 14 | created_at: (json['created_at'] as num?)?.toInt(), 15 | ); 16 | 17 | Map _$$ApiSpaceImplToJson(_$ApiSpaceImpl instance) => 18 | { 19 | 'id': instance.id, 20 | 'admin_id': instance.admin_id, 21 | 'name': instance.name, 22 | 'created_at': instance.created_at, 23 | }; 24 | 25 | _$ApiSpaceMemberImpl _$$ApiSpaceMemberImplFromJson(Map json) => 26 | _$ApiSpaceMemberImpl( 27 | id: json['id'] as String, 28 | space_id: json['space_id'] as String, 29 | user_id: json['user_id'] as String, 30 | role: (json['role'] as num).toInt(), 31 | location_enabled: json['location_enabled'] as bool, 32 | created_at: (json['created_at'] as num?)?.toInt(), 33 | ); 34 | 35 | Map _$$ApiSpaceMemberImplToJson( 36 | _$ApiSpaceMemberImpl instance) => 37 | { 38 | 'id': instance.id, 39 | 'space_id': instance.space_id, 40 | 'user_id': instance.user_id, 41 | 'role': instance.role, 42 | 'location_enabled': instance.location_enabled, 43 | 'created_at': instance.created_at, 44 | }; 45 | 46 | _$ApiSpaceInvitationImpl _$$ApiSpaceInvitationImplFromJson( 47 | Map json) => 48 | _$ApiSpaceInvitationImpl( 49 | id: json['id'] as String, 50 | space_id: json['space_id'] as String, 51 | code: json['code'] as String, 52 | created_at: (json['created_at'] as num?)?.toInt(), 53 | ); 54 | 55 | Map _$$ApiSpaceInvitationImplToJson( 56 | _$ApiSpaceInvitationImpl instance) => 57 | { 58 | 'id': instance.id, 59 | 'space_id': instance.space_id, 60 | 'code': instance.code, 61 | 'created_at': instance.created_at, 62 | }; 63 | 64 | _$SpaceInfoImpl _$$SpaceInfoImplFromJson(Map json) => 65 | _$SpaceInfoImpl( 66 | space: ApiSpace.fromJson(json['space'] as Map), 67 | members: (json['members'] as List) 68 | .map((e) => ApiUserInfo.fromJson(e as Map)) 69 | .toList(), 70 | ); 71 | 72 | Map _$$SpaceInfoImplToJson(_$SpaceInfoImpl instance) => 73 | { 74 | 'space': instance.space.toJson(), 75 | 'members': instance.members.map((e) => e.toJson()).toList(), 76 | }; 77 | -------------------------------------------------------------------------------- /data/lib/api/subscription/subscription_models.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'subscription_models.freezed.dart'; 5 | 6 | @freezed 7 | class SubscriptionPlan with _$SubscriptionPlan { 8 | const factory SubscriptionPlan({ 9 | required String id, 10 | required String name, 11 | required String planDetail, 12 | required String planInfo 13 | }) = _SubscriptionPlan; 14 | } -------------------------------------------------------------------------------- /data/lib/api/support/api_support_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:cloud_functions/cloud_functions.dart'; 3 | import 'package:firebase_storage/firebase_storage.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:data/api/auth/auth_models.dart'; 6 | 7 | import '../../service/device_service.dart'; 8 | import '../../storage/app_preferences.dart'; 9 | 10 | final apiSupportServiceProvider = Provider((ref) => ApiSupportService( 11 | ref.read(currentUserPod), 12 | ref.read(deviceServiceProvider), 13 | )); 14 | 15 | class ApiSupportService { 16 | final ApiUser? _currentUser; 17 | final DeviceService _device; 18 | 19 | ApiSupportService(this._currentUser, this._device); 20 | 21 | Future uploadImage(File file) async { 22 | final user = _currentUser; 23 | if (user == null) return null; 24 | 25 | final storageRef = FirebaseStorage.instance; 26 | final fileName = "IMG_${DateTime.now().millisecondsSinceEpoch}.jpg"; 27 | final imageRef = 28 | storageRef.ref().child("contact_support/${user.id}/$fileName"); 29 | 30 | final uploadTask = imageRef.putFile(file); 31 | await uploadTask.whenComplete(() => null); 32 | final downloadUrl = await imageRef.getDownloadURL(); 33 | return downloadUrl; 34 | } 35 | 36 | Future submitSupportRequest(String title, String description, List attachments) async { 37 | final data = { 38 | "title": title, 39 | "description": description, 40 | "device_name": await _device.deviceName, 41 | "app_version": await _device.appVersion, 42 | "device_os": Platform.operatingSystemVersion, 43 | "user_id": _currentUser?.id, 44 | "attachments": attachments, 45 | }; 46 | 47 | final callable = FirebaseFunctions.instanceFor(region: 'asia-south1').httpsCallable('sendSupportRequest'); 48 | await callable.call(data); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /data/lib/config.dart: -------------------------------------------------------------------------------- 1 | class AppConfig { 2 | static const String placeApiKey = 'YOUR PLACE API KEY'; 3 | } 4 | -------------------------------------------------------------------------------- /data/lib/converter/time_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | class TimeStampJsonConverter extends JsonConverter { 5 | const TimeStampJsonConverter(); 6 | 7 | @override 8 | DateTime fromJson(Timestamp json) { 9 | return json.toDate(); 10 | } 11 | 12 | @override 13 | Timestamp toJson(DateTime object) => Timestamp.fromDate(object); 14 | } 15 | -------------------------------------------------------------------------------- /data/lib/domain/journey_lat_lng_entension.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 2 | 3 | import '../api/location/journey/journey.dart'; 4 | 5 | extension JourneyRouteLatLngExtension on JourneyRoute { 6 | LatLng toLatLng() { 7 | return LatLng(latitude, longitude); 8 | } 9 | } -------------------------------------------------------------------------------- /data/lib/domain/location_data_extension.dart: -------------------------------------------------------------------------------- 1 | import '../api/location/journey/journey.dart'; 2 | import '../api/location/location.dart'; 3 | 4 | extension LocationDataExtension on LocationData { 5 | JourneyRoute toJourneyRoute() { 6 | return JourneyRoute(latitude: latitude, longitude: longitude); 7 | } 8 | 9 | ApiLocationJourney toLocationJourney(String userId, String journeyId) { 10 | return ApiLocationJourney( 11 | id: journeyId, 12 | user_id: userId, 13 | from_latitude: latitude, 14 | from_longitude: longitude, 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /data/lib/feature_flags.dart: -------------------------------------------------------------------------------- 1 | 2 | class FeatureFlags { 3 | static const enableSubscription = false; 4 | } -------------------------------------------------------------------------------- /data/lib/log/logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | 3 | final logger = setupLogger(); 4 | 5 | Logger setupLogger() { 6 | final logger = Logger( 7 | filter: ProductionFilter(), 8 | ); 9 | 10 | return logger; 11 | } -------------------------------------------------------------------------------- /data/lib/network/error.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | 4 | 5 | class ApiError implements Exception { 6 | final String? message; 7 | 8 | const ApiError({this.message}); 9 | 10 | @override 11 | String toString() { 12 | return '$runtimeType(message: $message)'; 13 | } 14 | 15 | factory ApiError.fromError(Object error) { 16 | if (error is ApiError) { 17 | return error; 18 | } else if (error is TimeoutException) { 19 | return const NoInternetConnectionError(); 20 | } else if (error is String) { 21 | return StringError(error: error); 22 | } else { 23 | return GenericError(error: error); 24 | } 25 | } 26 | } 27 | 28 | class NoInternetConnectionError extends ApiError { 29 | const NoInternetConnectionError() 30 | : super( 31 | message: 32 | "No internet connection. Please check your network and try again.", 33 | ); 34 | } 35 | 36 | class GenericError extends ApiError { 37 | final Object error; 38 | final int? code; 39 | 40 | GenericError({required this.error, this.code}); 41 | 42 | @override 43 | String get message => 'Status code: $code, Error: $error'; 44 | } 45 | 46 | class StringError extends ApiError { 47 | final String error; 48 | 49 | StringError({required this.error}); 50 | 51 | @override 52 | String get message => 'Error: $error'; 53 | } 54 | -------------------------------------------------------------------------------- /data/lib/repository/geofence_repository.dart: -------------------------------------------------------------------------------- 1 | //ignore_for_file: constant_identifier_names 2 | 3 | import 'package:cloud_functions/cloud_functions.dart'; 4 | import 'package:data/log/logger.dart'; 5 | import 'package:data/service/space_service.dart'; 6 | import 'package:data/storage/app_preferences.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | import '../api/auth/auth_models.dart'; 9 | import '../service/place_service.dart'; 10 | 11 | const GEOFENCE_ENTER = 1; 12 | const GEOFENCE_EXIT = 2; 13 | 14 | final geofenceRepositoryProvider = Provider((ref) => GeofenceRepository( 15 | ref.read(placeServiceProvider), 16 | ref.read(spaceServiceProvider), 17 | ref.read(currentUserPod), 18 | )); 19 | 20 | class GeofenceRepository { 21 | final PlaceService placeService; 22 | final SpaceService spaceService; 23 | final ApiUser? _currentUser; 24 | 25 | GeofenceRepository(this.placeService, this.spaceService, this._currentUser); 26 | 27 | void makeHttpCall(String placeId, int status) async { 28 | try { 29 | final place = await placeService.getPlace(placeId); 30 | if (place != null) { 31 | final message = status == GEOFENCE_ENTER 32 | ? '${_currentUser?.first_name ?? ''} has arrived at 📍${place.name}' 33 | : '${_currentUser?.first_name ?? ''} has left 📍${place.name}'; 34 | 35 | final data = { 36 | 'placeId': placeId, 37 | 'spaceId': place.space_id, 38 | 'eventBy': _currentUser?.id ?? '', 39 | 'message': message, 40 | 'eventType': status == GEOFENCE_ENTER ? "1" : "2", 41 | }; 42 | 43 | final callable = FirebaseFunctions.instanceFor(region: 'asia-south1') 44 | .httpsCallable('sendGeoFenceNotification'); 45 | await callable.call(data); 46 | } 47 | } catch (error, stack) { 48 | logger.e( 49 | "GeofenceService: error while getting place from place id", 50 | error: error, 51 | stackTrace: stack, 52 | ); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /data/lib/repository/journey_repository.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:cloud_firestore/cloud_firestore.dart'; 6 | import 'package:data/api/location/journey/journey.dart'; 7 | import 'package:data/api/location/location.dart'; 8 | import 'package:data/log/logger.dart'; 9 | import 'package:data/repository/journey_generator.dart'; 10 | 11 | import '../api/location/journey/api_journey_service.dart'; 12 | import '../storage/location_caches.dart'; 13 | 14 | class JourneyRepository { 15 | static JourneyRepository? _instance; 16 | 17 | final ApiJourneyService journeyService; 18 | final LocationCache locationCache = LocationCache.instance; 19 | 20 | JourneyRepository(this.journeyService); 21 | 22 | static JourneyRepository get instance { 23 | _instance ??= JourneyRepository( 24 | ApiJourneyService(FirebaseFirestore.instance), 25 | ); 26 | return _instance!; 27 | } 28 | 29 | Future saveLocationJourney({ 30 | required LocationData extractedLocation, 31 | required String userId, 32 | }) async { 33 | 34 | try { 35 | _cacheLocations(extractedLocation, userId); 36 | 37 | var lastKnownJourney = 38 | await getLastKnownLocation(userId, extractedLocation); 39 | 40 | final data = getJourney(userId, 41 | newLocation: extractedLocation, 42 | lastLocations: locationCache.getLastFiveLocations(userId), 43 | lastKnownJourney: lastKnownJourney); 44 | 45 | final updateJourney = data?.updatedJourney; 46 | if (updateJourney != null) { 47 | locationCache.putLastJourney(updateJourney, userId); 48 | await journeyService.updateJourney(userId, updateJourney); 49 | } 50 | 51 | final newJourney = data?.newJourney; 52 | if (newJourney != null) { 53 | final currentJourney = await journeyService.addJourney( 54 | newJourney: newJourney, userId: userId); 55 | locationCache.putLastJourney(currentJourney, userId); 56 | } 57 | } catch (error, stack) { 58 | logger.e( 59 | 'Journey Repository: Error while save journey, $extractedLocation', 60 | error: error, 61 | stackTrace: stack); 62 | } 63 | } 64 | 65 | Future getLastKnownLocation( 66 | String userId, LocationData? extractedLocation) async { 67 | return locationCache.getLastJourney(userId) ?? 68 | await journeyService.getLastJourneyLocation(userId); 69 | } 70 | 71 | void _cacheLocations( 72 | LocationData extractedLocation, 73 | String userId, 74 | ) { 75 | var lastFiveLocations = locationCache.getLastFiveLocations(userId).toList(); 76 | 77 | if (lastFiveLocations.length >= 5) { 78 | lastFiveLocations.removeAt(0); 79 | } 80 | lastFiveLocations.add(extractedLocation); 81 | locationCache.putLastFiveLocations(lastFiveLocations, userId); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /data/lib/service/device_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:device_info_plus/device_info_plus.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:flutter_timezone/flutter_timezone.dart'; 6 | import 'package:package_info_plus/package_info_plus.dart'; 7 | 8 | import '../storage/app_preferences.dart'; 9 | 10 | final deviceServiceProvider = 11 | Provider((ref) => DeviceService(deviceId: ref.read(deviceIdPod))); 12 | 13 | class DeviceService { 14 | final String deviceId; 15 | 16 | DeviceService({required this.deviceId}); 17 | 18 | Future get timezone { 19 | return FlutterTimezone.getLocalTimezone(); 20 | } 21 | 22 | Future get deviceName async { 23 | final deviceInfo = await DeviceInfoPlugin().deviceInfo; 24 | if (Platform.isAndroid) { 25 | return deviceInfo.data["product"]; 26 | } else { 27 | return deviceInfo.data["name"]; 28 | } 29 | } 30 | 31 | Future get appVersion async { 32 | final packageInfo = await PackageInfo.fromPlatform(); 33 | return int.tryParse(packageInfo.buildNumber) ?? 0; 34 | } 35 | 36 | Future get version async { 37 | final packageInfo = await PackageInfo.fromPlatform(); 38 | return packageInfo.version; 39 | } 40 | 41 | Future get packageName async { 42 | final packageInfo = await PackageInfo.fromPlatform(); 43 | return packageInfo.packageName; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /data/lib/service/geofence_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:data/log/logger.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | import '../api/place/api_place.dart'; 8 | 9 | final geofenceServiceProvider = Provider((ref) => GeofenceService()); 10 | 11 | class GeofenceService { 12 | static const MethodChannel _channel = MethodChannel('geofence_plugin'); 13 | 14 | static Future startMonitoring(List places) async { 15 | try { 16 | if (Platform.isAndroid) { 17 | startAndroidMonitoring(places); 18 | } else { 19 | for (final place in places) { 20 | await _channel.invokeMethod('startMonitoring', { 21 | 'latitude': place.latitude, 22 | 'longitude': place.longitude, 23 | 'radius': place.radius, 24 | 'identifier': place.id, 25 | }); 26 | } 27 | } 28 | } catch (error, stack) { 29 | logger.e( 30 | 'GeofenceService: error while start monitoring geofence places', 31 | error: error, 32 | stackTrace: stack, 33 | ); 34 | } 35 | } 36 | 37 | static Future startAndroidMonitoring(List places) async { 38 | final locations = places 39 | .map((place) => { 40 | 'latitude': place.latitude, 41 | 'longitude': place.longitude, 42 | 'radius': place.radius, 43 | 'identifier': place.id, 44 | }) 45 | .toList(); 46 | 47 | await _channel.invokeMethod('startMonitoring', {'locations': locations}); 48 | } 49 | 50 | static Future stopMonitoring(String id) async { 51 | try { 52 | await _channel.invokeMethod('stopMonitoring', {'identifier': id}); 53 | } catch (error, stack) { 54 | logger.e( 55 | 'GeofenceService: error while stop monitoring geofence place', 56 | error: error, 57 | stackTrace: stack, 58 | ); 59 | } 60 | } 61 | 62 | static void setGeofenceCallback({ 63 | required Function(String) onEnter, 64 | required Function(String) onExit, 65 | }) { 66 | _channel.setMethodCallHandler((call) async { 67 | switch (call.method) { 68 | case 'onEnterGeofence': 69 | onEnter(call.arguments['identifier']); 70 | logger 71 | .d('Entered in geofence place: ${call.arguments['identifier']}'); 72 | break; 73 | case 'onExitGeofence': 74 | onExit(call.arguments['identifier']); 75 | if (!Platform.isAndroid) { 76 | stopMonitoring(call.arguments['identifier']); 77 | } 78 | logger.d('Exited in geofence place: ${call.arguments['identifier']}'); 79 | break; 80 | } 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /data/lib/service/location_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | 6 | import '../api/auth/auth_models.dart'; 7 | import '../api/location/location.dart'; 8 | import '../api/network/client.dart'; 9 | 10 | final locationServiceProvider = Provider((ref) => LocationService( 11 | ref.read(firestoreProvider), 12 | )); 13 | 14 | class LocationService { 15 | final FirebaseFirestore _db; 16 | 17 | LocationService(this._db); 18 | 19 | CollectionReference get _userRef => 20 | _db.collection("users").withConverter( 21 | fromFirestore: ApiUser.fromFireStore, 22 | toFirestore: (user, options) => user.toJson()); 23 | 24 | CollectionReference _locationRef(String userId) => _userRef 25 | .doc(userId) 26 | .collection("user_locations") 27 | .withConverter( 28 | fromFirestore: ApiLocation.fromFireStore, 29 | toFirestore: (location, _) => location.toJson()); 30 | 31 | Stream?> getCurrentLocationStream(String userId) { 32 | return _locationRef(userId) 33 | .where("user_id", isEqualTo: userId) 34 | .orderBy('created_at', descending: true) 35 | .limit(1) 36 | .snapshots() 37 | .map((snapshot) { 38 | if (snapshot.docs.isNotEmpty) { 39 | return snapshot.docs.map((doc) => doc.data() as ApiLocation).toList(); 40 | } 41 | return null; 42 | }); 43 | } 44 | 45 | Future getCurrentLocation(String userId) async { 46 | var snapshot = await _locationRef(userId) 47 | .where("user_id", isEqualTo: userId) 48 | .orderBy('created_at', descending: true) 49 | .limit(1) 50 | .get(); 51 | 52 | if (snapshot.docs.isNotEmpty) { 53 | return snapshot.docs.map((doc) => doc.data() as ApiLocation).first; 54 | } 55 | return null; 56 | } 57 | 58 | Future saveCurrentLocation( 59 | String userId, 60 | LocationData locationData, 61 | ) async { 62 | final docRef = _locationRef(userId).doc(); 63 | 64 | final location = ApiLocation( 65 | id: docRef.id, 66 | user_id: userId, 67 | latitude: locationData.latitude, 68 | longitude: locationData.longitude, 69 | created_at: DateTime.now().millisecondsSinceEpoch); 70 | 71 | await docRef.set(location); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /data/lib/service/message_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/api/auth/api_user_service.dart'; 2 | import 'package:data/api/message/api_message_service.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import '../api/auth/auth_models.dart'; 6 | import '../api/message/message_models.dart'; 7 | import '../storage/app_preferences.dart'; 8 | 9 | final messageServiceProvider = Provider((ref) => MessageService( 10 | ref.read(currentUserPod), 11 | ref.read(apiMessageServiceProvider), 12 | ref.read(currentSpaceId.notifier), 13 | ref.read(apiUserServiceProvider), 14 | )); 15 | 16 | class MessageService { 17 | final ApiUser? currentUser; 18 | final ApiMessageService messageService; 19 | final StateController _currentSpaceIdController; 20 | final ApiUserService userService; 21 | 22 | MessageService( 23 | this.currentUser, 24 | this.messageService, 25 | this._currentSpaceIdController, 26 | this.userService, 27 | ); 28 | 29 | String? get currentSpaceId => _currentSpaceIdController.state; 30 | 31 | Future createThread(String spaceId, String adminId, List memberIds) async { 32 | final threadId = await messageService.createThread(spaceId: spaceId, adminId: adminId, memberIds: memberIds); 33 | return threadId; 34 | } 35 | 36 | Future generateMessage({required String threadId, required String senderId, required String message}) async { 37 | return messageService.generateMessage(threadId: threadId, senderId: senderId, message: message); 38 | } 39 | 40 | Future sendMessage(ApiThreadMessage message) async { 41 | return messageService.sendMessage(message); 42 | } 43 | 44 | Stream> getThreads(String spaceId) { 45 | return messageService.getThreads(spaceId, currentUser?.id ?? ''); 46 | } 47 | 48 | Future getThread(String threadId) async { 49 | return await messageService.getThread(threadId).first; 50 | } 51 | 52 | Stream streamThread(String threadId) { 53 | return messageService.getThread(threadId); 54 | } 55 | 56 | Future> getMessages(String threadId, DateTime? from, {int limit = 20}) async { 57 | return messageService.getMessages(threadId, from, limit: limit); 58 | } 59 | 60 | Stream> getLatestMessages(String threadId, {int limit = 20}) { 61 | return messageService.streamLatestMessages(threadId, limit: limit); 62 | } 63 | 64 | Future> getLatestMessageMember(ApiThread thread) { 65 | return messageService.getLatestMessagesMembers(thread); 66 | } 67 | 68 | Future addThreadSeenBy(String threadId, String userId) async { 69 | messageService.addThreadSeenBy(threadId, userId); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /data/lib/service/permission_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:geolocator/geolocator.dart'; 5 | import 'package:permission_handler/permission_handler.dart'; 6 | 7 | final permissionServiceProvider = Provider((ref) => const PermissionService()); 8 | 9 | class PermissionService { 10 | const PermissionService(); 11 | 12 | Future isLocationPermissionGranted() async { 13 | return await Permission.location.isGranted; 14 | } 15 | 16 | Future requestLocationPermission() async { 17 | return await Permission.location.request(); 18 | } 19 | Future requestLocationPermissionStatus() async { 20 | return await Permission.location.status; 21 | } 22 | 23 | Future isBackgroundLocationPermissionGranted() async { 24 | return await Permission.locationAlways.isGranted; 25 | } 26 | 27 | Future requestBackgroundLocationPermission() async { 28 | return await requestPermission(Permission.locationAlways); 29 | } 30 | 31 | Future hasNotificationPermission() async { 32 | return await Permission.notification.isGranted; 33 | } 34 | 35 | Future requestNotificationPermission() async { 36 | return await requestPermission(Permission.notification); 37 | } 38 | 39 | Future isBatteryOptimizationEnabled() async { 40 | return await Permission.ignoreBatteryOptimizations.isGranted; 41 | } 42 | 43 | Future requestIgnoreBatteryOptimizations() async { 44 | if (Platform.isAndroid) await Permission.ignoreBatteryOptimizations.request(); 45 | } 46 | 47 | Future isLocationAlwaysEnabled() async { 48 | final isLocationEnabled = await isLocationPermissionGranted(); 49 | final isBackgroundEnabled = await isBackgroundLocationPermissionGranted(); 50 | return (isLocationEnabled && isBackgroundEnabled); 51 | } 52 | 53 | Future isLocationServiceEnabled() async { 54 | return await Geolocator.isLocationServiceEnabled(); 55 | } 56 | 57 | Future requestPermission(Permission permission) async { 58 | final status = await permission.status; 59 | 60 | if (status.isDenied) { 61 | final newStatus = await permission.request(); 62 | return newStatus.isGranted; 63 | } else if (status.isPermanentlyDenied) { 64 | await openAppSettings(); 65 | return false; 66 | } else { 67 | return status.isGranted; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /data/lib/storage/app_preferences.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:data/storage/preferences_provider.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:uuid/uuid.dart'; 6 | 7 | import '../api/auth/auth_models.dart'; 8 | 9 | final isIntroScreenShownPod = createPrefProvider( 10 | prefKey: "show_intro_screen", 11 | defaultValue: false, 12 | ); 13 | 14 | final isOnboardingShownPod = createPrefProvider( 15 | prefKey: "show_onboard_screen", 16 | defaultValue: false, 17 | ); 18 | 19 | final deviceIdPod = createPrefProvider( 20 | prefKey: "unique_device_id", 21 | defaultValue: const Uuid().v4(), 22 | ); 23 | 24 | final googleMapType = createPrefProvider( 25 | prefKey: "google_map_type", 26 | defaultValue: 'Normal', 27 | ); 28 | 29 | final currentUserJsonPod = createPrefProvider( 30 | prefKey: "user_account", 31 | defaultValue: null, 32 | ); 33 | 34 | final currentUserSessionJsonPod = createPrefProvider( 35 | prefKey: "user_session", 36 | defaultValue: null, 37 | ); 38 | 39 | final currentUserSessionPod = Provider((ref) { 40 | final json = ref.watch(currentUserSessionJsonPod); 41 | return json == null ? null : ApiSession.fromJson(jsonDecode(json)); 42 | }); 43 | 44 | final currentUserPod = Provider((ref) { 45 | final json = ref.watch(currentUserJsonPod); 46 | return json == null ? null : ApiUser.fromJson(jsonDecode(json)); 47 | }); 48 | 49 | final hasUserSession = 50 | Provider((ref) => ref.watch(currentUserPod) != null); 51 | 52 | final currentSpaceId = createPrefProvider( 53 | prefKey: "current_space_id", 54 | defaultValue: null, 55 | ); 56 | 57 | final lastBatteryDialogPod = createPrefProvider( 58 | prefKey: 'show_battery_dialog', 59 | defaultValue: null, 60 | ); -------------------------------------------------------------------------------- /data/lib/storage/location_caches.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:data/api/location/journey/journey.dart'; 4 | import 'package:data/api/location/location.dart'; 5 | 6 | class LocationCache { 7 | final Cache _lastJourneyCache; 8 | final Cache> _lastFiveLocationCache; 9 | 10 | static LocationCache? _instance; 11 | 12 | static LocationCache get instance { 13 | _instance ??= LocationCache(); 14 | return _instance!; 15 | } 16 | 17 | LocationCache({int cacheSize = 5, int locationCacheSize = 200}) 18 | : _lastJourneyCache = Cache(cacheSize), 19 | _lastFiveLocationCache = Cache>(cacheSize); 20 | 21 | void putLastJourney(ApiLocationJourney journey, String userId) { 22 | _lastJourneyCache.put(userId, journey); 23 | } 24 | 25 | ApiLocationJourney? getLastJourney(String userId) { 26 | return _lastJourneyCache.get(userId); 27 | } 28 | 29 | void putLastFiveLocations(List locations, String userId) { 30 | _lastFiveLocationCache.put(userId, locations); 31 | } 32 | 33 | List getLastFiveLocations(String userId) { 34 | return _lastFiveLocationCache.get(userId) ?? []; 35 | } 36 | 37 | void clear() { 38 | _lastJourneyCache.clear(); 39 | _lastFiveLocationCache.clear(); 40 | } 41 | } 42 | 43 | class Cache { 44 | final int capacity; 45 | final LinkedHashMap _cache = LinkedHashMap(); 46 | 47 | Cache(this.capacity); 48 | 49 | void put(K key, V value) { 50 | if (_cache.length >= capacity) { 51 | _cache.remove(_cache.keys.first); 52 | } 53 | _cache[key] = value; 54 | } 55 | 56 | V? get(K key) { 57 | if (!_cache.containsKey(key)) return null; 58 | 59 | final value = _cache.remove(key); 60 | _cache[key] = value as V; 61 | return value; 62 | } 63 | 64 | void clear() { 65 | _cache.clear(); 66 | } 67 | 68 | Iterable> get entries => _cache.entries; 69 | } -------------------------------------------------------------------------------- /data/lib/storage/preferences_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | final sharedPreferencesProvider = 5 | Provider((ref) => throw UnimplementedError()); 6 | 7 | StateProvider createPrefProvider({ 8 | required String prefKey, 9 | required T defaultValue, 10 | }) { 11 | return StateProvider((ref) { 12 | final prefs = ref.watch(sharedPreferencesProvider); 13 | 14 | final currentValue = prefs.get(prefKey) as T? ?? defaultValue; 15 | 16 | ref.listenSelf((prev, curr) { 17 | if (curr == null) { 18 | prefs.remove(prefKey); 19 | } else if (curr is String) { 20 | prefs.setString(prefKey, curr); 21 | } else if (curr is bool) { 22 | prefs.setBool(prefKey, curr); 23 | } else if (curr is int) { 24 | prefs.setInt(prefKey, curr); 25 | } else if (curr is double) { 26 | prefs.setDouble(prefKey, curr); 27 | } else if (curr is List) { 28 | prefs.setStringList(prefKey, curr); 29 | } 30 | }); 31 | return currentValue; 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /data/lib/utils/location_converters.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:data/log/logger.dart'; 4 | 5 | import '../api/location/journey/journey.dart'; 6 | import '../api/location/location.dart'; 7 | 8 | class LocationConverters { 9 | // Convert JSON string to List 10 | static List? locationListFromString(String value) { 11 | try { 12 | final parsed = jsonDecode(value).cast>(); 13 | return parsed 14 | .map((json) => ApiLocation.fromJson(json)) 15 | .toList(); 16 | } catch (error) { 17 | logger.e('Error converting location list from string', error: error); 18 | return null; 19 | } 20 | } 21 | 22 | // Convert List to JSON string 23 | static String locationListToString(List list) { 24 | try { 25 | return jsonEncode(list.map((location) => location?.toJson()).toList()); 26 | } catch (error) { 27 | logger.e('Error converting location list to string', error: error); 28 | return ''; 29 | } 30 | } 31 | 32 | // Convert JSON string to LocationJourney 33 | static ApiLocationJourney? journeyFromString(String value) { 34 | try { 35 | final parsed = jsonDecode(value); 36 | return ApiLocationJourney.fromJson(parsed); 37 | } catch (error) { 38 | logger.e('Error converting journey from string', error: error); 39 | return null; 40 | } 41 | } 42 | 43 | // Convert LocationJourney to JSON string 44 | static String journeyToString(ApiLocationJourney? journey) { 45 | try { 46 | return jsonEncode(journey?.toJson()); 47 | } catch (error) { 48 | logger.e('Error converting journey to string', error: error); 49 | return ''; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /data/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: data 2 | description: "A new Flutter package project." 3 | version: 0.0.1 4 | 5 | environment: 6 | sdk: '>=3.2.6 <4.0.0' 7 | flutter: ">=1.17.0" 8 | 9 | dependencies: 10 | cloud_firestore: ^5.5.0 11 | firebase_auth: ^5.3.3 12 | cloud_functions: ^5.1.5 13 | firebase_messaging: ^15.1.5 14 | firebase_storage: ^12.3.6 15 | flutter: 16 | sdk: flutter 17 | flutter_riverpod: 2.5.3 18 | freezed_annotation: ^2.4.1 19 | json_annotation: ^4.8.1 20 | shared_preferences: ^2.0.18 21 | 22 | google_sign_in: ^6.1.6 23 | device_info_plus: ^9.1.2 24 | flutter_timezone: ^1.0.8 25 | package_info_plus: ^8.0.0 26 | uuid: ^4.4.0 27 | permission_handler: ^11.3.1 28 | geolocator: ^12.0.0 29 | flutter_background_service: ^5.0.6 30 | sqflite: ^2.3.3+1 31 | path: ^1.8.3 32 | path_provider: ^2.0.11 33 | google_maps_flutter: ^2.2.8 34 | connectivity_plus: ^6.0.5 35 | battery_plus: ^6.2.0 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | flutter_lints: ^2.0.0 41 | build_runner: ^2.4.8 42 | riverpod_lint: ^2.3.9 43 | freezed: ^2.4.7 44 | json_serializable: ^6.7.1 45 | 46 | # logging 47 | logger: ^2.3.0 48 | 49 | # For information on the generic Dart part of this file, see the 50 | # following page: https://dart.dev/tools/pub/pubspec 51 | 52 | # The following section is specific to Flutter packages. 53 | 54 | # To add assets to your package, add an assets section, like this: 55 | # assets: 56 | # - images/a_dot_burr.jpeg 57 | # - images/a_dot_ham.jpeg 58 | # 59 | # For details regarding assets in packages, see 60 | # https://flutter.dev/assets-and-images/#from-packages 61 | # 62 | # An image asset can refer to one or more resolution-specific "variants", see 63 | # https://flutter.dev/assets-and-images/#resolution-aware 64 | 65 | # To add custom fonts to your package, add a fonts section here, 66 | # in this "flutter" section. Each entry in this list should have a 67 | # "family" key with the font family name, and a "fonts" key with a 68 | # list giving the asset and other descriptors for the font. For 69 | # example: 70 | # fonts: 71 | # - family: Schyler 72 | # fonts: 73 | # - asset: fonts/Schyler-Regular.ttf 74 | # - asset: fonts/Schyler-Italic.ttf 75 | # style: italic 76 | # - family: Trajan Pro 77 | # fonts: 78 | # - asset: fonts/TrajanPro.ttf 79 | # - asset: fonts/TrajanPro_Bold.ttf 80 | # weight: 700 81 | # 82 | # For details regarding fonts in packages, see 83 | # https://flutter.dev/custom-fonts/#from-packages 84 | -------------------------------------------------------------------------------- /screenshots/cover_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/cover_image.png -------------------------------------------------------------------------------- /screenshots/create_space.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/create_space.gif -------------------------------------------------------------------------------- /screenshots/cta_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/cta_banner.png -------------------------------------------------------------------------------- /screenshots/cta_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/cta_btn.png -------------------------------------------------------------------------------- /screenshots/location_tracking.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/location_tracking.gif -------------------------------------------------------------------------------- /screenshots/message.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/message.gif -------------------------------------------------------------------------------- /screenshots/place.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/screenshots/place.gif -------------------------------------------------------------------------------- /style/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /style/.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: "41456452f29d64e8deb623a3c927524bcf9f111b" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /style/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /style/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /style/README.md: -------------------------------------------------------------------------------- 1 | # Style 2 | 3 | The YourSpace style module centralizes all the styling elements of the YourSpace app. It ensures 4 | that fonts, themes, and reusable styles are consistently applied across the entire application, 5 | creating a unified and cohesive design experience. 6 | 7 | ## Features 8 | 9 | - **Fonts:** Manages the fonts used throughout the app, defining consistent typography for headings, body text, and other text elements. 10 | - **Themes:** Configures global themes, including light and dark modes, and manages the app's color schemes. 11 | - **Reusable Styles:** Provides shared styles, such as button styles, animations, and other UI elements, to maintain design consistency across the app. 12 | -------------------------------------------------------------------------------- /style/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /style/assets/fonts/inter_black_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_black_italic.ttf -------------------------------------------------------------------------------- /style/assets/fonts/inter_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_bold.ttf -------------------------------------------------------------------------------- /style/assets/fonts/inter_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_italic.ttf -------------------------------------------------------------------------------- /style/assets/fonts/inter_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_light.ttf -------------------------------------------------------------------------------- /style/assets/fonts/inter_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_medium.ttf -------------------------------------------------------------------------------- /style/assets/fonts/inter_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_regular.ttf -------------------------------------------------------------------------------- /style/assets/fonts/inter_semi_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/inter_semi_bold.ttf -------------------------------------------------------------------------------- /style/assets/fonts/kalam_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canopas/group-track-flutter/a3f02d202d28f1d20051ef9857b100633c6ba996/style/assets/fonts/kalam_bold.ttf -------------------------------------------------------------------------------- /style/lib/animation/on_tap_scale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OnTapScale extends StatefulWidget { 4 | final Widget child; 5 | final bool enabled; 6 | final void Function()? onTap; 7 | final void Function()? onLongTap; 8 | 9 | const OnTapScale({ 10 | super.key, 11 | required this.child, 12 | this.enabled = true, 13 | this.onTap, 14 | this.onLongTap, 15 | }); 16 | 17 | @override 18 | State createState() => _OnTapScaleState(); 19 | } 20 | 21 | class _OnTapScaleState extends State with TickerProviderStateMixin { 22 | double _squareScaleA = 1; 23 | 24 | AnimationController? _animationController; 25 | DateTime? _downTime; 26 | 27 | @override 28 | void initState() { 29 | _animationController = AnimationController( 30 | vsync: this, 31 | value: 1, 32 | lowerBound: 0.96, 33 | upperBound: 1, 34 | duration: const Duration(milliseconds: 100), 35 | ); 36 | 37 | _animationController?.addListener(() { 38 | setState(() { 39 | _squareScaleA = _animationController?.value ?? 1; 40 | }); 41 | }); 42 | 43 | super.initState(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return GestureDetector( 49 | behavior: HitTestBehavior.opaque, 50 | onTap: () { 51 | if (widget.enabled) widget.onTap?.call(); 52 | }, 53 | onLongPress: () { 54 | if (widget.enabled) widget.onLongTap?.call(); 55 | }, 56 | onTapDown: (dp) async { 57 | if (widget.enabled && widget.onTap != null) { 58 | _downTime = DateTime.now(); 59 | _animationController?.reverse(); 60 | } 61 | }, 62 | onTapUp: (dp) { 63 | onTapUp(); 64 | }, 65 | onTapCancel: () { 66 | onTapUp(); 67 | }, 68 | child: Transform.scale( 69 | scale: _squareScaleA, 70 | child: widget.child, 71 | ), 72 | ); 73 | } 74 | 75 | void onTapUp() async { 76 | if (_downTime != null) { 77 | final diff = _downTime! 78 | .add(const Duration(milliseconds: 100)) 79 | .difference(DateTime.now()); 80 | await Future.delayed(diff); 81 | } 82 | _animationController?.forward(); 83 | } 84 | 85 | @override 86 | void dispose() { 87 | _animationController?.dispose(); 88 | _animationController = null; 89 | super.dispose(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /style/lib/button/action_button.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import '../indicator/progress_indicator.dart'; 7 | 8 | Widget actionButton({ 9 | required BuildContext context, 10 | required void Function() onPressed, 11 | required Widget icon, 12 | bool progress = false, 13 | bool enabled = true, 14 | EdgeInsets padding = EdgeInsets.zero, 15 | }) { 16 | final tappable = !progress && enabled; 17 | 18 | if (Platform.isIOS) { 19 | return CupertinoButton( 20 | onPressed: tappable ? onPressed : null, 21 | padding: padding, 22 | child: progress 23 | ? const AppProgressIndicator(size: AppProgressIndicatorSize.small) 24 | : icon, 25 | ); 26 | } else { 27 | return IconButton( 28 | onPressed: tappable ? onPressed : null, 29 | icon: progress 30 | ? const AppProgressIndicator(size: AppProgressIndicatorSize.small) 31 | : icon, 32 | padding: padding, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /style/lib/button/bottom_sticky_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:style/extenstions/context_extenstions.dart'; 3 | 4 | class BottomStickyOverlay extends StatelessWidget { 5 | static const padding = EdgeInsets.only(bottom: 80); 6 | 7 | final Widget child; 8 | 9 | const BottomStickyOverlay({super.key, required this.child}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Align( 14 | alignment: Alignment.bottomCenter, 15 | child: Column( 16 | mainAxisSize: MainAxisSize.min, 17 | children: [ 18 | Container( 19 | padding: EdgeInsets.only( 20 | left: 16, 21 | right: 16, 22 | bottom: 16 + MediaQuery.of(context).padding.bottom, 23 | ), 24 | decoration: BoxDecoration( 25 | gradient: LinearGradient( 26 | begin: Alignment.topCenter, 27 | end: Alignment.bottomCenter, 28 | colors: [ 29 | context.colorScheme.surface.withAlpha(0), 30 | context.colorScheme.surface, 31 | context.colorScheme.surface, 32 | context.colorScheme.surface, 33 | ], 34 | ), 35 | ), 36 | child: child, 37 | ), 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /style/lib/button/icon_primary_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:style/extenstions/context_extenstions.dart'; 3 | import 'package:style/indicator/progress_indicator.dart'; 4 | 5 | class IconPrimaryButton extends StatelessWidget { 6 | final Function() onTap; 7 | final bool progress; 8 | final bool enabled; 9 | final Widget icon; 10 | final double size; 11 | final double radius; 12 | final Color? bgColor; 13 | 14 | const IconPrimaryButton({ 15 | super.key, 16 | required this.onTap, 17 | this.progress = false, 18 | this.enabled = true, 19 | required this.icon, 20 | this.bgColor, 21 | this.size = 40.0, 22 | this.radius = 30, 23 | }); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final bg = bgColor ?? context.colorScheme.containerLow; 28 | final tappable = !progress && enabled; 29 | 30 | return GestureDetector( 31 | onTap: tappable?onTap : null, 32 | child: Container( 33 | width: size, 34 | height: size, 35 | decoration: BoxDecoration( 36 | color: bg, 37 | borderRadius: BorderRadius.circular(radius), 38 | ), 39 | padding: const EdgeInsets.all(8), 40 | child: progress 41 | ? const AppProgressIndicator( 42 | size: AppProgressIndicatorSize.small, 43 | ) 44 | : icon, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /style/lib/button/large_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:style/extenstions/context_extenstions.dart'; 3 | 4 | import '../animation/on_tap_scale.dart'; 5 | import '../indicator/progress_indicator.dart'; 6 | 7 | class LargeIconButton extends StatelessWidget { 8 | final Color? backgroundColor; 9 | final Widget icon; 10 | final double size; 11 | final bool enabled; 12 | final bool progress; 13 | final void Function()? onTap; 14 | 15 | const LargeIconButton({ 16 | super.key, 17 | this.backgroundColor, 18 | this.size = 52, 19 | required this.icon, 20 | this.onTap, 21 | this.enabled = true, 22 | this.progress = false, 23 | }); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final tappable = !progress && enabled; 28 | 29 | final bg = backgroundColor ?? context.colorScheme.primary; 30 | final bgColor = tappable 31 | ? bg 32 | : Color.alphaBlend(bg.withAlpha((0.4 * 255).toInt()), context.colorScheme.surface); 33 | 34 | return OnTapScale( 35 | enabled: tappable, 36 | onTap: onTap, 37 | child: Container( 38 | height: size, 39 | width: size, 40 | decoration: BoxDecoration( 41 | shape: BoxShape.circle, 42 | color: bgColor, 43 | ), 44 | child: progress ? const AppProgressIndicator() : icon, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /style/lib/button/secondary_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:style/extenstions/context_extenstions.dart'; 3 | 4 | import '../animation/on_tap_scale.dart'; 5 | import '../text/app_text_dart.dart'; 6 | 7 | class SecondaryButton extends StatelessWidget { 8 | final String text; 9 | final EdgeInsets edgeInsets; 10 | final bool expanded; 11 | final Function()? onPressed; 12 | 13 | const SecondaryButton(this.text, { 14 | super.key, 15 | this.onPressed, 16 | this.edgeInsets = 17 | const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), 18 | this.expanded = true, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return OnTapScale( 24 | onTap: () { 25 | onPressed?.call(); 26 | }, 27 | child: Container( 28 | width: expanded ? double.infinity : null, 29 | constraints: BoxConstraints( 30 | minHeight: expanded ? 48 : 36, 31 | minWidth: 88, 32 | ), 33 | padding: edgeInsets, 34 | decoration: BoxDecoration( 35 | border: Border.all(width: 1, color: context.colorScheme.primary), 36 | borderRadius: BorderRadius.circular(24), 37 | ), 38 | child: Row( 39 | mainAxisSize: expanded ? MainAxisSize.max : MainAxisSize.min, 40 | mainAxisAlignment: MainAxisAlignment.center, 41 | children: [ 42 | Text( 43 | text, 44 | style: AppTextStyle.button.copyWith( 45 | color: context.colorScheme.primary, 46 | ), 47 | ), 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /style/lib/extenstions/column_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class ColumnBuilder { 4 | static Column separated({ 5 | required int itemCount, 6 | required Widget Function(int index) itemBuilder, 7 | required Widget Function(int index) separatorBuilder, 8 | CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, 9 | MainAxisSize mainAxisSize = MainAxisSize.max, 10 | MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, 11 | }) => 12 | Column( 13 | crossAxisAlignment: crossAxisAlignment, 14 | mainAxisSize: mainAxisSize, 15 | mainAxisAlignment: mainAxisAlignment, 16 | children: List.generate( 17 | ((itemCount * 2) - 1) < 0 ? 0 : (itemCount * 2) - 1, 18 | (index) => index.isEven 19 | ? itemBuilder(index ~/ 2) 20 | : separatorBuilder(index ~/ 2), 21 | ), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /style/lib/extenstions/context_extenstions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:style/theme/colors.dart'; 3 | import 'package:style/theme/theme.dart'; 4 | 5 | extension BuildContextExtenstions on BuildContext { 6 | AppColorScheme get colorScheme => appColorSchemeOf(this); 7 | 8 | Brightness get brightness => MediaQuery.of(this).platformBrightness; 9 | 10 | Size get mediaQuerySize => MediaQuery.of(this).size; 11 | 12 | EdgeInsets get mediaQueryPadding => MediaQuery.of(this).padding; 13 | } 14 | -------------------------------------------------------------------------------- /style/lib/indicator/progress_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:style/extenstions/context_extenstions.dart'; 6 | 7 | const _bufferTimeMillis = Duration(milliseconds: 300); 8 | 9 | enum AppProgressIndicatorSize { small, normal } 10 | 11 | class AppProgressIndicator extends StatefulWidget { 12 | final AppProgressIndicatorSize size; 13 | final Color? color; 14 | 15 | const AppProgressIndicator({ 16 | super.key, 17 | this.size = AppProgressIndicatorSize.normal, 18 | this.color, 19 | }); 20 | 21 | @override 22 | State createState() => _AppProgressIndicatorState(); 23 | } 24 | 25 | class _AppProgressIndicatorState extends State { 26 | bool _showProgress = false; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | Future.delayed(_bufferTimeMillis).then((_) { 32 | if (mounted) { 33 | setState(() => _showProgress = true); 34 | } 35 | }); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return AnimatedOpacity( 41 | opacity: _showProgress ? 1.0 : 0.0, 42 | duration: const Duration(milliseconds: 300), 43 | child: _indicator(), 44 | ); 45 | } 46 | 47 | Widget _indicator() { 48 | final radius = widget.size == AppProgressIndicatorSize.small ? 10.0 : 16.0; 49 | 50 | if (Platform.isIOS) { 51 | return CupertinoActivityIndicator( 52 | color: widget.color ?? context.colorScheme.primary, 53 | radius: radius, 54 | ); 55 | } else { 56 | return Center( 57 | child: SizedBox( 58 | width: radius * 1.8, 59 | height: radius * 1.8, 60 | child: CircularProgressIndicator( 61 | color: widget.color ?? context.colorScheme.primary, 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /style/lib/style.dart: -------------------------------------------------------------------------------- 1 | library style; 2 | 3 | /// A Calculator. 4 | class Calculator { 5 | /// Returns [value] plus 1. 6 | int addOne(int value) => value + 1; 7 | } 8 | -------------------------------------------------------------------------------- /style/lib/theme/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:style/theme/colors.dart'; 3 | 4 | class AppTheme extends InheritedWidget { 5 | final AppColorScheme colorScheme; 6 | 7 | const AppTheme({ 8 | super.key, 9 | required this.colorScheme, 10 | required super.child, 11 | }); 12 | 13 | @override 14 | bool updateShouldNotify(AppTheme oldWidget) { 15 | return colorScheme != oldWidget.colorScheme; 16 | } 17 | } 18 | 19 | AppColorScheme appColorSchemeOf(BuildContext context) { 20 | return context.dependOnInheritedWidgetOfExactType()!.colorScheme; 21 | } -------------------------------------------------------------------------------- /style/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: style 2 | description: "A new Flutter package project." 3 | version: 0.0.1 4 | homepage: any 5 | 6 | environment: 7 | sdk: '>=3.2.6 <4.0.0' 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | intl: any 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | flutter_lints: ^2.0.0 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter packages. 24 | flutter: 25 | 26 | # To add assets to your package, add an assets section, like this: 27 | # assets: 28 | # - images/a_dot_burr.jpeg 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.dev/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.dev/assets-and-images/#resolution-aware 36 | 37 | fonts: 38 | - family: Kalam 39 | fonts: 40 | - asset: assets/fonts/kalam_bold.ttf 41 | - family: Inter 42 | fonts: 43 | - asset: assets/fonts/inter_regular.ttf 44 | - asset: assets/fonts/inter_light.ttf 45 | weight: 300 46 | - asset: assets/fonts/inter_medium.ttf 47 | weight: 500 48 | - asset: assets/fonts/inter_semi_bold.ttf 49 | weight: 600 50 | - asset: assets/fonts/inter_bold.ttf 51 | weight: 700 52 | - asset: assets/fonts/inter_italic.ttf 53 | style: italic 54 | - asset: assets/fonts/inter_black_italic.ttf 55 | weight: 900 56 | style: italic 57 | -------------------------------------------------------------------------------- /style/test/style_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:style/style.dart'; 4 | 5 | void main() { 6 | test('adds one to input values', () { 7 | final calculator = Calculator(); 8 | expect(calculator.addOne(2), 3); 9 | expect(calculator.addOne(-7), -6); 10 | expect(calculator.addOne(0), 1); 11 | }); 12 | } 13 | --------------------------------------------------------------------------------