├── .example.env ├── .github └── ISSUE_TEMPLATE │ └── Junior-AI.yml ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── travel_routes │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── 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 │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── main.dart ├── models │ ├── destination.dart │ ├── direction.dart │ ├── geometry.dart │ ├── location.dart │ ├── opening_hours.dart │ ├── photo.dart │ ├── photo_image.dart │ ├── place.dart │ ├── place_autocomplete_prediction.dart │ ├── point_of_interest.dart │ ├── query_autocomplete_prediction.dart │ ├── review.dart │ └── viewport.dart ├── repositories │ ├── destination_repository.dart │ ├── destination_repository.g.dart │ ├── maps_repository.dart │ └── maps_repository.g.dart ├── screens │ ├── destination_screen.dart │ ├── error_screen.dart │ ├── home_screen.dart │ ├── loading_screen.dart │ ├── map_screen.dart │ └── points_of_interest_screen.dart ├── services │ ├── api │ │ ├── geocoding_api_client.dart │ │ ├── places_api_client.dart │ │ └── routes_api_client.dart │ └── location_service.dart └── state │ ├── notifiers │ ├── user_location_provider.dart │ └── user_location_provider.g.dart │ └── providers │ ├── all_destinations_provider.dart │ ├── all_destinations_provider.g.dart │ ├── directions_provider.dart │ ├── directions_provider.g.dart │ ├── place_autocomplete_predictions_provider.dart │ ├── place_autocomplete_predictions_provider.g.dart │ ├── place_details_provider.dart │ ├── place_details_provider.g.dart │ ├── selected_destination_provider.dart │ ├── selected_destination_provider.g.dart │ ├── user_current_location_provider.dart │ └── user_current_location_provider.g.dart ├── pubspec.lock ├── pubspec.yaml └── screenshots ├── travel_routes_1.png ├── travel_routes_2.png ├── travel_routes_3.png └── travel_routes_4.png /.example.env: -------------------------------------------------------------------------------- 1 | API_KEY=YOUR_API_KEY 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Junior-AI.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Junior AI 3 | description: Create a new task for Junior AI 4 | title: "Junior AI: " 5 | labels: ["junior-ai"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | ### (Required) What is the task for Junior AI? 11 | - type: textarea 12 | id: task 13 | attributes: 14 | label: The scope of work for Junior AI 15 | description: Please provide a description of the task that you would like Junior AI to work on. 16 | render: shell 17 | validations: 18 | required: true 19 | - type: markdown 20 | attributes: 21 | value: "
" 22 | - type: markdown 23 | attributes: 24 | value: "### (Optional) Fill the information below to give Junior AI more detailed information" 25 | - type: textarea 26 | id: snippets 27 | attributes: 28 | label: Any relevant code snippet 29 | description: Please copy and paste any relevant code snippet for the task. 30 | render: shell 31 | - type: textarea 32 | id: file_references 33 | attributes: 34 | label: Any relevant file references 35 | description: Please copy and paste the path of any relevant files from the project repository 36 | render: shell 37 | - type: dropdown 38 | id: automated_workflows 39 | attributes: 40 | label: Automated Workflows 41 | description: Do you think any of these automated workflows fit well with your current request? 42 | options: 43 | - None of the options 44 | - API Client Workflow 45 | - BLoC Workflow 46 | - Cubit Workflow 47 | - Data Model Workflow 48 | - GoRouter Workflow 49 | - Hive Workflow 50 | - Localization Workflow 51 | - Widget Breakdown Workflow 52 | - Theme Workflow 53 | default: 0 54 | validations: 55 | required: false 56 | - type: dropdown 57 | id: code_or_test 58 | attributes: 59 | label: Code or Test 60 | description: Do you want Junior AI to generate code or test for the Flutter project? 61 | options: 62 | - Not sure 63 | - Code 64 | - Test 65 | default: 0 66 | validations: 67 | required: false 68 | - type: dropdown 69 | id: create_or_edit 70 | attributes: 71 | label: Create or Edit 72 | description: Do you want Junior AI to write new code or edit any existing part of your app? 73 | options: 74 | - Not sure 75 | - Create 76 | - Edit 77 | default: 0 78 | validations: 79 | required: false 80 | -------------------------------------------------------------------------------- /.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 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | 47 | .env -------------------------------------------------------------------------------- /.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. 5 | 6 | version: 7 | revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 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: 84a1e904f44f9b0e9c4510138010edcc653163f8 17 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 18 | - platform: android 19 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 20 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 21 | - platform: ios 22 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 23 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Travel Routes App with Flutter and Google Maps 2 | 3 | The Travel Routes Flutter app integrates with Google Maps Platform using 'google_maps_flutter'. It offers users details on travel locations, points of interest, and efficient routing. Real-time geolocation is facilitated through the 'geolocator' package and Google's Geocoding API, which converts coordinates into user-friendly addresses. 4 | 5 | The app is connected with three main APIs: 6 | * The Places API provides detailed insights into locations, including photos and operating hours. 7 | * The Routes API offers precise navigation directions. 8 | * The Geocoding API translates between geographical coordinates and addresses. 9 | 10 | State management is handled with Riverpod. The UI is animated with 'flutter_animate' library. 11 | 12 | ## App Screenshots: 13 | | ![Travel Routes App 3](screenshots/travel_routes_3.png) | ![Travel Routes App 2](screenshots/travel_routes_2.png) | 14 | |:---:|:---:| 15 | | Home Screen with City Selection | Destination Screen with City Information | 16 | | ![Travel Routes App 1](screenshots/travel_routes_1.png) | ![Travel Routes App 4](screenshots/travel_routes_4.png) | 17 | | Points of Interest Screen | Map Screen with Navigation Details | 18 | 19 | 20 | ## Features: 21 | * Flutter app integrates with: 22 | * Google Maps using the `google_maps_flutter` library. 23 | * Enables geolocation services with the `geolocator` package. 24 | * Supports geocoding using the Geocoding API from Google Maps Platform (e.g., converts coordinates to addresses). 25 | * API client to connect with: 26 | * Places API --> Get places information from photos and addresses to opening hours 27 | * Routes API --> Get directions from place A to place B and more. 28 | * Geocoding API --> Convert coordinates to addresses and vice versa. 29 | * The app uses Riverpod as a state management solution with the state stored and update through providers and notifiers. 30 | * The UI is enhanced with animations using the flutter_animate library. 31 | 32 | ## Prerequisites 33 | Before you start, make sure you have the following: 34 | * Flutter 3.10 (or newer) and Dart 3.0 (or newer version) 35 | * [Google Cloud Platform account](https://console.cloud.google.com/) 36 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | namespace "com.example.travel_routes" 30 | compileSdkVersion flutter.compileSdkVersion 31 | ndkVersion flutter.ndkVersion 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | kotlinOptions { 39 | jvmTarget = '1.8' 40 | } 41 | 42 | sourceSets { 43 | main.java.srcDirs += 'src/main/kotlin' 44 | } 45 | 46 | defaultConfig { 47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 48 | applicationId "com.example.travel_routes" 49 | // You can update the following values to match your application needs. 50 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 51 | minSdkVersion 20 52 | targetSdkVersion flutter.targetSdkVersion 53 | versionCode flutterVersionCode.toInteger() 54 | versionName flutterVersionName 55 | } 56 | 57 | buildTypes { 58 | release { 59 | // TODO: Add your own signing config for the release build. 60 | // Signing with the debug keys for now, so `flutter run --release` works. 61 | signingConfig signingConfigs.debug 62 | } 63 | } 64 | } 65 | 66 | flutter { 67 | source '../..' 68 | } 69 | 70 | dependencies { 71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 72 | } 73 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 12 | 13 | 21 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/travel_routes/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.travel_routes 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '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 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - geolocator_apple (1.2.0): 4 | - Flutter 5 | - google_maps_flutter_ios (0.0.1): 6 | - Flutter 7 | - GoogleMaps (< 8.0) 8 | - GoogleMaps (5.2.0): 9 | - GoogleMaps/Maps (= 5.2.0) 10 | - GoogleMaps/Base (5.2.0) 11 | - GoogleMaps/Maps (5.2.0): 12 | - GoogleMaps/Base 13 | 14 | DEPENDENCIES: 15 | - Flutter (from `Flutter`) 16 | - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) 17 | - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) 18 | 19 | SPEC REPOS: 20 | trunk: 21 | - GoogleMaps 22 | 23 | EXTERNAL SOURCES: 24 | Flutter: 25 | :path: Flutter 26 | geolocator_apple: 27 | :path: ".symlinks/plugins/geolocator_apple/ios" 28 | google_maps_flutter_ios: 29 | :path: ".symlinks/plugins/google_maps_flutter_ios/ios" 30 | 31 | SPEC CHECKSUMS: 32 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 33 | geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401 34 | google_maps_flutter_ios: abdac20d6ce8931f6ebc5f46616df241bfaa2cfd 35 | GoogleMaps: 025272d5876d3b32604e5c080dc25eaf68764693 36 | 37 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 38 | 39 | COCOAPODS: 1.15.2 40 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import GoogleMaps 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | GMSServices.provideAPIKey("YOU_API_KEY") 12 | GeneratedPluginRegistrant.register(with: self) 13 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Travel Routes 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | travel_routes 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | NSLocationWhenInUseUsageDescription 51 | We need access to your location when using the app to provide location-based features. 52 | 53 | NSLocationAlwaysUsageDescription 54 | We need access to your location all the time to provide location-based features. 55 | 56 | NSLocationAlwaysAndWhenInUseUsageDescription 57 | We need access to your location all the time and when using the app to provide location-based features. 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | 5 | import 'screens/destination_screen.dart'; 6 | import 'screens/home_screen.dart'; 7 | import 'screens/map_screen.dart'; 8 | import 'screens/points_of_interest_screen.dart'; 9 | 10 | Future main() async { 11 | await dotenv.load(fileName: ".env"); 12 | runApp(const ProviderScope(child: MyApp())); 13 | } 14 | 15 | class MyApp extends StatelessWidget { 16 | const MyApp({super.key}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MaterialApp( 21 | title: 'Flutter Demo', 22 | theme: ThemeData( 23 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange), 24 | useMaterial3: true, 25 | ), 26 | home: const HomeScreen(), 27 | routes: { 28 | '/destination': (context) => const DestinationScreen(), 29 | '/points-of-interest': (context) => const PointsOfInterestScreen(), 30 | '/map': (context) => const MapScreen(), 31 | }, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/models/destination.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 2 | 3 | import 'point_of_interest.dart'; 4 | 5 | class Destination { 6 | final String id; 7 | final String name; 8 | final LatLng latLng; 9 | final String description; 10 | final String imageUrl; 11 | final List additionalImages; 12 | final double rating; 13 | final int numReviews; 14 | final List pointsOfInterest; 15 | 16 | const Destination({ 17 | required this.id, 18 | required this.name, 19 | required this.latLng, 20 | required this.description, 21 | required this.imageUrl, 22 | required this.additionalImages, 23 | required this.rating, 24 | required this.numReviews, 25 | required this.pointsOfInterest, 26 | }); 27 | 28 | @override 29 | String toString() { 30 | return 'Destination(id: $id, name: $name, latLng: $latLng, description: $description, imageUrl: $imageUrl, additionalImages: $additionalImages, rating: $rating, numReviews: $numReviews, pointsOfInterest: $pointsOfInterest)'; 31 | } 32 | 33 | static const List sampleDestinations = [ 34 | Destination( 35 | id: '1', 36 | name: 'New York', 37 | latLng: LatLng(40.7128, -74.0060), 38 | description: 'The Big Apple', 39 | imageUrl: 40 | 'https://images.unsplash.com/photo-1500916434205-0c77489c6cf7?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80', 41 | additionalImages: [ 42 | 'https://images.unsplash.com/photo-1492666673288-3c4b4576ad9a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1632&q=80', 43 | 'https://images.unsplash.com/photo-1492666673288-3c4b4576ad9a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1632&q=80', 44 | 'https://images.unsplash.com/photo-1492666673288-3c4b4576ad9a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1632&q=80', 45 | 'https://images.unsplash.com/photo-1492666673288-3c4b4576ad9a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1632&q=80', 46 | ], 47 | rating: 4.8, 48 | numReviews: 1589, 49 | pointsOfInterest: [ 50 | PointOfInterest( 51 | id: 'p1', 52 | name: 'Statue of Liberty', 53 | latLng: LatLng(40.6892, -74.0445), 54 | description: 55 | 'World-famous statue gifted by France as a symbol of freedom.', 56 | ), 57 | PointOfInterest( 58 | id: 'p2', 59 | name: 'Central Park', 60 | latLng: LatLng(40.7829, -73.9654), 61 | description: 62 | 'Urban oasis with ball fields, a zoo, & a carousel, plus formal gardens & more.', 63 | ), 64 | PointOfInterest( 65 | id: 'p3', 66 | name: 'Times Square', 67 | latLng: LatLng(40.7580, -73.9855), 68 | description: 69 | 'Bustling destination in the heart of the Theater District known for bright lights, shopping & shows.', 70 | ), 71 | ], 72 | ), 73 | Destination( 74 | id: '2', 75 | name: 'Paris', 76 | latLng: LatLng(48.8566, 2.3522), 77 | description: 'City of Love', 78 | imageUrl: 79 | 'https://images.unsplash.com/photo-1499856871958-5b9627545d1a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1420&q=80', 80 | additionalImages: [ 81 | 'https://images.unsplash.com/photo-1431274172761-fca41d930114?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 82 | 'https://images.unsplash.com/photo-1431274172761-fca41d930114?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 83 | 'https://images.unsplash.com/photo-1431274172761-fca41d930114?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 84 | 'https://images.unsplash.com/photo-1431274172761-fca41d930114?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 85 | ], 86 | rating: 4.7, 87 | numReviews: 1390, 88 | pointsOfInterest: [ 89 | PointOfInterest( 90 | id: 'p4', 91 | name: 'Eiffel Tower', 92 | latLng: LatLng(48.8584, 2.2945), 93 | description: 94 | 'Iconic wrought-iron lattice tower offering expansive views of Paris.', 95 | ), 96 | PointOfInterest( 97 | id: 'p5', 98 | name: 'Louvre Museum', 99 | latLng: LatLng(48.8606, 2.3376), 100 | description: 101 | 'Historic palace housing huge art collection, from Roman sculptures to Da Vinci\'s "Mona Lisa."', 102 | ), 103 | PointOfInterest( 104 | id: 'p6', 105 | name: 'Cathédrale Notre-Dame de Paris', 106 | latLng: LatLng(48.8530, 2.3499), 107 | description: 108 | 'Iconic, 13th-century cathedral with flying buttresses & gargoyles, setting for Hugo\'s novel.', 109 | ), 110 | ], 111 | ), 112 | Destination( 113 | id: '3', 114 | name: 'Tokyo', 115 | latLng: LatLng(35.6895, 139.6917), 116 | description: 'Land of the Rising Sun', 117 | imageUrl: 118 | 'https://images.unsplash.com/photo-1513407030348-c983a97b98d8?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1472&q=80', 119 | additionalImages: [ 120 | 'https://images.unsplash.com/photo-1544885935-98dd03b09034?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80', 121 | 'https://images.unsplash.com/photo-1544885935-98dd03b09034?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80', 122 | 'https://images.unsplash.com/photo-1544885935-98dd03b09034?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80', 123 | 'https://images.unsplash.com/photo-1544885935-98dd03b09034?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80', 124 | ], 125 | rating: 4.9, 126 | numReviews: 1789, 127 | pointsOfInterest: [ 128 | PointOfInterest( 129 | id: 'p7', 130 | name: 'Mount Fuji', 131 | latLng: LatLng(35.3606, 138.7274), 132 | description: 133 | 'Iconic, active stratovolcano known for its symmetrical snow-capped peak & climbing routes.', 134 | ), 135 | PointOfInterest( 136 | id: 'p8', 137 | name: 'Tokyo Tower', 138 | latLng: LatLng(35.6586, 139.7454), 139 | description: 140 | 'Prominent steel tower offering observation decks with panoramic city views.', 141 | ), 142 | PointOfInterest( 143 | id: 'p9', 144 | name: 'Senso-ji', 145 | latLng: LatLng(35.7148, 139.7967), 146 | description: 147 | 'Tokyo\'s oldest temple, offering shopping, dining & a pagoda with views.', 148 | ), 149 | ], 150 | ), 151 | Destination( 152 | id: '4', 153 | name: 'Sydney', 154 | latLng: LatLng(-33.8688, 151.2093), 155 | description: 'Harbour City', 156 | imageUrl: 157 | 'https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 158 | additionalImages: [ 159 | 'https://images.unsplash.com/photo-1598948485421-33a1655d3c18?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1074&q=80', 160 | 'https://images.unsplash.com/photo-1598948485421-33a1655d3c18?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1074&q=80', 161 | 'https://images.unsplash.com/photo-1598948485421-33a1655d3c18?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1074&q=80', 162 | 'https://images.unsplash.com/photo-1598948485421-33a1655d3c18?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1074&q=80', 163 | ], 164 | rating: 4.6, 165 | numReviews: 1890, 166 | pointsOfInterest: [ 167 | PointOfInterest( 168 | id: 'p10', 169 | name: 'Sydney Opera House', 170 | latLng: LatLng(-33.8568, 151.2153), 171 | description: 172 | 'Iconic Sydney opera house with a distinctive sail-like design.', 173 | ), 174 | PointOfInterest( 175 | id: 'p11', 176 | name: 'Sydney Harbour Bridge', 177 | latLng: LatLng(-33.8523, 151.2108), 178 | description: 179 | 'Large steel arch bridge known for panoramic views of the city skyline.', 180 | ), 181 | PointOfInterest( 182 | id: 'p12', 183 | name: 'Bondi Beach', 184 | latLng: LatLng(-33.8908, 151.2743), 185 | description: 186 | 'Well-known beach attracting surfers to its large, curling waves, and hipsters to its organic eateries.', 187 | ), 188 | ], 189 | ), 190 | Destination( 191 | id: '5', 192 | name: 'Cape Town', 193 | latLng: LatLng(-33.9249, 18.4241), 194 | description: 'Mother City', 195 | imageUrl: 196 | 'https://images.unsplash.com/photo-1580060839134-75a5edca2e99?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1471&q=80', 197 | additionalImages: [ 198 | 'https://images.unsplash.com/photo-1576485290814-1c72aa4bbb8e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 199 | 'https://images.unsplash.com/photo-1576485290814-1c72aa4bbb8e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 200 | 'https://images.unsplash.com/photo-1576485290814-1c72aa4bbb8e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 201 | 'https://images.unsplash.com/photo-1576485290814-1c72aa4bbb8e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80', 202 | ], 203 | rating: 4.8, 204 | numReviews: 1523, 205 | pointsOfInterest: [ 206 | PointOfInterest( 207 | id: 'p13', 208 | name: 'Table Mountain', 209 | latLng: LatLng(-33.9249, 18.4241), 210 | description: 211 | 'Flat-topped mountain forming a prominent landmark overlooking the city of Cape Town.', 212 | ), 213 | PointOfInterest( 214 | id: 'p14', 215 | name: 'Robben Island', 216 | latLng: LatLng(-33.8076, 18.3713), 217 | description: 218 | 'Iconic island prison where Nelson Mandela was held, with tours led by ex-prisoners.', 219 | ), 220 | PointOfInterest( 221 | id: 'p15', 222 | name: 'Cape of Good Hope', 223 | latLng: LatLng(-34.3568, 18.4969), 224 | description: 225 | 'Scenic headland marking the southeastern corner of the Cape Peninsula.', 226 | ), 227 | ], 228 | ), 229 | ]; 230 | } 231 | -------------------------------------------------------------------------------- /lib/models/direction.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 2 | 3 | class Direction { 4 | final List route; 5 | final String distanceText; 6 | final String durationText; 7 | 8 | Direction({ 9 | required this.route, 10 | required this.distanceText, 11 | required this.durationText, 12 | }); 13 | 14 | factory Direction.fromJson(Map json) { 15 | // print(json.keys); 16 | // final steps = json['routes'][0]['legs'][0]['steps'] as List; 17 | final polylinePoints = json['routes'][0]['polyline']['encodedPolyline']; 18 | 19 | return Direction( 20 | route: _decodePoly(polylinePoints), 21 | distanceText: 22 | (json['routes'][0]['distanceMeters'] / 1000).toStringAsFixed(1), 23 | durationText: (int.parse( 24 | json['routes'][0]['duration'].replaceAll(RegExp(r'[^0-9]'), ''), 25 | ) / 26 | 60) 27 | .round() 28 | .toString(), 29 | ); 30 | } 31 | 32 | // The decodePoly function decodes a polyline that has been encoded using Google's 33 | // polyline algorithm, which is used for efficiently encoding a series of coordinates 34 | // into a string. The function accepts a string parameter "encoded" which 35 | // represents the encoded polyline string. 36 | static List _decodePoly(String encoded) { 37 | // We start by creating an empty list of LatLng objects. 38 | List poly = []; 39 | // These variables will be used to hold the index we're currently at in the encoded string, 40 | // the length of the encoded string, and the current latitude and longitude. 41 | int index = 0, len = encoded.length; 42 | int lat = 0, lng = 0; 43 | 44 | // We keep looping until we've processed the entire encoded string. 45 | while (index < len) { 46 | // These variables will be used to hold the current character's decoded value, 47 | // the amount to shift by, and the resulting value after shifting. 48 | int b, shift = 0, result = 0; 49 | 50 | // This loop decodes the latitude value. 51 | do { 52 | // Get the ASCII value of the character and subtract 63. 53 | b = encoded.codeUnitAt(index++) - 63; 54 | 55 | // Bitwise-OR the result with the value of the character, bitwise-ANDed with 31, shifted to 56 | // the left by the shift amount. 57 | result |= (b & 0x1f) << shift; 58 | // We're dealing with a base-32 number system, so increase the shift amount by 5 for the next iteration. 59 | shift += 5; 60 | } 61 | // If the ASCII value of the character is 32 or more, that means the latitude isn't done yet and 62 | // we need to continue to the next character. 63 | while (b >= 0x20); 64 | 65 | // Convert the result into a signed integer. 66 | int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); 67 | 68 | // Add the latitude difference to the current latitude. 69 | lat += dlat; 70 | 71 | // The longitude is decoded exactly the same way as the latitude, but starting from where the latitude left off. 72 | shift = 0; 73 | result = 0; 74 | do { 75 | b = encoded.codeUnitAt(index++) - 63; 76 | result |= (b & 0x1f) << shift; 77 | shift += 5; 78 | } while (b >= 0x20); 79 | 80 | // Convert the result into a signed integer. 81 | int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); 82 | // Add the longitude difference to the current longitude. 83 | lng += dlng; 84 | 85 | // The latitude and longitude are encoded as integers multiplied by 10^5. 86 | // We divide by 10^5 to get the actual latitude and longitude. 87 | LatLng p = LatLng(lat / 1E5, lng / 1E5); 88 | 89 | // Add the decoded latitude and longitude to the list. 90 | poly.add(p); 91 | } 92 | 93 | // Return the list of decoded latitude and longitude pairs. 94 | return poly; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/models/geometry.dart: -------------------------------------------------------------------------------- 1 | import 'location.dart'; 2 | import 'viewport.dart'; 3 | 4 | class Geometry { 5 | final Location location; 6 | final ViewPort viewport; 7 | 8 | const Geometry({ 9 | required this.location, 10 | required this.viewport, 11 | }); 12 | 13 | factory Geometry.fromJson(Map json) { 14 | return Geometry( 15 | location: Location.fromJson(json['location']), 16 | viewport: ViewPort.fromJson(json['viewport']), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/models/location.dart: -------------------------------------------------------------------------------- 1 | class Location { 2 | final double lat; 3 | final double lng; 4 | 5 | const Location({ 6 | required this.lat, 7 | required this.lng, 8 | }); 9 | 10 | factory Location.fromJson(Map json) { 11 | return Location( 12 | lat: json['lat'], 13 | lng: json['lng'], 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/models/opening_hours.dart: -------------------------------------------------------------------------------- 1 | class OpeningHours { 2 | final bool? openNow; 3 | final List? periods; 4 | final String? type; 5 | final List? weekdayText; 6 | 7 | const OpeningHours({ 8 | this.openNow, 9 | this.periods, 10 | this.type, 11 | this.weekdayText, 12 | }); 13 | 14 | factory OpeningHours.fromJson(Map json) { 15 | return OpeningHours( 16 | openNow: json['open_now'], 17 | periods: (json['periods'] as List?) 18 | ?.map((i) => OpeningHoursPeriod.fromJson(i)) 19 | .toList(), 20 | type: json['type'], 21 | weekdayText: 22 | (json['weekday_text'] as List?)?.map((e) => e as String).toList(), 23 | ); 24 | } 25 | } 26 | 27 | class OpeningHoursPeriodDetail { 28 | final int day; 29 | final String time; 30 | final String? date; 31 | final bool? truncated; 32 | 33 | const OpeningHoursPeriodDetail({ 34 | required this.day, 35 | required this.time, 36 | this.date, 37 | this.truncated, 38 | }); 39 | 40 | factory OpeningHoursPeriodDetail.fromJson(Map json) { 41 | return OpeningHoursPeriodDetail( 42 | day: json['day'], 43 | time: json['time'], 44 | date: json['date'], 45 | truncated: json['truncated'], 46 | ); 47 | } 48 | } 49 | 50 | class OpeningHoursPeriod { 51 | final OpeningHoursPeriodDetail open; 52 | final OpeningHoursPeriodDetail? close; 53 | 54 | const OpeningHoursPeriod({ 55 | required this.open, 56 | this.close, 57 | }); 58 | 59 | factory OpeningHoursPeriod.fromJson(Map json) { 60 | return OpeningHoursPeriod( 61 | open: OpeningHoursPeriodDetail.fromJson(json['open']), 62 | close: json['close'] != null 63 | ? OpeningHoursPeriodDetail.fromJson(json['close']) 64 | : null, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/models/photo.dart: -------------------------------------------------------------------------------- 1 | class Photo { 2 | final String photoReference; 3 | final int height; 4 | final int width; 5 | 6 | const Photo({ 7 | required this.photoReference, 8 | required this.height, 9 | required this.width, 10 | }); 11 | 12 | factory Photo.fromJson(Map json) { 13 | return Photo( 14 | photoReference: json['photo_reference'], 15 | height: json['height'], 16 | width: json['width'], 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/models/photo_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | class PhotoImage { 4 | final Uint8List imageData; 5 | 6 | const PhotoImage({required this.imageData}); 7 | } 8 | -------------------------------------------------------------------------------- /lib/models/place.dart: -------------------------------------------------------------------------------- 1 | import 'geometry.dart'; 2 | import 'opening_hours.dart'; 3 | import 'photo.dart'; 4 | import 'review.dart'; 5 | 6 | class Place { 7 | final String placeId; 8 | final String name; 9 | final String formattedAddress; 10 | final Geometry geometry; 11 | final List photos; 12 | final List? reviews; 13 | final OpeningHours? openingHours; 14 | 15 | const Place({ 16 | required this.placeId, 17 | required this.name, 18 | required this.formattedAddress, 19 | required this.geometry, 20 | required this.photos, 21 | this.reviews, 22 | this.openingHours, 23 | }); 24 | 25 | factory Place.fromJson(Map json) { 26 | return Place( 27 | placeId: json['place_id'], 28 | name: json['name'], 29 | formattedAddress: json['formatted_address'], 30 | geometry: Geometry.fromJson(json['geometry']), 31 | photos: (json['photos'] as List).map((i) => Photo.fromJson(i)).toList(), 32 | reviews: 33 | (json['reviews'] as List?)?.map((i) => Review.fromJson(i)).toList(), 34 | openingHours: json['opening_hours'] != null 35 | ? OpeningHours.fromJson(json['opening_hours']) 36 | : null, 37 | ); 38 | } 39 | 40 | @override 41 | String toString() { 42 | return 'Place{placeId: $placeId, name: $name, formattedAddress: $formattedAddress, geometry: $geometry, photos: $photos, reviews: $reviews, openingHours: $openingHours}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/models/place_autocomplete_prediction.dart: -------------------------------------------------------------------------------- 1 | class PlaceAutocompletePrediction { 2 | final String placeId; 3 | final String description; 4 | 5 | const PlaceAutocompletePrediction({ 6 | required this.placeId, 7 | required this.description, 8 | }); 9 | 10 | factory PlaceAutocompletePrediction.fromJson( 11 | Map json, 12 | ) { 13 | return PlaceAutocompletePrediction( 14 | placeId: json['place_id'], 15 | description: json['description'], 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/models/point_of_interest.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 2 | 3 | class PointOfInterest { 4 | final String id; 5 | final String name; 6 | final LatLng latLng; 7 | final String description; 8 | 9 | const PointOfInterest({ 10 | required this.id, 11 | required this.name, 12 | required this.latLng, 13 | required this.description, 14 | }); 15 | 16 | @override 17 | String toString() { 18 | return 'PointOfInterest(id: $id, name: $name, latLng: $latLng, description: $description)'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/models/query_autocomplete_prediction.dart: -------------------------------------------------------------------------------- 1 | class QueryAutocompletePrediction { 2 | final String description; 3 | 4 | const QueryAutocompletePrediction({ 5 | required this.description, 6 | }); 7 | 8 | factory QueryAutocompletePrediction.fromJson( 9 | Map json, 10 | ) { 11 | return QueryAutocompletePrediction( 12 | description: json['description'], 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/models/review.dart: -------------------------------------------------------------------------------- 1 | class Review { 2 | final String authorName; 3 | final int rating; 4 | final String text; 5 | final String? language; 6 | 7 | const Review({ 8 | required this.authorName, 9 | required this.rating, 10 | required this.text, 11 | this.language, 12 | }); 13 | 14 | factory Review.fromJson(Map json) { 15 | return Review( 16 | authorName: json['author_name'], 17 | rating: json['rating'], 18 | text: json['text'], 19 | language: json['language'], 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/models/viewport.dart: -------------------------------------------------------------------------------- 1 | import 'location.dart'; 2 | 3 | class ViewPort { 4 | final Location northeast; 5 | final Location southwest; 6 | 7 | const ViewPort({ 8 | required this.northeast, 9 | required this.southwest, 10 | }); 11 | 12 | factory ViewPort.fromJson(Map json) { 13 | return ViewPort( 14 | northeast: Location.fromJson(json['northeast']), 15 | southwest: Location.fromJson(json['southwest']), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/repositories/destination_repository.dart: -------------------------------------------------------------------------------- 1 | import '../models/destination.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | part 'destination_repository.g.dart'; 5 | 6 | @riverpod 7 | DestinationRepository destinationRepository(DestinationRepositoryRef ref) => 8 | DestinationRepository(); 9 | 10 | class DestinationRepository { 11 | Future> getDestinations() async { 12 | return Future.delayed( 13 | const Duration(milliseconds: 300), 14 | () => Destination.sampleDestinations, 15 | ); 16 | } 17 | 18 | Future getDestinationByName(String name) async { 19 | return Future.delayed( 20 | const Duration(milliseconds: 300), 21 | () => Destination.sampleDestinations 22 | .firstWhere((destination) => destination.name == name), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/repositories/destination_repository.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'destination_repository.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$destinationRepositoryHash() => 10 | r'f16b5d8b3b66914d775156e64900aca0712521c3'; 11 | 12 | /// See also [destinationRepository]. 13 | @ProviderFor(destinationRepository) 14 | final destinationRepositoryProvider = 15 | AutoDisposeProvider.internal( 16 | destinationRepository, 17 | name: r'destinationRepositoryProvider', 18 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') 19 | ? null 20 | : _$destinationRepositoryHash, 21 | dependencies: null, 22 | allTransitiveDependencies: null, 23 | ); 24 | 25 | typedef DestinationRepositoryRef 26 | = AutoDisposeProviderRef; 27 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 28 | -------------------------------------------------------------------------------- /lib/repositories/maps_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 2 | import 'package:geolocator/geolocator.dart'; 3 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 4 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 5 | import 'package:travel_routes/models/direction.dart'; 6 | import 'package:travel_routes/models/photo_image.dart'; 7 | 8 | import '../models/place.dart'; 9 | import '../models/place_autocomplete_prediction.dart'; 10 | import '../services/api/geocoding_api_client.dart'; 11 | import '../services/api/places_api_client.dart'; 12 | import '../services/api/routes_api_client.dart'; 13 | import '../services/location_service.dart'; 14 | 15 | part 'maps_repository.g.dart'; 16 | 17 | @riverpod 18 | MapsRepository mapsRepository(MapsRepositoryRef ref) => MapsRepository(); 19 | 20 | class MapsRepository { 21 | final PlacesApiClient _placesApiClient; 22 | final RoutesApiClient _routesApiClient; 23 | final GeocodingApiClient _geocodingApiClient; 24 | final LocationService _locationService; 25 | 26 | MapsRepository({ 27 | PlacesApiClient? placesApiClient, 28 | RoutesApiClient? routesApiClient, 29 | GeocodingApiClient? geocodingApiClient, 30 | LocationService? locationService, 31 | }) : _placesApiClient = placesApiClient ?? 32 | PlacesApiClient( 33 | apiKey: dotenv.env['API_KEY']!, 34 | ), 35 | _routesApiClient = routesApiClient ?? 36 | RoutesApiClient( 37 | apiKey: dotenv.env['API_KEY']!, 38 | ), 39 | _geocodingApiClient = geocodingApiClient ?? 40 | GeocodingApiClient( 41 | apiKey: dotenv.env['API_KEY']!, 42 | ), 43 | _locationService = locationService ?? const LocationService(); 44 | 45 | /// Takes an address and latitude-longitude (LatLng) as input and 46 | /// returns detailed information about the place and a photo of it. 47 | /// It first finds the place_id of the address and then fetches 48 | /// detailed information about the place including a photo of it. 49 | Future<(Place, PhotoImage)> getDetailedPlaceFromAddress( 50 | String address, 51 | LatLng latLng, 52 | ) async { 53 | final locationBias = 'circle:10000@${latLng.latitude},${latLng.longitude}'; 54 | 55 | // Get the place_id from the address 56 | final place = await _placesApiClient.findPlaceFromText( 57 | address, 58 | locationBias, 59 | ); 60 | 61 | // Get detailed place information from the place_id 62 | final placeDetails = await _placesApiClient.getDetailedPlace(place.placeId); 63 | 64 | // Get one picture from the place 65 | final placePhoto = await _placesApiClient.getPlacePhoto( 66 | placeDetails.photos[0].photoReference, 67 | placeDetails.photos[0].height, 68 | placeDetails.photos[0].width, 69 | ); 70 | 71 | return (placeDetails, placePhoto); 72 | } 73 | 74 | /// This method provides real-time suggestions as users type in a search box. 75 | /// It improves the user experience by reducing typing errors and speeding up the search process. 76 | Future> getPlaceAutocompletePredictions( 77 | String input, 78 | ) async { 79 | final predictions = await _placesApiClient.getAutocompletePredictions( 80 | input, 81 | ); 82 | // print(predictions.map((e) => e.description).toList()); 83 | return predictions; 84 | } 85 | 86 | /// This method fetches directions from origin to destination. 87 | Future getDirections( 88 | String origin, 89 | String destination, 90 | ) async { 91 | final directions = await _routesApiClient.getDirections( 92 | origin, 93 | destination, 94 | ); 95 | 96 | return directions; 97 | } 98 | 99 | /// Fetches the current location of the user. 100 | Future getCurrentUserLocation() async { 101 | final location = await _locationService.getCurrentPosition(); 102 | return location; 103 | } 104 | 105 | /// Fetches the current address of the user. 106 | Future getCurrentUserAddress() async { 107 | final location = await _locationService.getCurrentPosition(); 108 | if (location != null) { 109 | final geoData = await _geocodingApiClient.getAddress( 110 | location.latitude, 111 | location.longitude, 112 | ); 113 | 114 | String? address = geoData['results'][0]['formatted_address']; 115 | return address; 116 | } else { 117 | return null; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/repositories/maps_repository.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'maps_repository.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$mapsRepositoryHash() => r'3f58424001e8a01070286d4900d8db3ac9ef0edd'; 10 | 11 | /// See also [mapsRepository]. 12 | @ProviderFor(mapsRepository) 13 | final mapsRepositoryProvider = AutoDisposeProvider.internal( 14 | mapsRepository, 15 | name: r'mapsRepositoryProvider', 16 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') 17 | ? null 18 | : _$mapsRepositoryHash, 19 | dependencies: null, 20 | allTransitiveDependencies: null, 21 | ); 22 | 23 | typedef MapsRepositoryRef = AutoDisposeProviderRef; 24 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 25 | -------------------------------------------------------------------------------- /lib/screens/destination_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | 7 | import '../state/providers/selected_destination_provider.dart'; 8 | import 'error_screen.dart'; 9 | import 'loading_screen.dart'; 10 | 11 | class DestinationScreen extends ConsumerStatefulWidget { 12 | const DestinationScreen({super.key}); 13 | 14 | @override 15 | ConsumerState createState() => _DestinationScreenState(); 16 | } 17 | 18 | class _DestinationScreenState extends ConsumerState { 19 | final Completer _controller = 20 | Completer(); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | final size = MediaQuery.sizeOf(context); 25 | final theme = Theme.of(context); 26 | final name = ModalRoute.of(context)!.settings.arguments as String; 27 | 28 | return ref.watch(selectedDestinationProvider(name)).maybeWhen( 29 | orElse: () => const ErrorScreen(), 30 | error: (Object error, StackTrace stackTrace) => const ErrorScreen(), 31 | loading: () => const LoadingScreen(), 32 | data: (destination) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | title: Column( 36 | children: [ 37 | Text( 38 | 'Destination', 39 | style: theme.textTheme.bodySmall, 40 | ), 41 | const SizedBox(height: 4), 42 | Text( 43 | destination?.name ?? '', 44 | style: theme.textTheme.headlineSmall, 45 | ), 46 | ], 47 | ), 48 | ), 49 | body: SafeArea( 50 | child: Padding( 51 | padding: const EdgeInsets.only( 52 | left: 16.0, 53 | right: 16.0, 54 | top: 16.0, 55 | ), 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Image.network( 60 | destination?.imageUrl ?? '', 61 | fit: BoxFit.cover, 62 | width: size.width, 63 | height: size.height * 0.30, 64 | ), 65 | const SizedBox(height: 4.0), 66 | SizedBox( 67 | height: 75, 68 | child: Row( 69 | children: destination!.additionalImages.map((image) { 70 | return Expanded( 71 | child: Padding( 72 | padding: const EdgeInsets.only( 73 | left: 2.0, 74 | right: 2.0, 75 | ), 76 | child: Image.network(image, fit: BoxFit.cover), 77 | ), 78 | ); 79 | }).toList(), 80 | ), 81 | ), 82 | const SizedBox(height: 8.0), 83 | Text( 84 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 85 | style: theme.textTheme.bodyMedium, 86 | maxLines: 5, 87 | ), 88 | const SizedBox(height: 8.0), 89 | Expanded( 90 | child: GoogleMap( 91 | mapType: MapType.normal, 92 | myLocationButtonEnabled: false, 93 | initialCameraPosition: CameraPosition( 94 | target: destination.latLng, 95 | zoom: 10, 96 | ), 97 | onMapCreated: (GoogleMapController controller) { 98 | _controller.complete(controller); 99 | }, 100 | ), 101 | ), 102 | const SizedBox(height: 8.0), 103 | FilledButton( 104 | style: FilledButton.styleFrom( 105 | minimumSize: const Size.fromHeight(48.0), 106 | shape: RoundedRectangleBorder( 107 | borderRadius: BorderRadius.circular(0.0), 108 | ), 109 | ), 110 | onPressed: () { 111 | Navigator.pushNamed( 112 | context, 113 | '/points-of-interest', 114 | arguments: destination.name, 115 | ); 116 | }, 117 | child: const Text('Find Attractions'), 118 | ) 119 | ], 120 | ), 121 | ), 122 | ), 123 | ); 124 | }, 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/screens/error_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorScreen extends StatelessWidget { 4 | const ErrorScreen({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Scaffold( 9 | body: Center(child: Text('Something went wrong!')), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:carousel_slider/carousel_slider.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_animate/flutter_animate.dart'; 4 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 5 | 6 | import '../models/destination.dart'; 7 | import '../state/providers/all_destinations_provider.dart'; 8 | 9 | class HomeScreen extends ConsumerWidget { 10 | const HomeScreen({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context, WidgetRef ref) { 14 | final size = MediaQuery.of(context).size; 15 | final theme = Theme.of(context); 16 | final destinations = ref.watch(allDestinationsProvider); 17 | 18 | return Scaffold( 19 | body: SafeArea( 20 | child: Column( 21 | crossAxisAlignment: CrossAxisAlignment.center, 22 | children: [ 23 | const Spacer(), 24 | Text( 25 | 'Let\'s choose your travel route', 26 | textAlign: TextAlign.center, 27 | style: theme.textTheme.displaySmall!.copyWith( 28 | fontWeight: FontWeight.bold, 29 | ), 30 | ) 31 | .animate(onPlay: (controller) => controller.repeat()) 32 | .shimmer( 33 | duration: 2400.ms, 34 | color: theme.colorScheme.primary, 35 | ) 36 | .animate() 37 | .fadeIn(duration: 1200.ms, curve: Curves.easeOutQuad) 38 | .slide(), 39 | const Spacer(), 40 | CarouselSlider( 41 | options: CarouselOptions( 42 | height: size.height * 0.66, 43 | enlargeCenterPage: true, 44 | ), 45 | items: destinations.when( 46 | data: (destinations) { 47 | return destinations.map( 48 | (destination) { 49 | return DestinationCard( 50 | destination: destination, 51 | ); 52 | }, 53 | ).toList(); 54 | }, 55 | loading: () => [ 56 | const Center(child: CircularProgressIndicator()), 57 | ], 58 | error: (err, stack) => [Text('Error: $err')], 59 | ), 60 | ).animate().fadeIn(duration: 1200.ms, curve: Curves.easeOutQuad), 61 | const Spacer(), 62 | ], 63 | ), 64 | ), 65 | ); 66 | } 67 | } 68 | 69 | class DestinationCard extends StatelessWidget { 70 | const DestinationCard({super.key, required this.destination}); 71 | 72 | final Destination destination; 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return InkWell( 77 | onTap: () { 78 | Navigator.pushNamed( 79 | context, 80 | '/destination', 81 | arguments: destination.name, 82 | ); 83 | }, 84 | child: Card( 85 | shape: RoundedRectangleBorder( 86 | borderRadius: BorderRadius.circular(0.0), 87 | ), 88 | child: Stack( 89 | children: [ 90 | Positioned.fill( 91 | child: Image.network( 92 | destination.imageUrl, 93 | fit: BoxFit.cover, 94 | ), 95 | ).animate().shimmer(duration: 1200.ms), 96 | Align( 97 | alignment: Alignment.center, 98 | child: Text( 99 | destination.name, 100 | textAlign: TextAlign.center, 101 | style: Theme.of(context) 102 | .textTheme 103 | .displayLarge! 104 | .copyWith(color: Colors.white, fontWeight: FontWeight.bold), 105 | ), 106 | ), 107 | ], 108 | ), 109 | ), 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lib/screens/loading_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingScreen extends StatelessWidget { 4 | const LoadingScreen({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Scaffold( 9 | body: Center(child: CircularProgressIndicator()), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/screens/map_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_animate/flutter_animate.dart'; 5 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 6 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 7 | import '../models/point_of_interest.dart'; 8 | import '../state/providers/place_details_provider.dart'; 9 | 10 | import '../state/providers/directions_provider.dart'; 11 | import 'error_screen.dart'; 12 | import 'loading_screen.dart'; 13 | 14 | class MapScreen extends ConsumerStatefulWidget { 15 | const MapScreen({super.key}); 16 | 17 | @override 18 | ConsumerState createState() => _MapScreenState(); 19 | } 20 | 21 | class _MapScreenState extends ConsumerState { 22 | final Completer _controller = 23 | Completer(); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final size = MediaQuery.sizeOf(context); 28 | final theme = Theme.of(context); 29 | final arguments = 30 | ModalRoute.of(context)!.settings.arguments as Map; 31 | 32 | final origin = arguments['origin']; 33 | final destination = arguments['destination'] as PointOfInterest; 34 | 35 | return ref.watch(directionsProvider(origin, destination.name)).maybeWhen( 36 | orElse: () => const ErrorScreen(), 37 | error: (Object error, StackTrace stackTrace) => const ErrorScreen(), 38 | loading: () => const LoadingScreen(), 39 | data: (directions) { 40 | return Scaffold( 41 | appBar: AppBar( 42 | backgroundColor: Colors.transparent, 43 | title: Text( 44 | destination.name, 45 | style: theme.textTheme.headlineSmall! 46 | .copyWith(fontWeight: FontWeight.bold), 47 | ), 48 | ), 49 | extendBodyBehindAppBar: true, 50 | body: Stack( 51 | children: [ 52 | SizedBox( 53 | height: size.height, 54 | child: GoogleMap( 55 | mapType: MapType.normal, 56 | myLocationButtonEnabled: false, 57 | polylines: { 58 | Polyline( 59 | polylineId: const PolylineId('route1'), 60 | visible: true, 61 | points: directions.route, 62 | color: theme.colorScheme.secondary, 63 | width: 5, 64 | ) 65 | }, 66 | onMapCreated: (GoogleMapController controller) { 67 | _controller.complete(controller); 68 | }, 69 | initialCameraPosition: CameraPosition( 70 | target: LatLng( 71 | directions.route[0].latitude, 72 | directions.route[0].longitude, 73 | ), 74 | zoom: 12, 75 | ), 76 | ), 77 | ), 78 | Positioned( 79 | bottom: 48.0, 80 | left: 16.0, 81 | right: 16.0, 82 | child: Column( 83 | crossAxisAlignment: CrossAxisAlignment.start, 84 | children: [ 85 | Card( 86 | shape: RoundedRectangleBorder( 87 | borderRadius: BorderRadius.circular(0.0), 88 | ), 89 | child: Container( 90 | width: size.width * 0.33, 91 | padding: const EdgeInsets.all(8.0), 92 | child: Row( 93 | children: [ 94 | const Icon(Icons.directions), 95 | const SizedBox(width: 8.0), 96 | Text( 97 | directions.distanceText, 98 | style: theme.textTheme.headlineMedium! 99 | .copyWith(fontWeight: FontWeight.bold), 100 | ), 101 | const SizedBox(width: 4.0), 102 | const Text('km') 103 | ], 104 | ), 105 | ), 106 | ) 107 | .animate() 108 | .fadeIn(duration: 1200.ms, curve: Curves.easeOutQuad) 109 | .slide( 110 | begin: const Offset(1, 0), 111 | end: const Offset(0, 0), 112 | ), 113 | Card( 114 | shape: RoundedRectangleBorder( 115 | borderRadius: BorderRadius.circular(0.0), 116 | ), 117 | child: Container( 118 | width: size.width * 0.33, 119 | padding: const EdgeInsets.all(8.0), 120 | child: Row( 121 | children: [ 122 | const Icon(Icons.schedule), 123 | const SizedBox(width: 8.0), 124 | Text( 125 | directions.durationText, 126 | style: theme.textTheme.headlineMedium! 127 | .copyWith(fontWeight: FontWeight.bold), 128 | ), 129 | const SizedBox(width: 4.0), 130 | const Text('mins') 131 | ], 132 | ), 133 | ), 134 | ) 135 | .animate() 136 | .fadeIn(duration: 1200.ms, curve: Curves.easeOutQuad) 137 | .slide( 138 | begin: const Offset(1, 0), 139 | end: const Offset(0, 0), 140 | ), 141 | const SizedBox(height: 8), 142 | PointOfInterestDetails( 143 | size: size, 144 | theme: theme, 145 | destination: destination, 146 | ) 147 | .animate() 148 | .fadeIn(duration: 1200.ms, curve: Curves.easeOutQuad) 149 | .slide( 150 | begin: const Offset(0, 1), 151 | end: const Offset(0, 0), 152 | ), 153 | ], 154 | ), 155 | ), 156 | ], 157 | ), 158 | ); 159 | }); 160 | } 161 | } 162 | 163 | class PointOfInterestDetails extends ConsumerWidget { 164 | const PointOfInterestDetails({ 165 | super.key, 166 | required this.size, 167 | required this.theme, 168 | required this.destination, 169 | }); 170 | 171 | final Size size; 172 | final ThemeData theme; 173 | final PointOfInterest destination; 174 | 175 | @override 176 | Widget build(BuildContext context, WidgetRef ref) { 177 | return ref 178 | .watch(placeDetailsProvider(destination.name, destination.latLng)) 179 | .maybeWhen( 180 | orElse: () => const SizedBox(), 181 | data: (placeDetails) { 182 | return Card( 183 | shape: RoundedRectangleBorder( 184 | borderRadius: BorderRadius.circular(0.0), 185 | ), 186 | child: Padding( 187 | padding: const EdgeInsets.all(8.0), 188 | child: Row( 189 | children: [ 190 | Image.memory( 191 | placeDetails.$2.imageData, 192 | fit: BoxFit.cover, 193 | width: size.width * 0.25, 194 | height: size.width * 0.25, 195 | ).animate().shimmer(duration: 1000.ms), 196 | const SizedBox(width: 8.0), 197 | Expanded( 198 | child: Column( 199 | crossAxisAlignment: CrossAxisAlignment.start, 200 | children: [ 201 | Text( 202 | placeDetails.$1.name, 203 | style: theme.textTheme.bodyLarge!.copyWith( 204 | fontWeight: FontWeight.bold, 205 | ), 206 | ), 207 | Text(placeDetails.$1.formattedAddress), 208 | ], 209 | ), 210 | ) 211 | ], 212 | ), 213 | ), 214 | ); 215 | }, 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/screens/points_of_interest_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | 7 | import '../models/place_autocomplete_prediction.dart'; 8 | import '../models/point_of_interest.dart'; 9 | import '../state/notifiers/user_location_provider.dart'; 10 | import '../state/providers/place_autocomplete_predictions_provider.dart'; 11 | import '../state/providers/selected_destination_provider.dart'; 12 | import '../state/providers/user_current_location_provider.dart'; 13 | import 'error_screen.dart'; 14 | import 'loading_screen.dart'; 15 | 16 | class PointsOfInterestScreen extends ConsumerStatefulWidget { 17 | const PointsOfInterestScreen({super.key}); 18 | 19 | @override 20 | ConsumerState createState() => 21 | _PointsOfInterestScreenState(); 22 | } 23 | 24 | class _PointsOfInterestScreenState 25 | extends ConsumerState { 26 | final Completer _controller = 27 | Completer(); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | Size size = MediaQuery.sizeOf(context); 32 | final name = ModalRoute.of(context)!.settings.arguments as String; 33 | 34 | return ref.watch(selectedDestinationProvider(name)).maybeWhen( 35 | orElse: () => const ErrorScreen(), 36 | error: (Object error, StackTrace stackTrace) => const ErrorScreen(), 37 | loading: () => const LoadingScreen(), 38 | data: (destination) { 39 | final userLocation = ref.watch(userLocationProvider); 40 | final placePredictionsAsyncValue = ref.watch( 41 | placeAutocompletePredictionsProvider(userLocation), 42 | ); 43 | 44 | final markers = destination!.pointsOfInterest.map( 45 | (pointOfInterest) { 46 | return Marker( 47 | markerId: MarkerId(pointOfInterest.name), 48 | position: pointOfInterest.latLng, 49 | ); 50 | }, 51 | ).toSet(); 52 | 53 | return Scaffold( 54 | extendBodyBehindAppBar: true, 55 | body: SingleChildScrollView( 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Stack( 60 | children: [ 61 | SizedBox( 62 | height: size.height * 0.5, 63 | child: GoogleMap( 64 | mapType: MapType.normal, 65 | myLocationButtonEnabled: false, 66 | markers: markers, 67 | initialCameraPosition: CameraPosition( 68 | target: destination.latLng, 69 | zoom: 12, 70 | ), 71 | onMapCreated: (GoogleMapController controller) { 72 | _controller.complete(controller); 73 | }, 74 | ), 75 | ), 76 | Positioned( 77 | top: 56.0, 78 | left: 16.0, 79 | right: 16.0, 80 | child: SizedBox( 81 | height: 64, 82 | child: AutocompleteSearchWidget( 83 | placePredictionsAsyncValue: 84 | placePredictionsAsyncValue, 85 | ), 86 | ), 87 | ), 88 | ], 89 | ), 90 | Container( 91 | padding: const EdgeInsets.all(16.0), 92 | child: Row( 93 | children: [ 94 | Image.network( 95 | destination.imageUrl, 96 | fit: BoxFit.cover, 97 | height: 120, 98 | width: 160, 99 | ), 100 | const SizedBox(width: 16.0), 101 | Column( 102 | crossAxisAlignment: CrossAxisAlignment.start, 103 | children: [ 104 | Text( 105 | destination.name, 106 | style: 107 | Theme.of(context).textTheme.headlineSmall, 108 | ), 109 | const SizedBox(height: 4), 110 | Text( 111 | destination.description, 112 | style: Theme.of(context).textTheme.bodyMedium, 113 | ), 114 | ], 115 | ), 116 | ], 117 | ), 118 | ), 119 | PointsOfInterestWidget( 120 | pointsOfInterest: destination.pointsOfInterest, 121 | userLocation: userLocation, 122 | mapController: _controller, 123 | ), 124 | ], 125 | ), 126 | ), 127 | ); 128 | }, 129 | ); 130 | } 131 | } 132 | 133 | class AutocompleteSearchWidget extends ConsumerWidget { 134 | const AutocompleteSearchWidget({ 135 | super.key, 136 | required this.placePredictionsAsyncValue, 137 | }); 138 | 139 | final AsyncValue> 140 | placePredictionsAsyncValue; 141 | 142 | @override 143 | Widget build(BuildContext context, WidgetRef ref) { 144 | return RawAutocomplete( 145 | optionsBuilder: (TextEditingValue textEditingValue) { 146 | if (textEditingValue.text == '') { 147 | return const Iterable.empty(); 148 | } 149 | return placePredictionsAsyncValue.when( 150 | data: (data) => data, 151 | loading: () => [], 152 | error: (error, stack) => [], 153 | ); 154 | }, 155 | displayStringForOption: (option) => option.description, 156 | fieldViewBuilder: ( 157 | BuildContext context, 158 | TextEditingController textEditingController, 159 | FocusNode focusNode, 160 | VoidCallback onFieldSubmitted, 161 | ) { 162 | return TextFormField( 163 | controller: textEditingController, 164 | focusNode: focusNode, 165 | onFieldSubmitted: (String value) { 166 | onFieldSubmitted(); 167 | }, 168 | onChanged: (value) { 169 | ref.read(userLocationProvider.notifier).setUserLocation(value); 170 | }, 171 | decoration: InputDecoration( 172 | border: InputBorder.none, 173 | fillColor: Theme.of(context).colorScheme.surface, 174 | filled: true, 175 | labelText: 'Choose location', 176 | prefixIcon: IconButton( 177 | onPressed: () async { 178 | final currentLocation = await ref.read( 179 | userCurrentLocationProvider.future, 180 | ); 181 | 182 | if (currentLocation != null) { 183 | ref 184 | .read(userLocationProvider.notifier) 185 | .setUserLocation(currentLocation); 186 | 187 | textEditingController.text = currentLocation; 188 | } 189 | }, 190 | icon: const Icon(Icons.location_on), 191 | ), 192 | suffixIcon: IconButton( 193 | onPressed: () { 194 | textEditingController.clear(); 195 | }, 196 | icon: const Icon(Icons.clear), 197 | ), 198 | ), 199 | ); 200 | }, 201 | optionsViewBuilder: ( 202 | BuildContext context, 203 | AutocompleteOnSelected onSelected, 204 | Iterable options, 205 | ) { 206 | return Align( 207 | alignment: Alignment.topLeft, 208 | child: Container( 209 | color: Theme.of(context).colorScheme.surface, 210 | width: MediaQuery.of(context).size.width * 0.75, 211 | child: ListView.separated( 212 | shrinkWrap: true, 213 | padding: const EdgeInsets.all(16.0), 214 | itemCount: options.length, 215 | separatorBuilder: (context, index) { 216 | return const SizedBox(height: 16.0); 217 | }, 218 | itemBuilder: (BuildContext context, int index) { 219 | final PlaceAutocompletePrediction option = 220 | options.elementAt(index); 221 | return GestureDetector( 222 | onTap: () { 223 | ref 224 | .read(userLocationProvider.notifier) 225 | .setUserLocation(option.description); 226 | return onSelected(option); 227 | }, 228 | child: Text( 229 | option.description, 230 | style: Theme.of(context).textTheme.bodyMedium, 231 | ), 232 | ); 233 | }, 234 | ), 235 | ), 236 | ); 237 | }, 238 | ); 239 | } 240 | } 241 | 242 | class PointsOfInterestWidget extends StatelessWidget { 243 | const PointsOfInterestWidget({ 244 | super.key, 245 | required this.pointsOfInterest, 246 | required this.userLocation, 247 | required this.mapController, 248 | }); 249 | 250 | final List pointsOfInterest; 251 | final String userLocation; 252 | final Completer mapController; 253 | 254 | @override 255 | Widget build(BuildContext context) { 256 | return Column( 257 | children: pointsOfInterest.map( 258 | (pointOfInterest) { 259 | return Container( 260 | margin: const EdgeInsets.only(bottom: 8.0), 261 | child: ListTile( 262 | tileColor: Colors.grey.shade100, 263 | leading: const Icon(Icons.attractions), 264 | title: Text( 265 | pointOfInterest.name, 266 | maxLines: 1, 267 | ), 268 | subtitle: Text( 269 | pointOfInterest.description, 270 | maxLines: 2, 271 | ), 272 | onTap: () async { 273 | if (userLocation.length > 5) { 274 | Navigator.pushNamed( 275 | context, 276 | '/map', 277 | arguments: { 278 | 'origin': userLocation, 279 | 'destination': pointOfInterest, 280 | }, 281 | ); 282 | } else { 283 | final GoogleMapController controller = 284 | await mapController.future; 285 | controller.animateCamera( 286 | CameraUpdate.newCameraPosition( 287 | CameraPosition( 288 | zoom: 14, 289 | target: pointOfInterest.latLng, 290 | ), 291 | ), 292 | ); 293 | } 294 | }, 295 | ), 296 | ); 297 | }, 298 | ).toList(), 299 | ); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /lib/services/api/geocoding_api_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:http/http.dart' as http; 4 | 5 | class GeocodingApiClient { 6 | final String _baseUrl; 7 | final http.Client _httpClient; 8 | final String _apiKey; 9 | 10 | GeocodingApiClient({ 11 | http.Client? httpClient, 12 | required String apiKey, 13 | }) : this._( 14 | baseUrl: 'https://maps.googleapis.com/maps/api/geocode', 15 | httpClient: httpClient, 16 | apiKey: apiKey, 17 | ); 18 | 19 | GeocodingApiClient._({ 20 | required String baseUrl, 21 | http.Client? httpClient, 22 | required String apiKey, 23 | }) : _baseUrl = baseUrl, 24 | _httpClient = httpClient ?? http.Client(), 25 | _apiKey = apiKey; 26 | 27 | // https://developers.google.com/maps/documentation/geocoding/requests-geocoding 28 | Future> getLatLng(String address) async { 29 | final uri = Uri.parse('$_baseUrl/json').replace( 30 | queryParameters: { 31 | 'address': address, 32 | 'key': _apiKey, 33 | }, 34 | ); 35 | final response = await _httpClient.get(uri); 36 | 37 | if (response.statusCode != 200) { 38 | throw Exception('Error getting the address from LatLon.'); 39 | } 40 | return jsonDecode(response.body); 41 | } 42 | 43 | Future> getAddress(double lat, double lng) async { 44 | final uri = Uri.parse('$_baseUrl/json').replace( 45 | queryParameters: { 46 | 'latlng': '$lat,$lng', 47 | 'key': _apiKey, 48 | }, 49 | ); 50 | 51 | final response = await _httpClient.get(uri); 52 | 53 | if (response.statusCode != 200) { 54 | throw Exception( 55 | 'Error getting the address from LatLon ${response.statusCode}.}'); 56 | } 57 | 58 | return jsonDecode(response.body); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/services/api/places_api_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:http/http.dart' as http; 4 | 5 | import '../../models/photo_image.dart'; 6 | import '../../models/place.dart'; 7 | import '../../models/place_autocomplete_prediction.dart'; 8 | import '../../models/query_autocomplete_prediction.dart'; 9 | 10 | class PlacesApiClient { 11 | final String _baseUrl; 12 | final http.Client _httpClient; 13 | final String _apiKey; 14 | 15 | PlacesApiClient({ 16 | http.Client? httpClient, 17 | required String apiKey, 18 | }) : this._( 19 | baseUrl: 'https://maps.googleapis.com/maps/api/place', 20 | httpClient: httpClient, 21 | apiKey: apiKey, 22 | ); 23 | 24 | PlacesApiClient._({ 25 | required String baseUrl, 26 | http.Client? httpClient, 27 | required String apiKey, 28 | }) : _baseUrl = baseUrl, 29 | _httpClient = httpClient ?? http.Client(), 30 | _apiKey = apiKey; 31 | 32 | /// The Find Place API is a service that allows developers to search for 33 | /// specific places using the name, address, or a simple keyword. 34 | /// 35 | /// [inputText] - the name, address, or keyword to search for 36 | /// [locationBias] - the preferred area to find places 37 | Future findPlaceFromText( 38 | String inputText, 39 | String locationBias, 40 | ) async { 41 | final uri = Uri.parse('$_baseUrl/findplacefromtext/json').replace( 42 | queryParameters: { 43 | 'fields': 44 | 'place_id,geometry,name,formatted_address,rating,opening_hours,photos', 45 | 'locationbias': locationBias, 46 | 'inputtype': 'textquery', 47 | 'input': inputText, 48 | 'key': _apiKey, 49 | }, 50 | ); 51 | 52 | final response = await _httpClient.get(uri); 53 | 54 | if (response.statusCode != 200) { 55 | throw Exception('Error finding place from text.'); 56 | } 57 | 58 | final placeJson = jsonDecode(response.body); 59 | 60 | if (placeJson['status'] != 'OK') { 61 | throw Exception('Error: ${placeJson['status']}'); 62 | } 63 | 64 | final place = Place.fromJson(placeJson['candidates'][0]); 65 | return place; 66 | } 67 | 68 | /// The Text Search API is a web-based service that allows developers to search 69 | /// for places within the vast Google Maps database using natural language queries. 70 | /// 71 | /// [query] - The natural language query to search for 72 | Future> performTextSearch(String query) async { 73 | final uri = Uri.parse('$_baseUrl/textsearch/json').replace( 74 | queryParameters: { 75 | 'query': query, 76 | 'key': _apiKey, 77 | }, 78 | ); 79 | 80 | final response = await _httpClient.get(uri); 81 | 82 | if (response.statusCode != 200) { 83 | throw Exception('Error finding place from text.'); 84 | } 85 | 86 | final responseJson = jsonDecode(response.body); 87 | var results = responseJson['results'] as List; 88 | 89 | return results.map((place) => Place.fromJson(place)).toList(); 90 | } 91 | 92 | /// The Place Autocomplete API provides real-time suggestions as users type 93 | /// in a search box. 94 | /// 95 | /// [input] - The text input to get autocomplete predictions 96 | Future> getAutocompletePredictions( 97 | String input, 98 | ) async { 99 | final uri = Uri.parse('$_baseUrl/autocomplete/json').replace( 100 | queryParameters: { 101 | 'input': input, 102 | 'key': _apiKey, 103 | }, 104 | ); 105 | 106 | final response = await _httpClient.get(uri); 107 | 108 | if (response.statusCode != 200) { 109 | throw Exception('Error finding place from text.'); 110 | } 111 | 112 | final responseJson = jsonDecode(response.body); 113 | var predictions = responseJson['predictions'] as List; 114 | 115 | return predictions 116 | .map((prediction) => PlaceAutocompletePrediction.fromJson(prediction)) 117 | .toList(); 118 | } 119 | 120 | /// The Query Autocomplete service can be used to provide a query prediction 121 | /// for text-based geographic searches, by returning suggested queries as you type. 122 | /// 123 | /// [input] - The text input to get query autocomplete predictions 124 | Future> getQueryPredictions( 125 | String input, 126 | ) async { 127 | final uri = Uri.parse('$_baseUrl/queryautocomplete/json').replace( 128 | queryParameters: { 129 | 'input': input, 130 | 'key': _apiKey, 131 | }, 132 | ); 133 | 134 | final response = await _httpClient.get(uri); 135 | 136 | if (response.statusCode != 200) { 137 | throw Exception('Error finding place from text.'); 138 | } 139 | 140 | final responseJson = jsonDecode(response.body); 141 | var predictions = responseJson['predictions'] as List; 142 | 143 | return predictions 144 | .map((prediction) => QueryAutocompletePrediction.fromJson(prediction)) 145 | .toList(); 146 | } 147 | 148 | /// The Place Details API returns more detailed information about a place, 149 | /// including user reviews. 150 | /// 151 | /// [placeId] - The unique ID of the place 152 | Future getDetailedPlace(String placeId) async { 153 | final uri = Uri.parse('$_baseUrl/details/json').replace( 154 | queryParameters: { 155 | 'place_id': placeId, 156 | 'key': _apiKey, 157 | }, 158 | ); 159 | 160 | final response = await _httpClient.get(uri); 161 | 162 | if (response.statusCode != 200) { 163 | throw Exception('Error finding place from text.'); 164 | } 165 | 166 | final placeJson = jsonDecode(response.body); 167 | 168 | if (placeJson['status'] != 'OK') { 169 | throw Exception('Error: ${placeJson['status']}'); 170 | } 171 | 172 | final place = Place.fromJson(placeJson['result']); 173 | return place; 174 | } 175 | 176 | /// The Place Photos API provides millions of place-related photos. 177 | /// 178 | /// [photoReference] - The unique reference of the photo 179 | /// [height] - The preferred height of the photo 180 | /// [width] - The preferred width of the photo 181 | Future getPlacePhoto( 182 | String photoReference, 183 | int height, 184 | int width, 185 | ) async { 186 | final uri = Uri.parse('$_baseUrl/photo').replace( 187 | queryParameters: { 188 | 'photo_reference': photoReference, 189 | 'maxheight': '$height', 190 | 'maxwidth': '$width', 191 | 'key': _apiKey, 192 | }, 193 | ); 194 | 195 | final response = await _httpClient.get(uri); 196 | return PhotoImage(imageData: response.bodyBytes); 197 | } 198 | } 199 | 200 | 201 | // class PlacesApiClient { 202 | // final String _baseUrl; 203 | // final http.Client _httpClient; 204 | // final String _apiKey; 205 | 206 | // PlacesApiClient({ 207 | // http.Client? httpClient, 208 | // required String apiKey, 209 | // }) : this._( 210 | // baseUrl: 'https://maps.googleapis.com/maps/api/place', 211 | // httpClient: httpClient, 212 | // apiKey: apiKey, 213 | // ); 214 | 215 | // PlacesApiClient._({ 216 | // required String baseUrl, 217 | // http.Client? httpClient, 218 | // required String apiKey, 219 | // }) : _baseUrl = baseUrl, 220 | // _httpClient = httpClient ?? http.Client(), 221 | // _apiKey = apiKey; 222 | 223 | // // https://developers.google.com/maps/documentation/places/web-service/search-find-place 224 | // // Future getPlaceFromText( 225 | // // String inputText, 226 | // // String locationBias, 227 | // // ) async { 228 | // // final uri = Uri.parse('$_baseUrl/findplacefromtext/json').replace( 229 | // // queryParameters: { 230 | // // 'fields': 231 | // // 'place_id,geometry,name,formatted_address,rating,opening_hours,photos', 232 | // // 'locationbias': locationBias, 233 | // // 'inputtype': 'textquery', 234 | // // 'input': inputText, 235 | // // 'key': _apiKey, 236 | // // }, 237 | // // ); 238 | 239 | // // final response = await _httpClient.get(uri); 240 | 241 | // // if (response.statusCode != 200) { 242 | // // throw Exception('Error finding place from text.'); 243 | // // } 244 | 245 | // // final placeJson = jsonDecode(response.body); 246 | 247 | // // if (placeJson['status'] != 'OK') { 248 | // // throw Exception('Error: ${placeJson['status']}'); 249 | // // } 250 | 251 | // // final place = Place.fromJson(placeJson['candidates'][0]); 252 | // // return place; 253 | // // } 254 | 255 | // // // https://developers.google.com/maps/documentation/places/web-service/details 256 | // // Future getPlaceDetails(String placeId) async { 257 | // // final uri = Uri.parse('$_baseUrl/details/json').replace( 258 | // // queryParameters: { 259 | // // 'place_id': placeId, 260 | // // 'key': _apiKey, 261 | // // }, 262 | // // ); 263 | 264 | // // final response = await _httpClient.get(uri); 265 | 266 | // // if (response.statusCode != 200) { 267 | // // throw Exception('Error finding place from text.'); 268 | // // } 269 | 270 | // // final placeJson = jsonDecode(response.body); 271 | 272 | // // if (placeJson['status'] != 'OK') { 273 | // // throw Exception('Error: ${placeJson['status']}'); 274 | // // } 275 | 276 | // // final place = Place.fromJson(placeJson['result']); 277 | // // return place; 278 | // // } 279 | 280 | // // https://developers.google.com/maps/documentation/places/web-service/search-text 281 | // // Future> textSearch(String query) async { 282 | // // final uri = Uri.parse('$_baseUrl/textsearch/json').replace( 283 | // // queryParameters: { 284 | // // 'query': query, 285 | // // 'key': _apiKey, 286 | // // }, 287 | // // ); 288 | 289 | // // final response = await _httpClient.get(uri); 290 | 291 | // // if (response.statusCode != 200) { 292 | // // throw Exception('Error finding place from text.'); 293 | // // } 294 | 295 | // // final responseJson = jsonDecode(response.body); 296 | // // var results = responseJson['results'] as List; 297 | 298 | // // return results.map((place) => Place.fromJson(place)).toList(); 299 | // // } 300 | 301 | // // https://developers.google.com/maps/documentation/places/web-service/autocomplete 302 | // // Future> getPlaceAutocompletePredictions( 303 | // // String input, 304 | // // ) async { 305 | // // final uri = Uri.parse('$_baseUrl/autocomplete/json').replace( 306 | // // queryParameters: { 307 | // // 'input': input, 308 | // // 'key': _apiKey, 309 | // // }, 310 | // // ); 311 | 312 | // // final response = await _httpClient.get(uri); 313 | 314 | // // if (response.statusCode != 200) { 315 | // // throw Exception('Error finding place from text.'); 316 | // // } 317 | 318 | // // final responseJson = jsonDecode(response.body); 319 | // // var predictions = responseJson['predictions'] as List; 320 | 321 | // // return predictions 322 | // // .map((prediction) => PlaceAutocompletePrediction.fromJson(prediction)) 323 | // // .toList(); 324 | // // } 325 | 326 | // // https://developers.google.com/maps/documentation/places/web-service/query 327 | // // Future> getQueryAutocompletePredictions( 328 | // // String input, 329 | // // ) async { 330 | // // final uri = Uri.parse('$_baseUrl/queryautocomplete/json').replace( 331 | // // queryParameters: { 332 | // // 'input': input, 333 | // // 'key': _apiKey, 334 | // // }, 335 | // // ); 336 | 337 | // // final response = await _httpClient.get(uri); 338 | 339 | // // if (response.statusCode != 200) { 340 | // // throw Exception('Error finding place from text.'); 341 | // // } 342 | 343 | // // final responseJson = jsonDecode(response.body); 344 | // // var predictions = responseJson['predictions'] as List; 345 | 346 | // // return predictions 347 | // // .map((prediction) => QueryAutocompletePrediction.fromJson(prediction)) 348 | // // .toList(); 349 | // // } 350 | 351 | // // https://developers.google.com/maps/documentation/places/web-service/photos 352 | // // Future getPlacePhoto( 353 | // // String photoReference, 354 | // // int height, 355 | // // int width, 356 | // // ) async { 357 | // // final uri = Uri.parse('$_baseUrl/photo').replace( 358 | // // queryParameters: { 359 | // // 'photo_reference': photoReference, 360 | // // 'maxheight': '$height', 361 | // // 'maxwidth': '$width', 362 | // // 'key': _apiKey, 363 | // // }, 364 | // // ); 365 | 366 | // // final response = await _httpClient.get(uri); 367 | // // return PhotoImage(imageData: response.bodyBytes); 368 | // // } 369 | // // } 370 | 371 | 372 | // // ## Places API 373 | // // ### Find Place 374 | // // 375 | // // The Google Maps Platform Find Place API is a service that allows developers to search for specific places using the name, address, or a simple keyword, returning details about the place including its name, address, and location coordinates. This API works best for applications where the place that a user is searching for is likely to be in their current view of the map. In contrast to the Text Search API, the Find Place API takes a more targeted search string and returns a single result, making it highly suitable for immediate, context-aware place searches. 376 | 377 | // // ### Text Search 378 | // // 379 | // // The Google Maps Platform Text Search API is a web-based service that allows developers to search for places within the vast Google Maps database using natural language queries. These queries can represent business names, points of interest, or virtually any notable public space, returning details about the place including its name, address, coordinates, and more. This service greatly simplifies the task of finding specific locations and retrieving associated metadata, contributing to the development of more user-friendly, location-aware applications. It is particularly useful for apps involving travel, tourism, real estate, and local business search. 380 | 381 | // // ### Place Photos 382 | // // 383 | // // The Google Maps Platform Place Photos API provides millions of place-related photos. These photos are a collection of user-contributed photos and Google-sourced photos. The API returns the photo references of the requested place. Each photo reference can be used to retrieve the actual photo from the API, allowing developers to include high-quality photographic content in their applications. It can be useful in a range of applications where visual context is necessary or helpful, including travel, real estate, and local business search applications, offering users a visual experience of the place. 384 | 385 | // // ### Place Autocomplete 386 | // // 387 | // // The Google Maps Platform Place Autocomplete API is a service that provides real-time suggestions as users type in a search box. It helps users quickly find and select from a pre-populated list of locations as they start typing, improving the user experience by reducing typing errors and speeding up the search process. The service can autocomplete using a specific location or globally, and can be biased towards a certain region. This API is particularly useful for applications requiring a quick place search option, such as ride-sharing apps, travel apps, or any application where the user needs to input a location. 388 | 389 | // // ### Query Autocomplete 390 | // // 391 | // // The Query Autocomplete service can be used to provide a query prediction for text-based geographic searches, by returning suggested queries as you type. The Query Autocomplete service allows you to add on-the-fly geographic query predictions to your application. Instead of searching for a specific location, a user can type in a categorical search, such as "pizza near New York" and the service responds with a list of suggested queries matching the string. As the Query Autocomplete service can match on both full words and substrings, applications can send queries as the user types to provide on-the-fly predictions. 392 | 393 | -------------------------------------------------------------------------------- /lib/services/api/routes_api_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:http/http.dart' as http; 4 | import 'package:travel_routes/models/direction.dart'; 5 | 6 | class RoutesApiClient { 7 | final String _baseUrl; 8 | final http.Client _httpClient; 9 | final String _apiKey; 10 | 11 | RoutesApiClient({ 12 | http.Client? httpClient, 13 | required String apiKey, 14 | }) : this._( 15 | baseUrl: 'https://routes.googleapis.com/directions', 16 | httpClient: httpClient, 17 | apiKey: apiKey, 18 | ); 19 | 20 | RoutesApiClient._({ 21 | required String baseUrl, 22 | http.Client? httpClient, 23 | required String apiKey, 24 | }) : _baseUrl = baseUrl, 25 | _httpClient = httpClient ?? http.Client(), 26 | _apiKey = apiKey; 27 | 28 | // https://developers.google.com/maps/documentation/places/web-service/search-find-place 29 | Future getDirections( 30 | String origin, 31 | String destination, { 32 | String? travelMode, 33 | List? waypoints, 34 | }) async { 35 | final uri = Uri.parse('$_baseUrl/v2:computeRoutes'); 36 | final response = await _httpClient.post( 37 | uri, 38 | headers: { 39 | "Content-Type": "application/json", 40 | "X-Goog-Api-Key": _apiKey, 41 | "X-Goog-FieldMask": 42 | "routes.duration,routes.distanceMeters,routes.polyline.encodedPolyline,routes.legs.steps.start_location,routes.legs.steps.end_location" 43 | }, 44 | body: jsonEncode({ 45 | 'origin': {'address': origin}, 46 | 'destination': {'address': destination}, 47 | 'computeAlternativeRoutes': true, 48 | 'travelMode': travelMode ?? 'DRIVE', // DRIVE / WALK / TWO_WHEELER 49 | 'intermediates': waypoints != null 50 | ? waypoints.map((waypoint) => {'address': waypoint}).toList() 51 | : [], 52 | }), 53 | ); 54 | 55 | if (response.statusCode != 200) { 56 | throw Exception('Error getting the directions.'); 57 | } 58 | 59 | final directionJson = jsonDecode(response.body); 60 | 61 | print(directionJson); 62 | // if (directionJson['status'] != 'OK') { 63 | // throw Exception('Error: ${directionJson['status']}'); 64 | // } 65 | 66 | return Direction.fromJson(directionJson); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/services/location_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:geolocator/geolocator.dart'; 2 | 3 | class LocationService { 4 | const LocationService(); 5 | 6 | Future getCurrentPosition() async { 7 | bool serviceEnabled; 8 | LocationPermission permission; 9 | 10 | // Test if location services are enabled. 11 | serviceEnabled = await Geolocator.isLocationServiceEnabled(); 12 | if (!serviceEnabled) { 13 | // Location services are not enabled don't continue 14 | // accessing the position and request users of the 15 | // App to enable the location services. 16 | print('Location services are disabled.'); 17 | return null; 18 | } 19 | 20 | permission = await Geolocator.checkPermission(); 21 | if (permission == LocationPermission.denied) { 22 | permission = await Geolocator.requestPermission(); 23 | if (permission == LocationPermission.denied) { 24 | // Permissions are denied, next time you could try 25 | // requesting permissions again (this is also where 26 | // Android's shouldShowRequestPermissionRationale 27 | // returned true. According to Android guidelines 28 | // your App should show an explanatory UI now. 29 | print('Location permissions are denied'); 30 | return null; 31 | } 32 | } 33 | 34 | if (permission == LocationPermission.deniedForever) { 35 | // Permissions are denied forever, handle appropriately. 36 | print( 37 | 'Location permissions are permanently denied, we cannot request permissions.', 38 | ); 39 | return null; 40 | } 41 | 42 | // When we reach here, permissions are granted and we can 43 | // continue accessing the position of the device. 44 | return await Geolocator.getCurrentPosition(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/state/notifiers/user_location_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | part 'user_location_provider.g.dart'; 4 | 5 | @riverpod 6 | class UserLocation extends _$UserLocation { 7 | @override 8 | String build() { 9 | return ''; 10 | } 11 | 12 | setUserLocation(String location) { 13 | state = location; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/state/notifiers/user_location_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_location_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$userLocationHash() => r'fef3cb6e9febaedf409ccbd255803455384c4d99'; 10 | 11 | /// See also [UserLocation]. 12 | @ProviderFor(UserLocation) 13 | final userLocationProvider = 14 | AutoDisposeNotifierProvider.internal( 15 | UserLocation.new, 16 | name: r'userLocationProvider', 17 | debugGetCreateSourceHash: 18 | const bool.fromEnvironment('dart.vm.product') ? null : _$userLocationHash, 19 | dependencies: null, 20 | allTransitiveDependencies: null, 21 | ); 22 | 23 | typedef _$UserLocation = AutoDisposeNotifier; 24 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 25 | -------------------------------------------------------------------------------- /lib/state/providers/all_destinations_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | import '../../models/destination.dart'; 4 | import '../../repositories/destination_repository.dart'; 5 | 6 | part 'all_destinations_provider.g.dart'; 7 | 8 | @riverpod 9 | Future> allDestinations(AllDestinationsRef ref) async { 10 | final destinationRepository = ref.watch(destinationRepositoryProvider); 11 | return destinationRepository.getDestinations(); 12 | } 13 | -------------------------------------------------------------------------------- /lib/state/providers/all_destinations_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'all_destinations_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$allDestinationsHash() => r'0ce2581e2a9df4cf4e9d42631f706baa0d5d12fa'; 10 | 11 | /// See also [allDestinations]. 12 | @ProviderFor(allDestinations) 13 | final allDestinationsProvider = 14 | AutoDisposeFutureProvider>.internal( 15 | allDestinations, 16 | name: r'allDestinationsProvider', 17 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') 18 | ? null 19 | : _$allDestinationsHash, 20 | dependencies: null, 21 | allTransitiveDependencies: null, 22 | ); 23 | 24 | typedef AllDestinationsRef = AutoDisposeFutureProviderRef>; 25 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 26 | -------------------------------------------------------------------------------- /lib/state/providers/directions_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | import 'package:travel_routes/models/direction.dart'; 3 | 4 | import '../../repositories/maps_repository.dart'; 5 | 6 | part 'directions_provider.g.dart'; 7 | 8 | @riverpod 9 | Future directions( 10 | DirectionsRef ref, 11 | String origin, 12 | String destination, 13 | ) async { 14 | final mapsRepository = ref.watch(mapsRepositoryProvider); 15 | return mapsRepository.getDirections(origin, destination); 16 | } 17 | -------------------------------------------------------------------------------- /lib/state/providers/directions_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'directions_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$directionsHash() => r'f823fd3db8faa4f80dd31085158fd4ca78549a6b'; 10 | 11 | /// Copied from Dart SDK 12 | class _SystemHash { 13 | _SystemHash._(); 14 | 15 | static int combine(int hash, int value) { 16 | // ignore: parameter_assignments 17 | hash = 0x1fffffff & (hash + value); 18 | // ignore: parameter_assignments 19 | hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); 20 | return hash ^ (hash >> 6); 21 | } 22 | 23 | static int finish(int hash) { 24 | // ignore: parameter_assignments 25 | hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); 26 | // ignore: parameter_assignments 27 | hash = hash ^ (hash >> 11); 28 | return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); 29 | } 30 | } 31 | 32 | typedef DirectionsRef = AutoDisposeFutureProviderRef; 33 | 34 | /// See also [directions]. 35 | @ProviderFor(directions) 36 | const directionsProvider = DirectionsFamily(); 37 | 38 | /// See also [directions]. 39 | class DirectionsFamily extends Family> { 40 | /// See also [directions]. 41 | const DirectionsFamily(); 42 | 43 | /// See also [directions]. 44 | DirectionsProvider call( 45 | String origin, 46 | String destination, 47 | ) { 48 | return DirectionsProvider( 49 | origin, 50 | destination, 51 | ); 52 | } 53 | 54 | @override 55 | DirectionsProvider getProviderOverride( 56 | covariant DirectionsProvider provider, 57 | ) { 58 | return call( 59 | provider.origin, 60 | provider.destination, 61 | ); 62 | } 63 | 64 | static const Iterable? _dependencies = null; 65 | 66 | @override 67 | Iterable? get dependencies => _dependencies; 68 | 69 | static const Iterable? _allTransitiveDependencies = null; 70 | 71 | @override 72 | Iterable? get allTransitiveDependencies => 73 | _allTransitiveDependencies; 74 | 75 | @override 76 | String? get name => r'directionsProvider'; 77 | } 78 | 79 | /// See also [directions]. 80 | class DirectionsProvider extends AutoDisposeFutureProvider { 81 | /// See also [directions]. 82 | DirectionsProvider( 83 | this.origin, 84 | this.destination, 85 | ) : super.internal( 86 | (ref) => directions( 87 | ref, 88 | origin, 89 | destination, 90 | ), 91 | from: directionsProvider, 92 | name: r'directionsProvider', 93 | debugGetCreateSourceHash: 94 | const bool.fromEnvironment('dart.vm.product') 95 | ? null 96 | : _$directionsHash, 97 | dependencies: DirectionsFamily._dependencies, 98 | allTransitiveDependencies: 99 | DirectionsFamily._allTransitiveDependencies, 100 | ); 101 | 102 | final String origin; 103 | final String destination; 104 | 105 | @override 106 | bool operator ==(Object other) { 107 | return other is DirectionsProvider && 108 | other.origin == origin && 109 | other.destination == destination; 110 | } 111 | 112 | @override 113 | int get hashCode { 114 | var hash = _SystemHash.combine(0, runtimeType.hashCode); 115 | hash = _SystemHash.combine(hash, origin.hashCode); 116 | hash = _SystemHash.combine(hash, destination.hashCode); 117 | 118 | return _SystemHash.finish(hash); 119 | } 120 | } 121 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 122 | -------------------------------------------------------------------------------- /lib/state/providers/place_autocomplete_predictions_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | import '../../models/place_autocomplete_prediction.dart'; 4 | import '../../repositories/maps_repository.dart'; 5 | 6 | part 'place_autocomplete_predictions_provider.g.dart'; 7 | 8 | @riverpod 9 | Future> placeAutocompletePredictions( 10 | PlaceAutocompletePredictionsRef ref, 11 | String input, 12 | ) async { 13 | final mapsRepository = ref.watch(mapsRepositoryProvider); 14 | return mapsRepository.getPlaceAutocompletePredictions(input); 15 | } 16 | -------------------------------------------------------------------------------- /lib/state/providers/place_autocomplete_predictions_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'place_autocomplete_predictions_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$placeAutocompletePredictionsHash() => 10 | r'b8a511f80859029031a9ad20992a22ba2223ddab'; 11 | 12 | /// Copied from Dart SDK 13 | class _SystemHash { 14 | _SystemHash._(); 15 | 16 | static int combine(int hash, int value) { 17 | // ignore: parameter_assignments 18 | hash = 0x1fffffff & (hash + value); 19 | // ignore: parameter_assignments 20 | hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); 21 | return hash ^ (hash >> 6); 22 | } 23 | 24 | static int finish(int hash) { 25 | // ignore: parameter_assignments 26 | hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); 27 | // ignore: parameter_assignments 28 | hash = hash ^ (hash >> 11); 29 | return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); 30 | } 31 | } 32 | 33 | typedef PlaceAutocompletePredictionsRef 34 | = AutoDisposeFutureProviderRef>; 35 | 36 | /// See also [placeAutocompletePredictions]. 37 | @ProviderFor(placeAutocompletePredictions) 38 | const placeAutocompletePredictionsProvider = 39 | PlaceAutocompletePredictionsFamily(); 40 | 41 | /// See also [placeAutocompletePredictions]. 42 | class PlaceAutocompletePredictionsFamily 43 | extends Family>> { 44 | /// See also [placeAutocompletePredictions]. 45 | const PlaceAutocompletePredictionsFamily(); 46 | 47 | /// See also [placeAutocompletePredictions]. 48 | PlaceAutocompletePredictionsProvider call( 49 | String input, 50 | ) { 51 | return PlaceAutocompletePredictionsProvider( 52 | input, 53 | ); 54 | } 55 | 56 | @override 57 | PlaceAutocompletePredictionsProvider getProviderOverride( 58 | covariant PlaceAutocompletePredictionsProvider provider, 59 | ) { 60 | return call( 61 | provider.input, 62 | ); 63 | } 64 | 65 | static const Iterable? _dependencies = null; 66 | 67 | @override 68 | Iterable? get dependencies => _dependencies; 69 | 70 | static const Iterable? _allTransitiveDependencies = null; 71 | 72 | @override 73 | Iterable? get allTransitiveDependencies => 74 | _allTransitiveDependencies; 75 | 76 | @override 77 | String? get name => r'placeAutocompletePredictionsProvider'; 78 | } 79 | 80 | /// See also [placeAutocompletePredictions]. 81 | class PlaceAutocompletePredictionsProvider 82 | extends AutoDisposeFutureProvider> { 83 | /// See also [placeAutocompletePredictions]. 84 | PlaceAutocompletePredictionsProvider( 85 | this.input, 86 | ) : super.internal( 87 | (ref) => placeAutocompletePredictions( 88 | ref, 89 | input, 90 | ), 91 | from: placeAutocompletePredictionsProvider, 92 | name: r'placeAutocompletePredictionsProvider', 93 | debugGetCreateSourceHash: 94 | const bool.fromEnvironment('dart.vm.product') 95 | ? null 96 | : _$placeAutocompletePredictionsHash, 97 | dependencies: PlaceAutocompletePredictionsFamily._dependencies, 98 | allTransitiveDependencies: 99 | PlaceAutocompletePredictionsFamily._allTransitiveDependencies, 100 | ); 101 | 102 | final String input; 103 | 104 | @override 105 | bool operator ==(Object other) { 106 | return other is PlaceAutocompletePredictionsProvider && 107 | other.input == input; 108 | } 109 | 110 | @override 111 | int get hashCode { 112 | var hash = _SystemHash.combine(0, runtimeType.hashCode); 113 | hash = _SystemHash.combine(hash, input.hashCode); 114 | 115 | return _SystemHash.finish(hash); 116 | } 117 | } 118 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 119 | -------------------------------------------------------------------------------- /lib/state/providers/place_details_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | import 'package:travel_routes/models/photo_image.dart'; 4 | 5 | import '../../models/place.dart'; 6 | import '../../repositories/maps_repository.dart'; 7 | 8 | part 'place_details_provider.g.dart'; 9 | 10 | @riverpod 11 | Future<(Place, PhotoImage)> placeDetails( 12 | PlaceDetailsRef ref, 13 | String address, 14 | LatLng latLng, 15 | ) async { 16 | final mapsRepository = ref.watch(mapsRepositoryProvider); 17 | return mapsRepository.getDetailedPlaceFromAddress(address, latLng); 18 | } 19 | -------------------------------------------------------------------------------- /lib/state/providers/place_details_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'place_details_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$placeDetailsHash() => r'377be0cda0b77f5db09d43eca00ef7c43abc1670'; 10 | 11 | /// Copied from Dart SDK 12 | class _SystemHash { 13 | _SystemHash._(); 14 | 15 | static int combine(int hash, int value) { 16 | // ignore: parameter_assignments 17 | hash = 0x1fffffff & (hash + value); 18 | // ignore: parameter_assignments 19 | hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); 20 | return hash ^ (hash >> 6); 21 | } 22 | 23 | static int finish(int hash) { 24 | // ignore: parameter_assignments 25 | hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); 26 | // ignore: parameter_assignments 27 | hash = hash ^ (hash >> 11); 28 | return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); 29 | } 30 | } 31 | 32 | typedef PlaceDetailsRef = AutoDisposeFutureProviderRef<(Place, PhotoImage)>; 33 | 34 | /// See also [placeDetails]. 35 | @ProviderFor(placeDetails) 36 | const placeDetailsProvider = PlaceDetailsFamily(); 37 | 38 | /// See also [placeDetails]. 39 | class PlaceDetailsFamily extends Family> { 40 | /// See also [placeDetails]. 41 | const PlaceDetailsFamily(); 42 | 43 | /// See also [placeDetails]. 44 | PlaceDetailsProvider call( 45 | String address, 46 | LatLng latLng, 47 | ) { 48 | return PlaceDetailsProvider( 49 | address, 50 | latLng, 51 | ); 52 | } 53 | 54 | @override 55 | PlaceDetailsProvider getProviderOverride( 56 | covariant PlaceDetailsProvider provider, 57 | ) { 58 | return call( 59 | provider.address, 60 | provider.latLng, 61 | ); 62 | } 63 | 64 | static const Iterable? _dependencies = null; 65 | 66 | @override 67 | Iterable? get dependencies => _dependencies; 68 | 69 | static const Iterable? _allTransitiveDependencies = null; 70 | 71 | @override 72 | Iterable? get allTransitiveDependencies => 73 | _allTransitiveDependencies; 74 | 75 | @override 76 | String? get name => r'placeDetailsProvider'; 77 | } 78 | 79 | /// See also [placeDetails]. 80 | class PlaceDetailsProvider 81 | extends AutoDisposeFutureProvider<(Place, PhotoImage)> { 82 | /// See also [placeDetails]. 83 | PlaceDetailsProvider( 84 | this.address, 85 | this.latLng, 86 | ) : super.internal( 87 | (ref) => placeDetails( 88 | ref, 89 | address, 90 | latLng, 91 | ), 92 | from: placeDetailsProvider, 93 | name: r'placeDetailsProvider', 94 | debugGetCreateSourceHash: 95 | const bool.fromEnvironment('dart.vm.product') 96 | ? null 97 | : _$placeDetailsHash, 98 | dependencies: PlaceDetailsFamily._dependencies, 99 | allTransitiveDependencies: 100 | PlaceDetailsFamily._allTransitiveDependencies, 101 | ); 102 | 103 | final String address; 104 | final LatLng latLng; 105 | 106 | @override 107 | bool operator ==(Object other) { 108 | return other is PlaceDetailsProvider && 109 | other.address == address && 110 | other.latLng == latLng; 111 | } 112 | 113 | @override 114 | int get hashCode { 115 | var hash = _SystemHash.combine(0, runtimeType.hashCode); 116 | hash = _SystemHash.combine(hash, address.hashCode); 117 | hash = _SystemHash.combine(hash, latLng.hashCode); 118 | 119 | return _SystemHash.finish(hash); 120 | } 121 | } 122 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 123 | -------------------------------------------------------------------------------- /lib/state/providers/selected_destination_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | import '../../models/destination.dart'; 4 | import '../../repositories/destination_repository.dart'; 5 | 6 | part 'selected_destination_provider.g.dart'; 7 | 8 | @riverpod 9 | Future selectedDestination( 10 | SelectedDestinationRef ref, 11 | String name, 12 | ) async { 13 | final destinationRepository = ref.watch(destinationRepositoryProvider); 14 | return destinationRepository.getDestinationByName(name); 15 | } 16 | -------------------------------------------------------------------------------- /lib/state/providers/selected_destination_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'selected_destination_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$selectedDestinationHash() => 10 | r'fe221af24696442570c5f91adca9ea83d766b1fc'; 11 | 12 | /// Copied from Dart SDK 13 | class _SystemHash { 14 | _SystemHash._(); 15 | 16 | static int combine(int hash, int value) { 17 | // ignore: parameter_assignments 18 | hash = 0x1fffffff & (hash + value); 19 | // ignore: parameter_assignments 20 | hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); 21 | return hash ^ (hash >> 6); 22 | } 23 | 24 | static int finish(int hash) { 25 | // ignore: parameter_assignments 26 | hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); 27 | // ignore: parameter_assignments 28 | hash = hash ^ (hash >> 11); 29 | return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); 30 | } 31 | } 32 | 33 | typedef SelectedDestinationRef = AutoDisposeFutureProviderRef; 34 | 35 | /// See also [selectedDestination]. 36 | @ProviderFor(selectedDestination) 37 | const selectedDestinationProvider = SelectedDestinationFamily(); 38 | 39 | /// See also [selectedDestination]. 40 | class SelectedDestinationFamily extends Family> { 41 | /// See also [selectedDestination]. 42 | const SelectedDestinationFamily(); 43 | 44 | /// See also [selectedDestination]. 45 | SelectedDestinationProvider call( 46 | String name, 47 | ) { 48 | return SelectedDestinationProvider( 49 | name, 50 | ); 51 | } 52 | 53 | @override 54 | SelectedDestinationProvider getProviderOverride( 55 | covariant SelectedDestinationProvider provider, 56 | ) { 57 | return call( 58 | provider.name, 59 | ); 60 | } 61 | 62 | static const Iterable? _dependencies = null; 63 | 64 | @override 65 | Iterable? get dependencies => _dependencies; 66 | 67 | static const Iterable? _allTransitiveDependencies = null; 68 | 69 | @override 70 | Iterable? get allTransitiveDependencies => 71 | _allTransitiveDependencies; 72 | 73 | @override 74 | String? get name => r'selectedDestinationProvider'; 75 | } 76 | 77 | /// See also [selectedDestination]. 78 | class SelectedDestinationProvider 79 | extends AutoDisposeFutureProvider { 80 | /// See also [selectedDestination]. 81 | SelectedDestinationProvider( 82 | this.name, 83 | ) : super.internal( 84 | (ref) => selectedDestination( 85 | ref, 86 | name, 87 | ), 88 | from: selectedDestinationProvider, 89 | name: r'selectedDestinationProvider', 90 | debugGetCreateSourceHash: 91 | const bool.fromEnvironment('dart.vm.product') 92 | ? null 93 | : _$selectedDestinationHash, 94 | dependencies: SelectedDestinationFamily._dependencies, 95 | allTransitiveDependencies: 96 | SelectedDestinationFamily._allTransitiveDependencies, 97 | ); 98 | 99 | final String name; 100 | 101 | @override 102 | bool operator ==(Object other) { 103 | return other is SelectedDestinationProvider && other.name == name; 104 | } 105 | 106 | @override 107 | int get hashCode { 108 | var hash = _SystemHash.combine(0, runtimeType.hashCode); 109 | hash = _SystemHash.combine(hash, name.hashCode); 110 | 111 | return _SystemHash.finish(hash); 112 | } 113 | } 114 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 115 | -------------------------------------------------------------------------------- /lib/state/providers/user_current_location_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | import '../../repositories/maps_repository.dart'; 4 | 5 | part 'user_current_location_provider.g.dart'; 6 | 7 | @riverpod 8 | Future userCurrentLocation(UserCurrentLocationRef ref) async { 9 | final mapsRepository = ref.watch(mapsRepositoryProvider); 10 | return mapsRepository.getCurrentUserAddress(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/state/providers/user_current_location_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_current_location_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$userCurrentLocationHash() => 10 | r'27a349f3c9e50c7df317f8a8c89fbac79ae24c47'; 11 | 12 | /// See also [userCurrentLocation]. 13 | @ProviderFor(userCurrentLocation) 14 | final userCurrentLocationProvider = AutoDisposeFutureProvider.internal( 15 | userCurrentLocation, 16 | name: r'userCurrentLocationProvider', 17 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') 18 | ? null 19 | : _$userCurrentLocationHash, 20 | dependencies: null, 21 | allTransitiveDependencies: null, 22 | ); 23 | 24 | typedef UserCurrentLocationRef = AutoDisposeFutureProviderRef; 25 | // ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions 26 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "60.0.0" 12 | analyzer: 13 | dependency: transitive 14 | description: 15 | name: analyzer 16 | sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "5.12.0" 20 | analyzer_plugin: 21 | dependency: transitive 22 | description: 23 | name: analyzer_plugin 24 | sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "0.11.2" 28 | args: 29 | dependency: transitive 30 | description: 31 | name: args 32 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.4.2" 36 | async: 37 | dependency: transitive 38 | description: 39 | name: async 40 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.11.0" 44 | boolean_selector: 45 | dependency: transitive 46 | description: 47 | name: boolean_selector 48 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "2.1.1" 52 | build: 53 | dependency: transitive 54 | description: 55 | name: build 56 | sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.4.1" 60 | build_config: 61 | dependency: transitive 62 | description: 63 | name: build_config 64 | sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.1.1" 68 | build_daemon: 69 | dependency: transitive 70 | description: 71 | name: build_daemon 72 | sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "4.0.0" 76 | build_resolvers: 77 | dependency: transitive 78 | description: 79 | name: build_resolvers 80 | sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "2.2.1" 84 | build_runner: 85 | dependency: "direct dev" 86 | description: 87 | name: build_runner 88 | sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "2.4.6" 92 | build_runner_core: 93 | dependency: transitive 94 | description: 95 | name: build_runner_core 96 | sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "7.2.10" 100 | built_collection: 101 | dependency: transitive 102 | description: 103 | name: built_collection 104 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "5.1.1" 108 | built_value: 109 | dependency: transitive 110 | description: 111 | name: built_value 112 | sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "8.6.1" 116 | carousel_slider: 117 | dependency: "direct main" 118 | description: 119 | name: carousel_slider 120 | sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "4.2.1" 124 | characters: 125 | dependency: transitive 126 | description: 127 | name: characters 128 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "1.3.0" 132 | checked_yaml: 133 | dependency: transitive 134 | description: 135 | name: checked_yaml 136 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "2.0.3" 140 | ci: 141 | dependency: transitive 142 | description: 143 | name: ci 144 | sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "0.1.0" 148 | cli_util: 149 | dependency: transitive 150 | description: 151 | name: cli_util 152 | sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "0.4.0" 156 | clock: 157 | dependency: transitive 158 | description: 159 | name: clock 160 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.1.1" 164 | code_builder: 165 | dependency: transitive 166 | description: 167 | name: code_builder 168 | sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "4.5.0" 172 | collection: 173 | dependency: transitive 174 | description: 175 | name: collection 176 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "1.18.0" 180 | convert: 181 | dependency: transitive 182 | description: 183 | name: convert 184 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "3.1.1" 188 | crypto: 189 | dependency: transitive 190 | description: 191 | name: crypto 192 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "3.0.3" 196 | csslib: 197 | dependency: transitive 198 | description: 199 | name: csslib 200 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "1.0.0" 204 | cupertino_icons: 205 | dependency: "direct main" 206 | description: 207 | name: cupertino_icons 208 | sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "1.0.5" 212 | custom_lint: 213 | dependency: transitive 214 | description: 215 | name: custom_lint 216 | sha256: "3ce36c04d30c60cde295588c6185b3f9800e6c18f6670a7ffdb3d5eab39bb942" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "0.4.0" 220 | custom_lint_core: 221 | dependency: transitive 222 | description: 223 | name: custom_lint_core 224 | sha256: "9170d9db2daf774aa2251a3bc98e4ba903c7702ab07aa438bc83bd3c9a0de57f" 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "0.4.0" 228 | dart_style: 229 | dependency: transitive 230 | description: 231 | name: dart_style 232 | sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "2.3.2" 236 | fake_async: 237 | dependency: transitive 238 | description: 239 | name: fake_async 240 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "1.3.1" 244 | file: 245 | dependency: transitive 246 | description: 247 | name: file 248 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 249 | url: "https://pub.dev" 250 | source: hosted 251 | version: "7.0.0" 252 | fixnum: 253 | dependency: transitive 254 | description: 255 | name: fixnum 256 | sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" 257 | url: "https://pub.dev" 258 | source: hosted 259 | version: "1.1.0" 260 | flutter: 261 | dependency: "direct main" 262 | description: flutter 263 | source: sdk 264 | version: "0.0.0" 265 | flutter_animate: 266 | dependency: "direct main" 267 | description: 268 | name: flutter_animate 269 | sha256: be54662837a6e66cc53ee88549e808c625275e0faf5a43e11cf3182cb0bd1b02 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "4.2.0" 273 | flutter_dotenv: 274 | dependency: "direct main" 275 | description: 276 | name: flutter_dotenv 277 | sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "5.1.0" 281 | flutter_hooks: 282 | dependency: transitive 283 | description: 284 | name: flutter_hooks 285 | sha256: "6a126f703b89499818d73305e4ce1e3de33b4ae1c5512e3b8eab4b986f46774c" 286 | url: "https://pub.dev" 287 | source: hosted 288 | version: "0.18.6" 289 | flutter_lints: 290 | dependency: "direct dev" 291 | description: 292 | name: flutter_lints 293 | sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" 294 | url: "https://pub.dev" 295 | source: hosted 296 | version: "2.0.2" 297 | flutter_plugin_android_lifecycle: 298 | dependency: transitive 299 | description: 300 | name: flutter_plugin_android_lifecycle 301 | sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" 302 | url: "https://pub.dev" 303 | source: hosted 304 | version: "2.0.15" 305 | flutter_riverpod: 306 | dependency: transitive 307 | description: 308 | name: flutter_riverpod 309 | sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4 310 | url: "https://pub.dev" 311 | source: hosted 312 | version: "2.3.6" 313 | flutter_test: 314 | dependency: "direct dev" 315 | description: flutter 316 | source: sdk 317 | version: "0.0.0" 318 | flutter_web_plugins: 319 | dependency: transitive 320 | description: flutter 321 | source: sdk 322 | version: "0.0.0" 323 | freezed_annotation: 324 | dependency: transitive 325 | description: 326 | name: freezed_annotation 327 | sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338 328 | url: "https://pub.dev" 329 | source: hosted 330 | version: "2.2.0" 331 | frontend_server_client: 332 | dependency: transitive 333 | description: 334 | name: frontend_server_client 335 | sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" 336 | url: "https://pub.dev" 337 | source: hosted 338 | version: "3.2.0" 339 | geolocator: 340 | dependency: "direct main" 341 | description: 342 | name: geolocator 343 | sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" 344 | url: "https://pub.dev" 345 | source: hosted 346 | version: "9.0.2" 347 | geolocator_android: 348 | dependency: transitive 349 | description: 350 | name: geolocator_android 351 | sha256: b06c72853c993ae533f482d81a12805d7a441f5231d9668718bc7131d7464082 352 | url: "https://pub.dev" 353 | source: hosted 354 | version: "4.2.0" 355 | geolocator_apple: 356 | dependency: transitive 357 | description: 358 | name: geolocator_apple 359 | sha256: "36527c555f4c425f7d8fa8c7c07d67b78e3ff7590d40448051959e1860c1cfb4" 360 | url: "https://pub.dev" 361 | source: hosted 362 | version: "2.2.7" 363 | geolocator_platform_interface: 364 | dependency: transitive 365 | description: 366 | name: geolocator_platform_interface 367 | sha256: af4d69231452f9620718588f41acc4cb58312368716bfff2e92e770b46ce6386 368 | url: "https://pub.dev" 369 | source: hosted 370 | version: "4.0.7" 371 | geolocator_web: 372 | dependency: transitive 373 | description: 374 | name: geolocator_web 375 | sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 376 | url: "https://pub.dev" 377 | source: hosted 378 | version: "2.1.6" 379 | geolocator_windows: 380 | dependency: transitive 381 | description: 382 | name: geolocator_windows 383 | sha256: f5911c88e23f48b598dd506c7c19eff0e001645bdc03bb6fecb9f4549208354d 384 | url: "https://pub.dev" 385 | source: hosted 386 | version: "0.1.1" 387 | glob: 388 | dependency: transitive 389 | description: 390 | name: glob 391 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 392 | url: "https://pub.dev" 393 | source: hosted 394 | version: "2.1.2" 395 | google_maps: 396 | dependency: transitive 397 | description: 398 | name: google_maps 399 | sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" 400 | url: "https://pub.dev" 401 | source: hosted 402 | version: "6.3.0" 403 | google_maps_flutter: 404 | dependency: "direct main" 405 | description: 406 | name: google_maps_flutter 407 | sha256: "7e35644d8a88ad86409976db8fa23ddc7d933f8239e57405e4660534be09acd2" 408 | url: "https://pub.dev" 409 | source: hosted 410 | version: "2.3.1" 411 | google_maps_flutter_android: 412 | dependency: transitive 413 | description: 414 | name: google_maps_flutter_android 415 | sha256: "9512c862df77c1f0fa5f445513dd3c57f5996f0a809dccb74e54b690ee4e3a0f" 416 | url: "https://pub.dev" 417 | source: hosted 418 | version: "2.4.15" 419 | google_maps_flutter_ios: 420 | dependency: transitive 421 | description: 422 | name: google_maps_flutter_ios 423 | sha256: a9462a433bf3ebe60aadcf4906d2d6341a270d69d3e0fcaa8eb2b64699fcfb4f 424 | url: "https://pub.dev" 425 | source: hosted 426 | version: "2.2.3" 427 | google_maps_flutter_platform_interface: 428 | dependency: transitive 429 | description: 430 | name: google_maps_flutter_platform_interface 431 | sha256: "308f0af138fa78e8224d598d46ca182673874d0ef4d754b7157c073b5b4b8e0d" 432 | url: "https://pub.dev" 433 | source: hosted 434 | version: "2.2.7" 435 | google_maps_flutter_web: 436 | dependency: transitive 437 | description: 438 | name: google_maps_flutter_web 439 | sha256: "280170a2dcac3364317b5786f0d2e3c4128fdb795bc0d87ffe56226b0cf1f57d" 440 | url: "https://pub.dev" 441 | source: hosted 442 | version: "0.5.1" 443 | graphs: 444 | dependency: transitive 445 | description: 446 | name: graphs 447 | sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 448 | url: "https://pub.dev" 449 | source: hosted 450 | version: "2.3.1" 451 | hooks_riverpod: 452 | dependency: "direct main" 453 | description: 454 | name: hooks_riverpod 455 | sha256: be68cf7653fcab798500f9047ac58c3f109287a1595012412f4a0d654a9bb9c5 456 | url: "https://pub.dev" 457 | source: hosted 458 | version: "2.3.6" 459 | html: 460 | dependency: transitive 461 | description: 462 | name: html 463 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 464 | url: "https://pub.dev" 465 | source: hosted 466 | version: "0.15.4" 467 | http: 468 | dependency: "direct main" 469 | description: 470 | name: http 471 | sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" 472 | url: "https://pub.dev" 473 | source: hosted 474 | version: "1.1.0" 475 | http_multi_server: 476 | dependency: transitive 477 | description: 478 | name: http_multi_server 479 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" 480 | url: "https://pub.dev" 481 | source: hosted 482 | version: "3.2.1" 483 | http_parser: 484 | dependency: transitive 485 | description: 486 | name: http_parser 487 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 488 | url: "https://pub.dev" 489 | source: hosted 490 | version: "4.0.2" 491 | io: 492 | dependency: transitive 493 | description: 494 | name: io 495 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" 496 | url: "https://pub.dev" 497 | source: hosted 498 | version: "1.0.4" 499 | js: 500 | dependency: transitive 501 | description: 502 | name: js 503 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 504 | url: "https://pub.dev" 505 | source: hosted 506 | version: "0.6.7" 507 | js_wrapping: 508 | dependency: transitive 509 | description: 510 | name: js_wrapping 511 | sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c 512 | url: "https://pub.dev" 513 | source: hosted 514 | version: "0.7.4" 515 | json_annotation: 516 | dependency: transitive 517 | description: 518 | name: json_annotation 519 | sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 520 | url: "https://pub.dev" 521 | source: hosted 522 | version: "4.8.1" 523 | leak_tracker: 524 | dependency: transitive 525 | description: 526 | name: leak_tracker 527 | sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" 528 | url: "https://pub.dev" 529 | source: hosted 530 | version: "10.0.0" 531 | leak_tracker_flutter_testing: 532 | dependency: transitive 533 | description: 534 | name: leak_tracker_flutter_testing 535 | sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 536 | url: "https://pub.dev" 537 | source: hosted 538 | version: "2.0.1" 539 | leak_tracker_testing: 540 | dependency: transitive 541 | description: 542 | name: leak_tracker_testing 543 | sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 544 | url: "https://pub.dev" 545 | source: hosted 546 | version: "2.0.1" 547 | lints: 548 | dependency: transitive 549 | description: 550 | name: lints 551 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 552 | url: "https://pub.dev" 553 | source: hosted 554 | version: "2.1.1" 555 | logging: 556 | dependency: transitive 557 | description: 558 | name: logging 559 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 560 | url: "https://pub.dev" 561 | source: hosted 562 | version: "1.2.0" 563 | matcher: 564 | dependency: transitive 565 | description: 566 | name: matcher 567 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 568 | url: "https://pub.dev" 569 | source: hosted 570 | version: "0.12.16+1" 571 | material_color_utilities: 572 | dependency: transitive 573 | description: 574 | name: material_color_utilities 575 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 576 | url: "https://pub.dev" 577 | source: hosted 578 | version: "0.8.0" 579 | meta: 580 | dependency: transitive 581 | description: 582 | name: meta 583 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 584 | url: "https://pub.dev" 585 | source: hosted 586 | version: "1.11.0" 587 | mime: 588 | dependency: transitive 589 | description: 590 | name: mime 591 | sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e 592 | url: "https://pub.dev" 593 | source: hosted 594 | version: "1.0.4" 595 | package_config: 596 | dependency: transitive 597 | description: 598 | name: package_config 599 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 600 | url: "https://pub.dev" 601 | source: hosted 602 | version: "2.1.0" 603 | path: 604 | dependency: transitive 605 | description: 606 | name: path 607 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 608 | url: "https://pub.dev" 609 | source: hosted 610 | version: "1.9.0" 611 | plugin_platform_interface: 612 | dependency: transitive 613 | description: 614 | name: plugin_platform_interface 615 | sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" 616 | url: "https://pub.dev" 617 | source: hosted 618 | version: "2.1.4" 619 | pool: 620 | dependency: transitive 621 | description: 622 | name: pool 623 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 624 | url: "https://pub.dev" 625 | source: hosted 626 | version: "1.5.1" 627 | pub_semver: 628 | dependency: transitive 629 | description: 630 | name: pub_semver 631 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 632 | url: "https://pub.dev" 633 | source: hosted 634 | version: "2.1.4" 635 | pubspec_parse: 636 | dependency: transitive 637 | description: 638 | name: pubspec_parse 639 | sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 640 | url: "https://pub.dev" 641 | source: hosted 642 | version: "1.2.3" 643 | riverpod: 644 | dependency: "direct main" 645 | description: 646 | name: riverpod 647 | sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef" 648 | url: "https://pub.dev" 649 | source: hosted 650 | version: "2.3.6" 651 | riverpod_analyzer_utils: 652 | dependency: transitive 653 | description: 654 | name: riverpod_analyzer_utils 655 | sha256: "1b2632a6fc0b659c923a4dcc7cd5da42476f5b3294c70c86c971e63bdd443384" 656 | url: "https://pub.dev" 657 | source: hosted 658 | version: "0.3.1" 659 | riverpod_annotation: 660 | dependency: "direct main" 661 | description: 662 | name: riverpod_annotation 663 | sha256: cedd6a54b6f5764ffd5c05df57b6676bfc8c01978e14ee60a2c16891038820fe 664 | url: "https://pub.dev" 665 | source: hosted 666 | version: "2.1.1" 667 | riverpod_generator: 668 | dependency: "direct main" 669 | description: 670 | name: riverpod_generator 671 | sha256: cd0595de57ccf5d944ff4b0f68289e11ac6a2eff1e3dfd1d884a43f6f3bcee5e 672 | url: "https://pub.dev" 673 | source: hosted 674 | version: "2.2.3" 675 | rxdart: 676 | dependency: transitive 677 | description: 678 | name: rxdart 679 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" 680 | url: "https://pub.dev" 681 | source: hosted 682 | version: "0.27.7" 683 | sanitize_html: 684 | dependency: transitive 685 | description: 686 | name: sanitize_html 687 | sha256: "0a445f19bbaa196f5a4f93461aa066b94e6e025622eb1e9bc77872a5e25233a5" 688 | url: "https://pub.dev" 689 | source: hosted 690 | version: "2.0.0" 691 | shelf: 692 | dependency: transitive 693 | description: 694 | name: shelf 695 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 696 | url: "https://pub.dev" 697 | source: hosted 698 | version: "1.4.1" 699 | shelf_web_socket: 700 | dependency: transitive 701 | description: 702 | name: shelf_web_socket 703 | sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" 704 | url: "https://pub.dev" 705 | source: hosted 706 | version: "1.0.4" 707 | sky_engine: 708 | dependency: transitive 709 | description: flutter 710 | source: sdk 711 | version: "0.0.99" 712 | source_gen: 713 | dependency: transitive 714 | description: 715 | name: source_gen 716 | sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 717 | url: "https://pub.dev" 718 | source: hosted 719 | version: "1.4.0" 720 | source_span: 721 | dependency: transitive 722 | description: 723 | name: source_span 724 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 725 | url: "https://pub.dev" 726 | source: hosted 727 | version: "1.10.0" 728 | stack_trace: 729 | dependency: transitive 730 | description: 731 | name: stack_trace 732 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 733 | url: "https://pub.dev" 734 | source: hosted 735 | version: "1.11.1" 736 | state_notifier: 737 | dependency: transitive 738 | description: 739 | name: state_notifier 740 | sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289" 741 | url: "https://pub.dev" 742 | source: hosted 743 | version: "0.7.2+1" 744 | stream_channel: 745 | dependency: transitive 746 | description: 747 | name: stream_channel 748 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 749 | url: "https://pub.dev" 750 | source: hosted 751 | version: "2.1.2" 752 | stream_transform: 753 | dependency: transitive 754 | description: 755 | name: stream_transform 756 | sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" 757 | url: "https://pub.dev" 758 | source: hosted 759 | version: "2.1.0" 760 | string_scanner: 761 | dependency: transitive 762 | description: 763 | name: string_scanner 764 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 765 | url: "https://pub.dev" 766 | source: hosted 767 | version: "1.2.0" 768 | term_glyph: 769 | dependency: transitive 770 | description: 771 | name: term_glyph 772 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 773 | url: "https://pub.dev" 774 | source: hosted 775 | version: "1.2.1" 776 | test_api: 777 | dependency: transitive 778 | description: 779 | name: test_api 780 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 781 | url: "https://pub.dev" 782 | source: hosted 783 | version: "0.6.1" 784 | timing: 785 | dependency: transitive 786 | description: 787 | name: timing 788 | sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" 789 | url: "https://pub.dev" 790 | source: hosted 791 | version: "1.0.1" 792 | typed_data: 793 | dependency: transitive 794 | description: 795 | name: typed_data 796 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 797 | url: "https://pub.dev" 798 | source: hosted 799 | version: "1.3.2" 800 | uuid: 801 | dependency: transitive 802 | description: 803 | name: uuid 804 | sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" 805 | url: "https://pub.dev" 806 | source: hosted 807 | version: "3.0.7" 808 | vector_math: 809 | dependency: transitive 810 | description: 811 | name: vector_math 812 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 813 | url: "https://pub.dev" 814 | source: hosted 815 | version: "2.1.4" 816 | vm_service: 817 | dependency: transitive 818 | description: 819 | name: vm_service 820 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 821 | url: "https://pub.dev" 822 | source: hosted 823 | version: "13.0.0" 824 | watcher: 825 | dependency: transitive 826 | description: 827 | name: watcher 828 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 829 | url: "https://pub.dev" 830 | source: hosted 831 | version: "1.1.0" 832 | web_socket_channel: 833 | dependency: transitive 834 | description: 835 | name: web_socket_channel 836 | sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b 837 | url: "https://pub.dev" 838 | source: hosted 839 | version: "2.4.0" 840 | yaml: 841 | dependency: transitive 842 | description: 843 | name: yaml 844 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 845 | url: "https://pub.dev" 846 | source: hosted 847 | version: "3.1.2" 848 | sdks: 849 | dart: ">=3.2.0-0 <4.0.0" 850 | flutter: ">=3.10.0" 851 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: travel_routes 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: '>=3.0.0 <4.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | flutter_dotenv: ^5.1.0 15 | cupertino_icons: ^1.0.2 16 | http: ^1.1.0 17 | flutter_animate: ^4.2.0 18 | google_maps_flutter: ^2.3.1 19 | carousel_slider: ^4.2.1 20 | riverpod: ^2.3.6 21 | riverpod_generator: ^2.2.3 22 | riverpod_annotation: ^2.1.1 23 | hooks_riverpod: ^2.3.6 24 | geolocator: ^9.0.2 25 | 26 | dev_dependencies: 27 | flutter_test: 28 | sdk: flutter 29 | flutter_lints: ^2.0.0 30 | build_runner: ^2.4.6 31 | 32 | flutter: 33 | uses-material-design: true 34 | 35 | assets: 36 | - .env 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /screenshots/travel_routes_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/screenshots/travel_routes_1.png -------------------------------------------------------------------------------- /screenshots/travel_routes_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/screenshots/travel_routes_2.png -------------------------------------------------------------------------------- /screenshots/travel_routes_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/screenshots/travel_routes_3.png -------------------------------------------------------------------------------- /screenshots/travel_routes_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxonflutter/Travel-App-with-Flutter-Google-Maps-Platform/50771634dc7f6161861048f3c47a4944bf0279ea/screenshots/travel_routes_4.png --------------------------------------------------------------------------------