├── .eslintignore
├── ios
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── .gitignore
├── Podfile
└── Podfile.lock
├── analysis_options.yaml
├── android
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── amplify_trips_planner
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
└── build.gradle
├── images
└── amplify.png
├── amplify
├── backend
│ ├── api
│ │ └── amplifytripsplanner
│ │ │ ├── transform.conf.json
│ │ │ ├── resolvers
│ │ │ └── README.md
│ │ │ ├── parameters.json
│ │ │ ├── cli-inputs.json
│ │ │ ├── schema.graphql
│ │ │ └── stacks
│ │ │ └── CustomResources.json
│ ├── function
│ │ └── amplifytripsplanner4600aecaPostConfirmation
│ │ │ ├── custom-policies.json
│ │ │ ├── parameters.json
│ │ │ ├── src
│ │ │ ├── event.json
│ │ │ ├── package.json
│ │ │ ├── index.js
│ │ │ └── custom.js
│ │ │ ├── amplify.state
│ │ │ └── function-parameters.json
│ ├── tags.json
│ ├── storage
│ │ └── s37d9e620f
│ │ │ └── cli-inputs.json
│ ├── types
│ │ └── amplify-dependent-resources-ref.d.ts
│ ├── auth
│ │ └── amplifytripsplanner4600aeca
│ │ │ └── cli-inputs.json
│ └── backend-config.json
├── .config
│ └── project-config.json
├── README.md
├── hooks
│ └── README.md
└── cli.json
├── lib
├── common
│ ├── navigation
│ │ └── router
│ │ │ ├── routes.dart
│ │ │ └── router.dart
│ ├── utils
│ │ ├── date_time_formatter.dart
│ │ └── colors.dart
│ ├── services
│ │ ├── auth_service.dart
│ │ └── storage_service.dart
│ └── ui
│ │ ├── bottomsheet_text_form_field.dart
│ │ ├── upload_progress_dialog.dart
│ │ └── the_navigation_drawer.dart
├── features
│ ├── trip
│ │ ├── controller
│ │ │ ├── past_trips_list.dart
│ │ │ ├── trips_list.g.dart
│ │ │ ├── past_trips_list.g.dart
│ │ │ ├── trips_list.dart
│ │ │ ├── trip_controller.dart
│ │ │ └── trip_controller.g.dart
│ │ ├── ui
│ │ │ ├── trip_page
│ │ │ │ ├── delete_trip_dialog.dart
│ │ │ │ ├── trip_page_floating_button.dart
│ │ │ │ ├── trip_page.dart
│ │ │ │ ├── trip_details.dart
│ │ │ │ └── selected_trip_card.dart
│ │ │ ├── past_trips
│ │ │ │ └── past_trips_list.dart
│ │ │ ├── trips_gridview
│ │ │ │ ├── trip_gridview_item.dart
│ │ │ │ ├── trips_list_gridview.dart
│ │ │ │ └── trip_gridview_item_card.dart
│ │ │ ├── past_trip_page
│ │ │ │ ├── past_trip_page.dart
│ │ │ │ ├── past_trip_details.dart
│ │ │ │ └── selected_past_trip_card.dart
│ │ │ ├── trips_list
│ │ │ │ ├── trips_list_page.dart
│ │ │ │ └── add_trip_bottomsheet.dart
│ │ │ └── edit_trip_page
│ │ │ │ └── edit_trip_page.dart
│ │ ├── data
│ │ │ └── trips_repository.dart
│ │ └── service
│ │ │ └── trips_api_service.dart
│ ├── activity
│ │ ├── ui
│ │ │ ├── activity_page
│ │ │ │ ├── delete_activity_dialog.dart
│ │ │ │ ├── activity_page_appbar_icon.dart
│ │ │ │ ├── activity_page.dart
│ │ │ │ └── activity_listview.dart
│ │ │ ├── activities_list
│ │ │ │ ├── activities_list.dart
│ │ │ │ └── activities_timeline.dart
│ │ │ ├── activity_category_icon.dart
│ │ │ ├── add_activity
│ │ │ │ ├── add_activity_page.dart
│ │ │ │ └── add_activity_form.dart
│ │ │ └── edit_activity
│ │ │ │ └── edit_activity_page.dart
│ │ ├── data
│ │ │ └── activities_repository.dart
│ │ ├── controller
│ │ │ ├── activity_controller.dart
│ │ │ ├── activities_list.dart
│ │ │ ├── activities_list.g.dart
│ │ │ └── activity_controller.g.dart
│ │ └── service
│ │ │ └── activities_api_service.dart
│ └── profile
│ │ ├── data
│ │ └── profile_repository.dart
│ │ ├── controller
│ │ ├── profile_controller.dart
│ │ └── profile_controller.g.dart
│ │ ├── ui
│ │ └── profile_page
│ │ │ ├── profile_page.dart
│ │ │ ├── edit_profile_bottomsheet.dart
│ │ │ └── profile_listview.dart
│ │ └── service
│ │ └── profile_api_service.dart
├── trips_planner_app.dart
├── main.dart
└── models
│ ├── ActivityCategory.dart
│ └── ModelProvider.dart
├── .vscode
└── settings.json
├── README.md
├── pubspec.yaml
├── .metadata
├── test
└── widget_test.dart
└── .gitignore
/.eslintignore:
--------------------------------------------------------------------------------
1 | amplify-codegen-temp/models
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:amplify_lints/app.yaml
2 |
3 | analyzer:
4 | exclude:
5 | - lib/models/**
6 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/images/amplify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/images/amplify.png
--------------------------------------------------------------------------------
/amplify/backend/api/amplifytripsplanner/transform.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 5,
3 | "ElasticsearchWarning": true
4 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/custom-policies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Action": [],
4 | "Resource": []
5 | }
6 | ]
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "modules": "custom",
3 | "resourceName": "amplifytripsplanner4600aecaPostConfirmation"
4 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/src/event.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "userPoolId": "testID",
4 | "userName": "testUser"
5 | },
6 | "response": {}
7 | }
8 |
--------------------------------------------------------------------------------
/amplify/backend/tags.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Key": "user:Stack",
4 | "Value": "{project-env}"
5 | },
6 | {
7 | "Key": "user:Application",
8 | "Value": "{project-name}"
9 | }
10 | ]
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/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/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/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/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/lib/common/navigation/router/routes.dart:
--------------------------------------------------------------------------------
1 | enum AppRoute {
2 | home,
3 | trip,
4 | editTrip,
5 | pastTrips,
6 | pastTrip,
7 | activity,
8 | addActivity,
9 | editActivity,
10 | profile,
11 | }
12 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/offlineprogrammer/amplify_trips_planner_tutorial/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/lib/common/utils/date_time_formatter.dart:
--------------------------------------------------------------------------------
1 | import 'package:intl/intl.dart';
2 |
3 | extension DateTimeFormatter on DateTime {
4 | String format(String format) {
5 | return DateFormat(format).format(this);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/amplify_trips_planner/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.amplify_trips_planner
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/amplify.state:
--------------------------------------------------------------------------------
1 | {
2 | "pluginId": "amplify-nodejs-function-runtime-provider",
3 | "functionRuntime": "nodejs",
4 | "useLegacyBuild": true,
5 | "defaultEditorFile": "src/index.js"
6 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/amplify/.config/project-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "amplifytripsplanner",
3 | "version": "3.1",
4 | "frontend": "flutter",
5 | "flutter": {
6 | "config": {
7 | "ResDir": "./lib/"
8 | }
9 | },
10 | "providers": [
11 | "awscloudformation"
12 | ]
13 | }
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/amplify/backend/api/amplifytripsplanner/resolvers/README.md:
--------------------------------------------------------------------------------
1 | Any resolvers that you add in this directory will override the ones automatically generated by Amplify CLI and will be directly copied to the cloud.
2 | For more information, visit [https://docs.amplify.aws/cli/graphql-transformer/resolvers](https://docs.amplify.aws/cli/graphql-transformer/resolvers)
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/ios/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.
--------------------------------------------------------------------------------
/amplify/backend/storage/s37d9e620f/cli-inputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "resourceName": "s37d9e620f",
3 | "policyUUID": "7d9e620f",
4 | "bucketName": "amptripsplanner03",
5 | "storageAccess": "authAndGuest",
6 | "guestAccess": [
7 | "READ"
8 | ],
9 | "authAccess": [
10 | "CREATE_AND_UPDATE",
11 | "READ",
12 | "DELETE"
13 | ],
14 | "triggerFunction": "NONE",
15 | "groupAccess": {}
16 | }
--------------------------------------------------------------------------------
/amplify/backend/api/amplifytripsplanner/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSyncApiName": "amplifytripsplanner",
3 | "DynamoDBBillingMode": "PAY_PER_REQUEST",
4 | "DynamoDBEnableServerSideEncryption": false,
5 | "AuthCognitoUserPoolId": {
6 | "Fn::GetAtt": [
7 | "authamplifytripsplanner4600aeca",
8 | "Outputs.UserPoolId"
9 | ]
10 | },
11 | "AuthModeLastUpdated": "2023-03-19T00:40:07.402Z"
12 | }
--------------------------------------------------------------------------------
/amplify/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Amplify CLI
2 | This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli).
3 |
4 | Helpful resources:
5 | - Amplify documentation: https://docs.amplify.aws
6 | - Amplify CLI documentation: https://docs.amplify.aws/cli
7 | - More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files
8 | - Join Amplify's community: https://amplify.aws/community/
9 |
--------------------------------------------------------------------------------
/amplify/hooks/README.md:
--------------------------------------------------------------------------------
1 | # Command Hooks
2 |
3 | Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc.
4 |
5 | To get started, add your script files based on the expected naming convention in this directory.
6 |
7 | Learn more about the script file naming convention, hook parameters, third party dependencies, and advanced configurations at https://docs.amplify.aws/cli/usage/command-hooks
8 |
--------------------------------------------------------------------------------
/amplify/backend/api/amplifytripsplanner/cli-inputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "serviceConfiguration": {
4 | "apiName": "amplifytripsplanner",
5 | "serviceName": "AppSync",
6 | "defaultAuthType": {
7 | "mode": "AMAZON_COGNITO_USER_POOLS",
8 | "cognitoUserPoolId": "authamplifytripsplanner4600aeca"
9 | },
10 | "conflictResolution": {},
11 | "additionalAuthTypes": [
12 | {
13 | "mode": "AWS_IAM"
14 | }
15 | ]
16 | }
17 | }
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "amplify/.config": true,
4 | "amplify/**/*-parameters.json": true,
5 | "amplify/**/amplify.state": true,
6 | "amplify/**/transform.conf.json": true,
7 | "amplify/#current-cloud-backend": true,
8 | "amplify/backend/amplify-meta.json": true,
9 | "amplify/backend/awscloudformation": true
10 | },
11 | "[dart]": {
12 | "editor.formatOnSave": true,
13 | "editor.formatOnType": true
14 | },
15 | "editor.codeActionsOnSave": {
16 | "source.fixAll": true,
17 | "source.organizeImports": true
18 | }
19 | }
--------------------------------------------------------------------------------
/lib/common/services/auth_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_flutter/amplify_flutter.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 |
5 | final authServiceProvider = Provider((ref) {
6 | return AuthService();
7 | });
8 |
9 | class AuthService {
10 | AuthService();
11 |
12 | Future signOut() async {
13 | try {
14 | await Amplify.Auth.signOut();
15 | await Amplify.DataStore.clear();
16 | } on Exception catch (e) {
17 | debugPrint(e.toString());
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # amplify_trips_planner
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "amplifytripsplanner4600aecaPostConfirmation",
3 | "version": "2.0.0",
4 | "description": "Lambda function generated by Amplify",
5 | "main": "index.js",
6 | "license": "Apache-2.0",
7 | "dependencies": {
8 | "axios": "latest",
9 | "@aws-crypto/sha256-js": "^2.0.1",
10 | "@aws-sdk/credential-provider-node": "^3.76.0",
11 | "@aws-sdk/protocol-http": "^3.58.0",
12 | "@aws-sdk/signature-v4": "^3.58.0",
13 | "node-fetch": "2"
14 | },
15 | "devDependencies": {
16 | "@types/aws-lambda": "^8.10.92"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/features/trip/controller/past_trips_list.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:amplify_trips_planner/features/trip/data/trips_repository.dart';
4 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
5 | import 'package:riverpod_annotation/riverpod_annotation.dart';
6 |
7 | part 'past_trips_list.g.dart';
8 |
9 | @riverpod
10 | class PastTripsList extends _$PastTripsList {
11 | Future> _fetchTrips() async {
12 | final tripsRepository = ref.read(tripsRepositoryProvider);
13 | final trips = await tripsRepository.getPastTrips();
14 | return trips;
15 | }
16 |
17 | @override
18 | FutureOr> build() async {
19 | return _fetchTrips();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.2.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 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trip_page/delete_trip_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DeleteTripDialog extends StatelessWidget {
4 | const DeleteTripDialog({
5 | super.key,
6 | });
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return AlertDialog(
11 | title: const Text('Please Confirm'),
12 | content: const Text('Delete this trip?'),
13 | actions: [
14 | TextButton(
15 | onPressed: () async {
16 | Navigator.of(context).pop(true);
17 | },
18 | child: const Text('Yes'),
19 | ),
20 | TextButton(
21 | onPressed: () {
22 | Navigator.of(context).pop(false);
23 | },
24 | child: const Text('No'),
25 | )
26 | ],
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activity_page/delete_activity_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DeleteActivityDialog extends StatelessWidget {
4 | const DeleteActivityDialog({
5 | super.key,
6 | });
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return AlertDialog(
11 | title: const Text('Please Confirm'),
12 | content: const Text('Delete this activity?'),
13 | actions: [
14 | TextButton(
15 | onPressed: () async {
16 | Navigator.of(context).pop(true);
17 | },
18 | child: const Text('Yes'),
19 | ),
20 | TextButton(
21 | onPressed: () {
22 | Navigator.of(context).pop(false);
23 | },
24 | child: const Text('No'),
25 | )
26 | ],
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/features/profile/data/profile_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/profile/service/profile_api_service.dart';
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 |
5 | final profileRepositoryProvider = Provider((ref) {
6 | final profileAPIService = ref.read(profileAPIServiceProvider);
7 | return ProfileRepository(profileAPIService);
8 | });
9 |
10 | class ProfileRepository {
11 | ProfileRepository(this.profileAPIService);
12 | final ProfileAPIService profileAPIService;
13 |
14 | Future getProfile() {
15 | return profileAPIService.getProfile();
16 | }
17 |
18 | Future update(Profile updatedProfile) async {
19 | return profileAPIService.updateProfile(updatedProfile);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/trips_planner_app.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_authenticator/amplify_authenticator.dart';
2 | import 'package:amplify_trips_planner/common/navigation/router/router.dart';
3 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
4 | import 'package:flutter/material.dart';
5 |
6 | class TripsPlannerApp extends StatelessWidget {
7 | const TripsPlannerApp({
8 | super.key,
9 | });
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Authenticator(
14 | child: MaterialApp.router(
15 | routerConfig: router,
16 | builder: Authenticator.builder(),
17 | theme: ThemeData(
18 | colorScheme:
19 | ColorScheme.fromSwatch(primarySwatch: constants.primaryColor)
20 | .copyWith(
21 | background: const Color(0xff82CFEA),
22 | ),
23 | ),
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/function-parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "trigger": true,
3 | "modules": [
4 | "custom"
5 | ],
6 | "parentResource": "amplifytripsplanner4600aeca",
7 | "functionName": "amplifytripsplanner4600aecaPostConfirmation",
8 | "resourceName": "amplifytripsplanner4600aecaPostConfirmation",
9 | "parentStack": "auth",
10 | "triggerEnvs": "[]",
11 | "triggerDir": "/snapshot/repo/build/node_modules/@aws-amplify/amplify-category-auth/provider-utils/awscloudformation/triggers/PostConfirmation",
12 | "triggerTemplate": "PostConfirmation.json.ejs",
13 | "triggerEventPath": "PostConfirmation.event.json",
14 | "roleName": "amplifytripsplanner4600aecaPostConfirmation",
15 | "skipEdit": true,
16 | "permissions": {
17 | "api": {
18 | "amplifytripsplanner": [
19 | "Query",
20 | "Mutation",
21 | "Subscription"
22 | ]
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/lib/common/utils/colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | const Map primarySwatch = {
4 | 50: Color.fromRGBO(255, 207, 68, .1),
5 | 100: Color.fromRGBO(255, 207, 68, .2),
6 | 200: Color.fromRGBO(255, 207, 68, .3),
7 | 300: Color.fromRGBO(255, 207, 68, .4),
8 | 400: Color.fromRGBO(255, 207, 68, .5),
9 | 500: Color.fromRGBO(255, 207, 68, .6),
10 | 600: Color.fromRGBO(255, 207, 68, .7),
11 | 700: Color.fromRGBO(255, 207, 68, .8),
12 | 800: Color.fromRGBO(255, 207, 68, .9),
13 | 900: Color.fromRGBO(255, 207, 68, 1),
14 | };
15 | const MaterialColor primaryColor = MaterialColor(0xFFFFCF44, primarySwatch);
16 | const int primaryColorDark = 0xFFFD9725;
17 | const List greyoutMatrix = [
18 | 0.2126,
19 | 0.7152,
20 | 0.0722,
21 | 0,
22 | 0,
23 | 0.2126,
24 | 0.7152,
25 | 0.0722,
26 | 0,
27 | 0,
28 | 0.2126,
29 | 0.7152,
30 | 0.0722,
31 | 0,
32 | 0,
33 | 0,
34 | 0,
35 | 0,
36 | 1,
37 | 0,
38 | ];
39 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: amplify_trips_planner
2 | description: A new Flutter project.
3 | version: 1.0.0+1
4 |
5 | environment:
6 | sdk: ">=3.0.2 <4.0.0"
7 |
8 | dependencies:
9 | amplify_api: ^1.0.0
10 | amplify_auth_cognito: ^1.0.0
11 | amplify_authenticator: ^1.0.0
12 | amplify_flutter: ^1.0.0
13 | amplify_storage_s3: ^1.0.0
14 | cached_network_image: ^3.2.3
15 | cupertino_icons: ^1.0.5
16 | file_picker: ^5.0.0
17 | flutter:
18 | sdk: flutter
19 | flutter_riverpod: ^2.1.3
20 | go_router: ^7.0.0
21 | image_picker: ^0.8.0
22 | intl: ^0.18.0
23 | path: ^1.8.3
24 | riverpod_annotation: ^2.0.1
25 | timelines: ^0.1.0
26 | url_launcher: ^6.1.5
27 | uuid: ^3.0.7
28 |
29 | dev_dependencies:
30 | amplify_lints: ^3.0.0
31 | build_runner:
32 | custom_lint:
33 | flutter_test:
34 | sdk: flutter
35 | riverpod_generator: ^2.1.3
36 | riverpod_lint: ^1.1.5
37 |
38 | flutter:
39 | uses-material-design: true
40 | assets:
41 | - images/amplify.png
42 |
--------------------------------------------------------------------------------
/amplify/backend/types/amplify-dependent-resources-ref.d.ts:
--------------------------------------------------------------------------------
1 | export type AmplifyDependentResourcesAttributes = {
2 | "api": {
3 | "amplifytripsplanner": {
4 | "GraphQLAPIEndpointOutput": "string",
5 | "GraphQLAPIIdOutput": "string"
6 | }
7 | },
8 | "auth": {
9 | "amplifytripsplanner4600aeca": {
10 | "AppClientID": "string",
11 | "AppClientIDWeb": "string",
12 | "IdentityPoolId": "string",
13 | "IdentityPoolName": "string",
14 | "UserPoolArn": "string",
15 | "UserPoolId": "string",
16 | "UserPoolName": "string"
17 | }
18 | },
19 | "function": {
20 | "amplifytripsplanner4600aecaPostConfirmation": {
21 | "Arn": "string",
22 | "LambdaExecutionRole": "string",
23 | "LambdaExecutionRoleArn": "string",
24 | "Name": "string",
25 | "Region": "string"
26 | }
27 | },
28 | "storage": {
29 | "s37d9e620f": {
30 | "BucketName": "string",
31 | "Region": "string"
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/lib/features/profile/controller/profile_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/profile/data/profile_repository.dart';
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:riverpod_annotation/riverpod_annotation.dart';
4 |
5 | part 'profile_controller.g.dart';
6 |
7 | @riverpod
8 | class ProfileController extends _$ProfileController {
9 | Future _fetchProfile() async {
10 | final profileRepository = ref.read(profileRepositoryProvider);
11 | return profileRepository.getProfile();
12 | }
13 |
14 | @override
15 | FutureOr build() async {
16 | return _fetchProfile();
17 | }
18 |
19 | Future updateProfile(Profile profile) async {
20 | state = const AsyncValue.loading();
21 | state = await AsyncValue.guard(() async {
22 | final profileRepository = ref.read(profileRepositoryProvider);
23 | await profileRepository.update(profile);
24 | return _fetchProfile();
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.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: f72efea43c3013323d1b95cff571f3c1caa37583
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: f72efea43c3013323d1b95cff571f3c1caa37583
17 | base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
18 | - platform: ios
19 | create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
20 | base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/lib/features/trip/controller/trips_list.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'trips_list.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$tripsListHash() => r'3e51ec4baddbb143787d53cbfba723bfb9a88aaa';
10 |
11 | /// See also [TripsList].
12 | @ProviderFor(TripsList)
13 | final tripsListProvider =
14 | AutoDisposeAsyncNotifierProvider>.internal(
15 | TripsList.new,
16 | name: r'tripsListProvider',
17 | debugGetCreateSourceHash:
18 | const bool.fromEnvironment('dart.vm.product') ? null : _$tripsListHash,
19 | dependencies: null,
20 | allTransitiveDependencies: null,
21 | );
22 |
23 | typedef _$TripsList = AutoDisposeAsyncNotifier>;
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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/common/ui/bottomsheet_text_form_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BottomSheetTextFormField extends StatelessWidget {
4 | const BottomSheetTextFormField({
5 | required this.labelText,
6 | required this.controller,
7 | required this.keyboardType,
8 | this.onTap,
9 | super.key,
10 | });
11 |
12 | final String labelText;
13 | final TextEditingController controller;
14 | final TextInputType keyboardType;
15 | final void Function()? onTap;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return TextFormField(
20 | controller: controller,
21 | keyboardType: keyboardType,
22 | autofocus: true,
23 | autocorrect: false,
24 | textInputAction: TextInputAction.next,
25 | validator: (value) {
26 | if (value == null || value.isEmpty) {
27 | return 'Please enter a value';
28 | }
29 |
30 | return null;
31 | },
32 | decoration: InputDecoration(
33 | labelText: labelText,
34 | ),
35 | onTap: onTap,
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/features/trip/controller/past_trips_list.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'past_trips_list.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$pastTripsListHash() => r'59549eda47044c05047c3093fbe2f5aacf35bfa9';
10 |
11 | /// See also [PastTripsList].
12 | @ProviderFor(PastTripsList)
13 | final pastTripsListProvider =
14 | AutoDisposeAsyncNotifierProvider>.internal(
15 | PastTripsList.new,
16 | name: r'pastTripsListProvider',
17 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
18 | ? null
19 | : _$pastTripsListHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef _$PastTripsList = AutoDisposeAsyncNotifier>;
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/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_api/amplify_api.dart';
2 | import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
3 | import 'package:amplify_flutter/amplify_flutter.dart';
4 | import 'package:amplify_storage_s3/amplify_storage_s3.dart';
5 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
6 | import 'package:amplify_trips_planner/trips_planner_app.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_riverpod/flutter_riverpod.dart';
9 |
10 | import 'amplifyconfiguration.dart';
11 |
12 | Future main() async {
13 | WidgetsFlutterBinding.ensureInitialized();
14 | try {
15 | await _configureAmplify();
16 | } on AmplifyAlreadyConfiguredException {
17 | debugPrint('Amplify configuration failed.');
18 | }
19 |
20 | runApp(
21 | const ProviderScope(
22 | child: TripsPlannerApp(),
23 | ),
24 | );
25 | }
26 |
27 | Future _configureAmplify() async {
28 | await Amplify.addPlugins([
29 | AmplifyAuthCognito(),
30 | AmplifyAPI(modelProvider: ModelProvider.instance),
31 | AmplifyStorageS3()
32 | ]);
33 | await Amplify.configure(amplifyconfig);
34 | }
35 |
--------------------------------------------------------------------------------
/lib/features/profile/ui/profile_page/profile_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/ui/the_navigation_drawer.dart';
2 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
3 | import 'package:amplify_trips_planner/features/profile/controller/profile_controller.dart';
4 | import 'package:amplify_trips_planner/features/profile/ui/profile_page/profile_listview.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | class ProfilePage extends ConsumerWidget {
9 | const ProfilePage({
10 | super.key,
11 | });
12 |
13 | @override
14 | Widget build(BuildContext context, WidgetRef ref) {
15 | final profileValue = ref.watch(profileControllerProvider);
16 | return Scaffold(
17 | appBar: AppBar(
18 | centerTitle: true,
19 | title: const Text(
20 | 'Amplify Trips Planner',
21 | ),
22 | backgroundColor: const Color(constants.primaryColorDark),
23 | ),
24 | drawer: const TheNavigationDrawer(),
25 | body: ProfileListView(
26 | profile: profileValue,
27 | ),
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/features/profile/controller/profile_controller.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'profile_controller.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$profileControllerHash() => r'09d513dcea0ba43f01ade4141fd877b04e540df7';
10 |
11 | /// See also [ProfileController].
12 | @ProviderFor(ProfileController)
13 | final profileControllerProvider =
14 | AutoDisposeAsyncNotifierProvider.internal(
15 | ProfileController.new,
16 | name: r'profileControllerProvider',
17 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
18 | ? null
19 | : _$profileControllerHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef _$ProfileController = AutoDisposeAsyncNotifier;
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 |
--------------------------------------------------------------------------------
/amplify/backend/api/amplifytripsplanner/schema.graphql:
--------------------------------------------------------------------------------
1 | type Trip @model @auth(rules: [{ allow: owner }]) {
2 | id: ID!
3 | tripName: String!
4 | destination: String!
5 | startDate: AWSDate!
6 | endDate: AWSDate!
7 | tripImageUrl: String
8 | tripImageKey: String
9 | Activities: [Activity] @hasMany(indexName: "byTrip", fields: ["id"])
10 | }
11 |
12 | type Activity @model @auth(rules: [{allow: owner}]) {
13 | id: ID!
14 | activityName: String!
15 | tripID: ID! @index(name: "byTrip", sortKeyFields: ["activityName"])
16 | trip: Trip! @belongsTo(fields: ["tripID"])
17 | activityImageUrl: String
18 | activityImageKey: String
19 | activityDate: AWSDate!
20 | activityTime: AWSTime
21 | category: ActivityCategory!
22 | }
23 |
24 |
25 |
26 | type Profile
27 | @model
28 | @auth(
29 | rules: [
30 | { allow: private, provider: iam }
31 | { allow: owner, operations: [read, update, create] }
32 | ]
33 | ) {
34 | id: ID!
35 | email: String!
36 | firstName: String
37 | lastName: String
38 | homeCity: String
39 | owner: String!
40 | }
41 |
42 | enum ActivityCategory { Flight, Lodging, Meeting, Restaurant }
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/past_trips/past_trips_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/ui/the_navigation_drawer.dart';
2 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
3 | import 'package:amplify_trips_planner/features/trip/controller/past_trips_list.dart';
4 | import 'package:amplify_trips_planner/features/trip/ui/trips_gridview/trips_list_gridview.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | class PastTripsList extends ConsumerWidget {
9 | const PastTripsList({
10 | super.key,
11 | });
12 |
13 | @override
14 | Widget build(BuildContext context, WidgetRef ref) {
15 | final tripsListValue = ref.watch(pastTripsListProvider);
16 |
17 | return Scaffold(
18 | appBar: AppBar(
19 | centerTitle: true,
20 | title: const Text(
21 | 'Amplify Trips Planner',
22 | ),
23 | backgroundColor: const Color(constants.primaryColorDark),
24 | ),
25 | drawer: const TheNavigationDrawer(),
26 | body: TripsListGridView(
27 | tripsList: tripsListValue,
28 | isPast: true,
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:amplify_trips_planner/trips_planner_app.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_test/flutter_test.dart';
11 |
12 | void main() {
13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(const TripsPlannerApp());
16 |
17 | // Verify that our counter starts at 0.
18 | expect(find.text('0'), findsOneWidget);
19 | expect(find.text('1'), findsNothing);
20 |
21 | // Tap the '+' icon and trigger a frame.
22 | await tester.tap(find.byIcon(Icons.add));
23 | await tester.pump();
24 |
25 | // Verify that our counter has incremented.
26 | expect(find.text('0'), findsNothing);
27 | expect(find.text('1'), findsOneWidget);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/lib/features/trip/data/trips_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/trip/service/trips_api_service.dart';
2 | import 'package:amplify_trips_planner/models/Trip.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 |
5 | final tripsRepositoryProvider = Provider((ref) {
6 | final tripsAPIService = ref.read(tripsAPIServiceProvider);
7 | return TripsRepository(tripsAPIService);
8 | });
9 |
10 | class TripsRepository {
11 | TripsRepository(this.tripsAPIService);
12 |
13 | final TripsAPIService tripsAPIService;
14 |
15 | Future> getTrips() {
16 | return tripsAPIService.getTrips();
17 | }
18 |
19 | Future> getPastTrips() {
20 | return tripsAPIService.getPastTrips();
21 | }
22 |
23 | Future add(Trip trip) async {
24 | return tripsAPIService.addTrip(trip);
25 | }
26 |
27 | Future update(Trip updatedTrip) async {
28 | return tripsAPIService.updateTrip(updatedTrip);
29 | }
30 |
31 | Future delete(Trip deletedTrip) async {
32 | return tripsAPIService.deleteTrip(deletedTrip);
33 | }
34 |
35 | Future getTrip(String tripId) async {
36 | return tripsAPIService.getTrip(tripId);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/features/profile/service/profile_api_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_api/amplify_api.dart';
2 | import 'package:amplify_flutter/amplify_flutter.dart';
3 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 |
6 | final profileAPIServiceProvider = Provider((ref) {
7 | return ProfileAPIService();
8 | });
9 |
10 | class ProfileAPIService {
11 | ProfileAPIService();
12 |
13 | Future getProfile() async {
14 | try {
15 | final request = ModelQueries.list(Profile.classType);
16 | final response = await Amplify.API.query(request: request).response;
17 |
18 | final profile = response.data!.items.first;
19 |
20 | return profile!;
21 | } on Exception catch (error) {
22 | safePrint('getProfile failed: $error');
23 | rethrow;
24 | }
25 | }
26 |
27 | Future updateProfile(Profile updatedProfile) async {
28 | try {
29 | await Amplify.API
30 | .mutate(
31 | request: ModelMutations.update(updatedProfile),
32 | )
33 | .response;
34 | } on Exception catch (error) {
35 | safePrint('updateProfile failed: $error');
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activity_page/activity_page_appbar_icon.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 | import 'package:go_router/go_router.dart';
6 |
7 | class ActivityPageAppBarIcon extends StatelessWidget {
8 | const ActivityPageAppBarIcon({
9 | super.key,
10 | required this.activity,
11 | });
12 |
13 | final AsyncValue activity;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | switch (activity) {
18 | case AsyncData(:final value):
19 | return IconButton(
20 | onPressed: () {
21 | context.goNamed(
22 | AppRoute.trip.name,
23 | pathParameters: {'id': value.trip.id},
24 | );
25 | },
26 | icon: const Icon(Icons.arrow_back),
27 | );
28 |
29 | case AsyncError():
30 | return const Placeholder();
31 | case AsyncLoading():
32 | return const SizedBox();
33 |
34 | case _:
35 | return const Center(
36 | child: Text('Error'),
37 | );
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/features/activity/data/activities_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/activity/service/activities_api_service.dart';
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 |
5 | final activitiesRepositoryProvider = Provider((ref) {
6 | final activitiesAPIService = ref.read(activitiesAPIServiceProvider);
7 | return ActivitiesRepository(activitiesAPIService);
8 | });
9 |
10 | class ActivitiesRepository {
11 | ActivitiesRepository(
12 | this.activitiesAPIService,
13 | );
14 |
15 | final ActivitiesAPIService activitiesAPIService;
16 |
17 | Future> getActivitiesForTrip(String tripId) {
18 | return activitiesAPIService.getActivitiesForTrip(tripId);
19 | }
20 |
21 | Future getActivity(String activityId) {
22 | return activitiesAPIService.getActivity(activityId);
23 | }
24 |
25 | Future add(Activity activity) async {
26 | return activitiesAPIService.addActivity(activity);
27 | }
28 |
29 | Future delete(Activity activity) async {
30 | return activitiesAPIService.deleteActivity(activity);
31 | }
32 |
33 | Future update(Activity activity) async {
34 | return activitiesAPIService.updateActivity(activity);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activity_page/activity_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
2 | import 'package:amplify_trips_planner/features/activity/controller/activity_controller.dart';
3 | import 'package:amplify_trips_planner/features/activity/ui/activity_page/activity_listview.dart';
4 | import 'package:amplify_trips_planner/features/activity/ui/activity_page/activity_page_appbar_icon.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | class ActivityPage extends ConsumerWidget {
9 | const ActivityPage({
10 | required this.activityId,
11 | super.key,
12 | });
13 |
14 | final String activityId;
15 |
16 | @override
17 | Widget build(BuildContext context, WidgetRef ref) {
18 | final activityValue = ref.watch(activityControllerProvider(activityId));
19 |
20 | return Scaffold(
21 | appBar: AppBar(
22 | centerTitle: true,
23 | title: const Text(
24 | 'Amplify Trips Planner',
25 | ),
26 | leading: ActivityPageAppBarIcon(
27 | activity: activityValue,
28 | ),
29 | backgroundColor: const Color(constants.primaryColorDark),
30 | ),
31 | body: ActivityListView(
32 | activity: activityValue,
33 | ),
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trip_page/trip_page_floating_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
3 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | class TripPageFloatingButton extends StatelessWidget {
9 | const TripPageFloatingButton({
10 | required this.trip,
11 | super.key,
12 | });
13 |
14 | final AsyncValue trip;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | switch (trip) {
19 | case AsyncData(:final value):
20 | return FloatingActionButton(
21 | onPressed: () {
22 | context.goNamed(
23 | AppRoute.addActivity.name,
24 | pathParameters: {'id': value.id},
25 | );
26 | },
27 | backgroundColor: const Color(constants.primaryColorDark),
28 | child: const Icon(Icons.add),
29 | );
30 |
31 | case AsyncError():
32 | return const Placeholder();
33 | case AsyncLoading():
34 | return const SizedBox();
35 |
36 | case _:
37 | return const SizedBox();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activities_list/activities_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/activity/controller/activities_list.dart';
2 | import 'package:amplify_trips_planner/features/activity/ui/activities_list/activities_timeline.dart';
3 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 |
7 | class ActivitiesList extends ConsumerWidget {
8 | const ActivitiesList({
9 | required this.trip,
10 | super.key,
11 | });
12 | final Trip trip;
13 |
14 | @override
15 | Widget build(BuildContext context, WidgetRef ref) {
16 | final activitiesListValue = ref.watch(activitiesListProvider(trip.id));
17 | switch (activitiesListValue) {
18 | case AsyncData(:final value):
19 | return value.isEmpty
20 | ? const Center(
21 | child: Text('No Activities'),
22 | )
23 | : ActivitiesTimeline(activities: value);
24 |
25 | case AsyncError():
26 | return const Center(
27 | child: Text('Error'),
28 | );
29 | case AsyncLoading():
30 | return const Center(
31 | child: CircularProgressIndicator(),
32 | );
33 |
34 | case _:
35 | return const Center(
36 | child: Text('Error'),
37 | );
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/src/index.js:
--------------------------------------------------------------------------------
1 | /* Amplify Params - DO NOT EDIT
2 | API_AMPLIFYTRIPSPLANNER_GRAPHQLAPIENDPOINTOUTPUT
3 | API_AMPLIFYTRIPSPLANNER_GRAPHQLAPIIDOUTPUT
4 | ENV
5 | REGION
6 | Amplify Params - DO NOT EDIT *//**
7 | * @fileoverview
8 | *
9 | * This CloudFormation Trigger creates a handler which awaits the other handlers
10 | * specified in the `MODULES` env var, located at `./${MODULE}`.
11 | */
12 |
13 | /**
14 | * The names of modules to load are stored as a comma-delimited string in the
15 | * `MODULES` env var.
16 | */
17 | const moduleNames = process.env.MODULES.split(',');
18 | /**
19 | * The array of imported modules.
20 | */
21 | const modules = moduleNames.map((name) => require(`./${name}`));
22 |
23 | /**
24 | * This async handler iterates over the given modules and awaits them.
25 | *
26 | * @see https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html#nodejs-handler-async
27 | * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
28 | *
29 | */
30 | exports.handler = async (event, context) => {
31 | /**
32 | * Instead of naively iterating over all handlers, run them concurrently with
33 | * `await Promise.all(...)`. This would otherwise just be determined by the
34 | * order of names in the `MODULES` var.
35 | */
36 | await Promise.all(modules.map((module) => module.handler(event, context)));
37 | return event;
38 | };
39 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trips_gridview/trip_gridview_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
3 | import 'package:amplify_trips_planner/features/trip/ui/trips_gridview/trip_gridview_item_card.dart';
4 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | class TripGridViewItem extends StatelessWidget {
9 | const TripGridViewItem({
10 | required this.trip,
11 | required this.isPast,
12 | super.key,
13 | });
14 |
15 | final Trip trip;
16 | final bool isPast;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return InkWell(
21 | splashColor: Theme.of(context).primaryColor,
22 | borderRadius: BorderRadius.circular(15),
23 | onTap: () {
24 | context.goNamed(
25 | isPast ? AppRoute.pastTrip.name : AppRoute.trip.name,
26 | pathParameters: {'id': trip.id},
27 | extra: trip,
28 | );
29 | },
30 | child: isPast
31 | ? ColorFiltered(
32 | colorFilter: const ColorFilter.matrix(constants.greyoutMatrix),
33 | child: TripGridViewItemCard(
34 | trip: trip,
35 | ),
36 | )
37 | : TripGridViewItemCard(
38 | trip: trip,
39 | ),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.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 | #amplify-do-not-edit-begin
47 | amplify/\#current-cloud-backend
48 | amplify/.config/local-*
49 | amplify/logs
50 | amplify/mock-data
51 | amplify/mock-api-resources
52 | amplify/backend/amplify-meta.json
53 | amplify/backend/.temp
54 | build/
55 | dist/
56 | node_modules/
57 | aws-exports.js
58 | awsconfiguration.json
59 | amplifyconfiguration.json
60 | amplifyconfiguration.dart
61 | amplify-build-config.json
62 | amplify-gradle-config.json
63 | amplifytools.xcconfig
64 | .secret-*
65 | **.sample
66 | #amplify-do-not-edit-end
67 | lib/test.dart
68 | lib/somthing.md
69 |
70 | team-provider-info.json
71 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '13.5'
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 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activity_category_icon.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class ActivityCategoryIcon extends StatelessWidget {
6 | const ActivityCategoryIcon({
7 | required this.activityCategory,
8 | super.key,
9 | });
10 | final ActivityCategory activityCategory;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | switch (activityCategory) {
15 | case ActivityCategory.Flight:
16 | return const Icon(
17 | Icons.flight,
18 | size: 50,
19 | color: Color(constants.primaryColorDark),
20 | );
21 |
22 | case ActivityCategory.Lodging:
23 | return const Icon(
24 | Icons.hotel,
25 | size: 50,
26 | color: Color(constants.primaryColorDark),
27 | );
28 | case ActivityCategory.Meeting:
29 | return const Icon(
30 | Icons.computer,
31 | size: 50,
32 | color: Color(constants.primaryColorDark),
33 | );
34 | case ActivityCategory.Restaurant:
35 | return const Icon(
36 | Icons.restaurant,
37 | size: 50,
38 | color: Color(constants.primaryColorDark),
39 | );
40 | default:
41 | ActivityCategory.Flight;
42 | }
43 | return const Icon(
44 | Icons.flight,
45 | size: 50,
46 | color: Color(constants.primaryColorDark),
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/add_activity/add_activity_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
3 | import 'package:amplify_trips_planner/features/activity/ui/add_activity/add_activity_form.dart';
4 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 | import 'package:go_router/go_router.dart';
8 |
9 | class AddActivityPage extends ConsumerWidget {
10 | AddActivityPage({
11 | required this.tripId,
12 | super.key,
13 | });
14 |
15 | final String tripId;
16 |
17 | final formGlobalKey = GlobalKey();
18 |
19 | @override
20 | Widget build(BuildContext context, WidgetRef ref) {
21 | final tripValue = ref.watch(tripControllerProvider(tripId));
22 |
23 | return Scaffold(
24 | appBar: AppBar(
25 | centerTitle: true,
26 | title: const Text(
27 | 'Amplify Trips Planner',
28 | ),
29 | leading: IconButton(
30 | onPressed: () {
31 | context.goNamed(
32 | AppRoute.trip.name,
33 | pathParameters: {'id': tripId},
34 | );
35 | },
36 | icon: const Icon(Icons.arrow_back),
37 | ),
38 | backgroundColor: const Color(constants.primaryColorDark),
39 | ),
40 | body: AddActivityForm(
41 | trip: tripValue,
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/common/ui/upload_progress_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_riverpod/flutter_riverpod.dart';
4 |
5 | class UploadProgressDialog extends ConsumerWidget {
6 | const UploadProgressDialog({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context, WidgetRef ref) {
10 | return Dialog(
11 | backgroundColor: Colors.white,
12 | child: Padding(
13 | padding: const EdgeInsets.symmetric(vertical: 20),
14 | child: ValueListenableBuilder(
15 | valueListenable:
16 | ref.read(tripControllerProvider('').notifier).uploadProgress(),
17 | builder: (context, value, child) {
18 | return Column(
19 | mainAxisSize: MainAxisSize.min,
20 | children: [
21 | const CircularProgressIndicator(),
22 | const SizedBox(
23 | height: 15,
24 | ),
25 | Text('${(double.parse(value.toString()) * 100).toInt()} %'),
26 | Container(
27 | alignment: Alignment.topCenter,
28 | margin: const EdgeInsets.all(20),
29 | child: LinearProgressIndicator(
30 | value: double.parse(value.toString()),
31 | backgroundColor: Colors.grey,
32 | color: Colors.purple,
33 | minHeight: 10,
34 | ),
35 | ),
36 | ],
37 | );
38 | },
39 | ),
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/past_trip_page/past_trip_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/ui/the_navigation_drawer.dart';
3 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
4 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
5 | import 'package:amplify_trips_planner/features/trip/ui/past_trip_page/past_trip_details.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_riverpod/flutter_riverpod.dart';
8 | import 'package:go_router/go_router.dart';
9 |
10 | class PastTripPage extends ConsumerWidget {
11 | const PastTripPage({
12 | required this.tripId,
13 | super.key,
14 | });
15 | final String tripId;
16 |
17 | @override
18 | Widget build(BuildContext context, WidgetRef ref) {
19 | final tripValue = ref.watch(tripControllerProvider(tripId));
20 | return Scaffold(
21 | appBar: AppBar(
22 | centerTitle: true,
23 | title: const Text(
24 | 'Amplify Trips Planner',
25 | ),
26 | actions: [
27 | IconButton(
28 | onPressed: () {
29 | context.goNamed(
30 | AppRoute.home.name,
31 | );
32 | },
33 | icon: const Icon(Icons.home),
34 | ),
35 | ],
36 | backgroundColor: const Color(constants.primaryColorDark),
37 | ),
38 | drawer: const TheNavigationDrawer(),
39 | body: ColorFiltered(
40 | colorFilter: const ColorFilter.matrix(constants.greyoutMatrix),
41 | child: PastTripDetails(
42 | trip: tripValue,
43 | ),
44 | ),
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/features/trip/controller/trips_list.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:amplify_flutter/amplify_flutter.dart';
4 | import 'package:amplify_trips_planner/features/trip/data/trips_repository.dart';
5 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
6 | import 'package:riverpod_annotation/riverpod_annotation.dart';
7 |
8 | part 'trips_list.g.dart';
9 |
10 | @riverpod
11 | class TripsList extends _$TripsList {
12 | Future> _fetchTrips() async {
13 | final tripsRepository = ref.read(tripsRepositoryProvider);
14 | final trips = await tripsRepository.getTrips();
15 | return trips;
16 | }
17 |
18 | @override
19 | FutureOr> build() async {
20 | return _fetchTrips();
21 | }
22 |
23 | Future addTrip({
24 | required String name,
25 | required String destination,
26 | required String startDate,
27 | required String endDate,
28 | }) async {
29 | final trip = Trip(
30 | tripName: name,
31 | destination: destination,
32 | startDate: TemporalDate(DateTime.parse(startDate)),
33 | endDate: TemporalDate(DateTime.parse(endDate)),
34 | );
35 |
36 | state = const AsyncValue.loading();
37 |
38 | state = await AsyncValue.guard(() async {
39 | final tripsRepository = ref.read(tripsRepositoryProvider);
40 | await tripsRepository.add(trip);
41 | return _fetchTrips();
42 | });
43 | }
44 |
45 | Future removeTrip(Trip trip) async {
46 | state = const AsyncValue.loading();
47 | state = await AsyncValue.guard(() async {
48 | final tripsRepository = ref.read(tripsRepositoryProvider);
49 | await tripsRepository.delete(trip);
50 |
51 | return _fetchTrips();
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/models/ActivityCategory.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | // NOTE: This file is generated and may not follow lint rules defined in your app
17 | // Generated files can be excluded from analysis in analysis_options.yaml
18 | // For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis
19 |
20 | // ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously
21 |
22 | enum ActivityCategory { Flight, Lodging, Meeting, Restaurant }
23 |
--------------------------------------------------------------------------------
/amplify/backend/api/amplifytripsplanner/stacks/CustomResources.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "An auto-generated nested stack.",
4 | "Metadata": {},
5 | "Parameters": {
6 | "AppSyncApiId": {
7 | "Type": "String",
8 | "Description": "The id of the AppSync API associated with this project."
9 | },
10 | "AppSyncApiName": {
11 | "Type": "String",
12 | "Description": "The name of the AppSync API",
13 | "Default": "AppSyncSimpleTransform"
14 | },
15 | "env": {
16 | "Type": "String",
17 | "Description": "The environment name. e.g. Dev, Test, or Production",
18 | "Default": "NONE"
19 | },
20 | "S3DeploymentBucket": {
21 | "Type": "String",
22 | "Description": "The S3 bucket containing all deployment assets for the project."
23 | },
24 | "S3DeploymentRootKey": {
25 | "Type": "String",
26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory."
27 | }
28 | },
29 | "Resources": {
30 | "EmptyResource": {
31 | "Type": "Custom::EmptyResource",
32 | "Condition": "AlwaysFalse"
33 | }
34 | },
35 | "Conditions": {
36 | "HasEnvironmentParameter": {
37 | "Fn::Not": [
38 | {
39 | "Fn::Equals": [
40 | {
41 | "Ref": "env"
42 | },
43 | "NONE"
44 | ]
45 | }
46 | ]
47 | },
48 | "AlwaysFalse": {
49 | "Fn::Equals": ["true", "false"]
50 | }
51 | },
52 | "Outputs": {
53 | "EmptyOutput": {
54 | "Description": "An empty output. You may delete this if you have at least one resource above.",
55 | "Value": ""
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trip_page/trip_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/ui/the_navigation_drawer.dart';
3 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
4 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
5 | import 'package:amplify_trips_planner/features/trip/ui/trip_page/trip_details.dart';
6 | import 'package:amplify_trips_planner/features/trip/ui/trip_page/trip_page_floating_button.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_riverpod/flutter_riverpod.dart';
9 | import 'package:go_router/go_router.dart';
10 |
11 | class TripPage extends ConsumerWidget {
12 | const TripPage({
13 | required this.tripId,
14 | super.key,
15 | });
16 | final String tripId;
17 |
18 | @override
19 | Widget build(BuildContext context, WidgetRef ref) {
20 | final tripValue = ref.watch(tripControllerProvider(tripId));
21 |
22 | return Scaffold(
23 | appBar: AppBar(
24 | centerTitle: true,
25 | title: const Text(
26 | 'Amplify Trips Planner',
27 | ),
28 | actions: [
29 | IconButton(
30 | onPressed: () {
31 | context.goNamed(
32 | AppRoute.home.name,
33 | );
34 | },
35 | icon: const Icon(Icons.home),
36 | ),
37 | ],
38 | backgroundColor: const Color(constants.primaryColorDark),
39 | ),
40 | drawer: const TheNavigationDrawer(),
41 | floatingActionButton: TripPageFloatingButton(
42 | trip: tripValue,
43 | ),
44 | body: TripDetails(
45 | tripId: tripId,
46 | trip: tripValue,
47 | ),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/features/trip/controller/trip_controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 |
4 | import 'package:amplify_trips_planner/common/services/storage_service.dart';
5 | import 'package:amplify_trips_planner/features/trip/data/trips_repository.dart';
6 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:riverpod_annotation/riverpod_annotation.dart';
9 |
10 | part 'trip_controller.g.dart';
11 |
12 | @riverpod
13 | class TripController extends _$TripController {
14 | Future _fetchTrip(String tripId) async {
15 | final tripsRepository = ref.read(tripsRepositoryProvider);
16 | return tripsRepository.getTrip(tripId);
17 | }
18 |
19 | @override
20 | FutureOr build(String tripId) async {
21 | return _fetchTrip(tripId);
22 | }
23 |
24 | Future updateTrip(Trip trip) async {
25 | state = const AsyncValue.loading();
26 | state = await AsyncValue.guard(() async {
27 | final tripsRepository = ref.read(tripsRepositoryProvider);
28 | await tripsRepository.update(trip);
29 | return _fetchTrip(trip.id);
30 | });
31 | }
32 |
33 | Future uploadFile(File file, Trip trip) async {
34 | final fileKey = await ref.read(storageServiceProvider).uploadFile(file);
35 | if (fileKey != null) {
36 | final imageUrl =
37 | await ref.read(storageServiceProvider).getImageUrl(fileKey);
38 | final updatedTrip =
39 | trip.copyWith(tripImageKey: fileKey, tripImageUrl: imageUrl);
40 | await ref.read(tripsRepositoryProvider).update(updatedTrip);
41 | ref.read(storageServiceProvider).resetUploadProgress();
42 | }
43 | }
44 |
45 | ValueNotifier uploadProgress() {
46 | return ref.read(storageServiceProvider).getUploadProgress();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trips_list/trips_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/ui/the_navigation_drawer.dart';
2 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
3 | import 'package:amplify_trips_planner/features/trip/controller/trips_list.dart';
4 | import 'package:amplify_trips_planner/features/trip/ui/trips_gridview/trips_list_gridview.dart';
5 | import 'package:amplify_trips_planner/features/trip/ui/trips_list/add_trip_bottomsheet.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_riverpod/flutter_riverpod.dart';
8 |
9 | class TripsListPage extends ConsumerWidget {
10 | const TripsListPage({
11 | super.key,
12 | });
13 |
14 | Future showAddTripDialog(BuildContext context) =>
15 | showModalBottomSheet(
16 | isScrollControlled: true,
17 | elevation: 5,
18 | context: context,
19 | builder: (sheetContext) {
20 | return AddTripBottomSheet();
21 | },
22 | );
23 |
24 | @override
25 | Widget build(BuildContext context, WidgetRef ref) {
26 | final tripsListValue = ref.watch(tripsListProvider);
27 | return Scaffold(
28 | appBar: AppBar(
29 | centerTitle: true,
30 | title: const Text(
31 | 'Amplify Trips Planner',
32 | ),
33 | backgroundColor: const Color(constants.primaryColorDark),
34 | ),
35 | drawer: const TheNavigationDrawer(),
36 | floatingActionButton: FloatingActionButton(
37 | onPressed: () {
38 | showAddTripDialog(context);
39 | },
40 | backgroundColor: const Color(constants.primaryColorDark),
41 | child: const Icon(Icons.add),
42 | ),
43 | body: TripsListGridView(
44 | tripsList: tripsListValue,
45 | isPast: false,
46 | ),
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/common/services/storage_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:amplify_flutter/amplify_flutter.dart';
4 | import 'package:amplify_storage_s3/amplify_storage_s3.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 | import 'package:path/path.dart' as p;
8 | import 'package:uuid/uuid.dart';
9 |
10 | final storageServiceProvider = Provider((ref) {
11 | return StorageService(ref: ref);
12 | });
13 |
14 | class StorageService {
15 | StorageService({
16 | required Ref ref,
17 | });
18 |
19 | ValueNotifier uploadProgress = ValueNotifier(0);
20 | Future getImageUrl(String key) async {
21 | final result = await Amplify.Storage.getUrl(
22 | key: key,
23 | options: const StorageGetUrlOptions(
24 | pluginOptions: S3GetUrlPluginOptions(
25 | validateObjectExistence: true,
26 | expiresIn: Duration(days: 1),
27 | ),
28 | ),
29 | ).result;
30 | return result.url.toString();
31 | }
32 |
33 | ValueNotifier getUploadProgress() {
34 | return uploadProgress;
35 | }
36 |
37 | Future uploadFile(File file) async {
38 | try {
39 | final extension = p.extension(file.path);
40 | final key = const Uuid().v1() + extension;
41 | final awsFile = AWSFile.fromPath(file.path);
42 |
43 | await Amplify.Storage.uploadFile(
44 | localFile: awsFile,
45 | key: key,
46 | onProgress: (progress) {
47 | uploadProgress.value = progress.fractionCompleted;
48 | },
49 | ).result;
50 |
51 | return key;
52 | } on Exception catch (e) {
53 | debugPrint(e.toString());
54 | return null;
55 | }
56 | }
57 |
58 | void resetUploadProgress() {
59 | uploadProgress.value = 0;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/past_trip_page/past_trip_details.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/trip/ui/past_trip_page/selected_past_trip_card.dart';
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 |
6 | class PastTripDetails extends ConsumerWidget {
7 | const PastTripDetails({
8 | required this.trip,
9 | super.key,
10 | });
11 |
12 | final AsyncValue trip;
13 |
14 | @override
15 | Widget build(BuildContext context, WidgetRef ref) {
16 | switch (trip) {
17 | case AsyncData(:final value):
18 | return Column(
19 | crossAxisAlignment: CrossAxisAlignment.stretch,
20 | children: [
21 | const SizedBox(
22 | height: 8,
23 | ),
24 | SelectedPastTripCard(trip: value),
25 | const SizedBox(
26 | height: 20,
27 | ),
28 | const Divider(
29 | height: 20,
30 | thickness: 5,
31 | indent: 20,
32 | endIndent: 20,
33 | ),
34 | const Text(
35 | 'Your Activities',
36 | textAlign: TextAlign.center,
37 | style: TextStyle(
38 | fontSize: 20,
39 | fontWeight: FontWeight.bold,
40 | ),
41 | ),
42 | const SizedBox(
43 | height: 8,
44 | ),
45 | ],
46 | );
47 |
48 | case AsyncError():
49 | return const Center(
50 | child: Text('Error'),
51 | );
52 | case AsyncLoading():
53 | return const Center(
54 | child: CircularProgressIndicator(),
55 | );
56 |
57 | case _:
58 | return const Center(
59 | child: Text('Error'),
60 | );
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trips_gridview/trips_list_gridview.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/trip/ui/trips_gridview/trip_gridview_item.dart';
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_riverpod/flutter_riverpod.dart';
5 |
6 | class TripsListGridView extends StatelessWidget {
7 | const TripsListGridView({
8 | required this.tripsList,
9 | required this.isPast,
10 | super.key,
11 | });
12 |
13 | final AsyncValue> tripsList;
14 | final bool isPast;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | switch (tripsList) {
19 | case AsyncData(:final value):
20 | return value.isEmpty
21 | ? const Center(
22 | child: Text('No Trips'),
23 | )
24 | : OrientationBuilder(
25 | builder: (context, orientation) {
26 | return GridView.count(
27 | crossAxisCount:
28 | (orientation == Orientation.portrait) ? 2 : 3,
29 | mainAxisSpacing: 4,
30 | crossAxisSpacing: 4,
31 | padding: const EdgeInsets.all(4),
32 | childAspectRatio:
33 | (orientation == Orientation.portrait) ? 0.9 : 1.4,
34 | children: value.map((tripData) {
35 | return TripGridViewItem(
36 | trip: tripData,
37 | isPast: isPast,
38 | );
39 | }).toList(growable: false),
40 | );
41 | },
42 | );
43 |
44 | case AsyncError():
45 | return const Center(
46 | child: Text('Error'),
47 | );
48 | case AsyncLoading():
49 | return const Center(
50 | child: CircularProgressIndicator(),
51 | );
52 |
53 | case _:
54 | return const Center(
55 | child: Text('Error'),
56 | );
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/features/activity/controller/activity_controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:amplify_trips_planner/common/services/storage_service.dart';
4 | import 'package:amplify_trips_planner/features/activity/data/activities_repository.dart';
5 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:riverpod_annotation/riverpod_annotation.dart';
8 |
9 | part 'activity_controller.g.dart';
10 |
11 | @riverpod
12 | class ActivityController extends _$ActivityController {
13 | Future _fetchActivity(String activityId) async {
14 | final activitiesRepository = ref.read(activitiesRepositoryProvider);
15 | return activitiesRepository.getActivity(activityId);
16 | }
17 |
18 | @override
19 | FutureOr build(String activityId) async {
20 | return _fetchActivity(activityId);
21 | }
22 |
23 | Future uploadFile(File file, Activity activity) async {
24 | final fileKey = await ref.read(storageServiceProvider).uploadFile(file);
25 | if (fileKey != null) {
26 | final imageUrl =
27 | await ref.read(storageServiceProvider).getImageUrl(fileKey);
28 | final updatedActivity = activity.copyWith(
29 | activityImageKey: fileKey,
30 | activityImageUrl: imageUrl,
31 | );
32 | await updateActivity(updatedActivity);
33 | ref.read(storageServiceProvider).resetUploadProgress();
34 | }
35 | }
36 |
37 | Future getFileUrl(Activity activity) async {
38 | final fileKey = activity.activityImageKey;
39 |
40 | return ref.read(storageServiceProvider).getImageUrl(fileKey!);
41 | }
42 |
43 | ValueNotifier uploadProgress() {
44 | return ref.read(storageServiceProvider).getUploadProgress();
45 | }
46 |
47 | Future updateActivity(Activity activity) async {
48 | state = const AsyncValue.loading();
49 | state = await AsyncValue.guard(() async {
50 | final activitiesRepository = ref.read(activitiesRepositoryProvider);
51 | await activitiesRepository.update(activity);
52 | return _fetchActivity(activity.id);
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/amplify/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "features": {
3 | "graphqltransformer": {
4 | "addmissingownerfields": true,
5 | "improvepluralization": false,
6 | "validatetypenamereservedwords": true,
7 | "useexperimentalpipelinedtransformer": true,
8 | "enableiterativegsiupdates": true,
9 | "secondarykeyasgsi": true,
10 | "skipoverridemutationinputtypes": true,
11 | "transformerversion": 2,
12 | "suppressschemamigrationprompt": true,
13 | "securityenhancementnotification": false,
14 | "showfieldauthnotification": false,
15 | "usesubusernamefordefaultidentityclaim": true,
16 | "usefieldnameforprimarykeyconnectionfield": false,
17 | "enableautoindexquerynames": true,
18 | "respectprimarykeyattributesonconnectionfield": true,
19 | "shoulddeepmergedirectiveconfigdefaults": false,
20 | "populateownerfieldforstaticgroupauth": true
21 | },
22 | "frontend-ios": {
23 | "enablexcodeintegration": true
24 | },
25 | "auth": {
26 | "enablecaseinsensitivity": true,
27 | "useinclusiveterminology": true,
28 | "breakcirculardependency": true,
29 | "forcealiasattributes": false,
30 | "useenabledmfas": true
31 | },
32 | "codegen": {
33 | "useappsyncmodelgenplugin": true,
34 | "usedocsgeneratorplugin": true,
35 | "usetypesgeneratorplugin": true,
36 | "cleangeneratedmodelsdirectory": true,
37 | "retaincasestyle": true,
38 | "addtimestampfields": true,
39 | "handlelistnullabilitytransparently": true,
40 | "emitauthprovider": true,
41 | "generateindexrules": true,
42 | "enabledartnullsafety": true,
43 | "generatemodelsforlazyloadandcustomselectionset": false
44 | },
45 | "appsync": {
46 | "generategraphqlpermissions": true
47 | },
48 | "latestregionsupport": {
49 | "pinpoint": 1,
50 | "translate": 1,
51 | "transcribe": 1,
52 | "rekognition": 1,
53 | "textract": 1,
54 | "comprehend": 1
55 | },
56 | "project": {
57 | "overrides": true
58 | }
59 | },
60 | "debug": {
61 | "shareProjectConfig": false
62 | }
63 | }
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Amplify Trips Planner Tutorial
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | amplify_trips_planner_tutorial
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 | NSCameraUsageDescription
51 | Some Description
52 | NSMicrophoneUsageDescription
53 | Some Description
54 | NSPhotoLibraryUsageDescription
55 | Some Description
56 |
57 |
58 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/past_trip_page/selected_past_trip_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
2 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
3 | import 'package:cached_network_image/cached_network_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 |
7 | class SelectedPastTripCard extends ConsumerWidget {
8 | const SelectedPastTripCard({
9 | required this.trip,
10 | super.key,
11 | });
12 |
13 | final Trip trip;
14 |
15 | @override
16 | Widget build(BuildContext context, WidgetRef ref) {
17 | return Card(
18 | clipBehavior: Clip.antiAlias,
19 | shape: RoundedRectangleBorder(
20 | borderRadius: BorderRadius.circular(15),
21 | ),
22 | elevation: 5,
23 | child: Column(
24 | mainAxisSize: MainAxisSize.min,
25 | children: [
26 | Text(
27 | trip.tripName,
28 | textAlign: TextAlign.center,
29 | style: const TextStyle(
30 | fontSize: 20,
31 | fontWeight: FontWeight.bold,
32 | ),
33 | ),
34 | const SizedBox(
35 | height: 8,
36 | ),
37 | Container(
38 | alignment: Alignment.center,
39 | color: const Color(constants.primaryColorDark),
40 | height: 150,
41 | child: trip.tripImageUrl != null
42 | ? Stack(
43 | children: [
44 | const Center(child: CircularProgressIndicator()),
45 | CachedNetworkImage(
46 | imageUrl: trip.tripImageUrl!,
47 | cacheKey: trip.tripImageKey,
48 | width: double.maxFinite,
49 | height: 500,
50 | alignment: Alignment.topCenter,
51 | fit: BoxFit.fill,
52 | ),
53 | ],
54 | )
55 | : Image.asset(
56 | 'images/amplify.png',
57 | fit: BoxFit.contain,
58 | ),
59 | ),
60 | ],
61 | ),
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/features/activity/controller/activities_list.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:amplify_flutter/amplify_flutter.dart';
4 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
5 | import 'package:amplify_trips_planner/features/activity/data/activities_repository.dart';
6 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:riverpod_annotation/riverpod_annotation.dart';
9 |
10 | part 'activities_list.g.dart';
11 |
12 | @riverpod
13 | class ActivitiesList extends _$ActivitiesList {
14 | Future> _fetchActivities(String tripId) async {
15 | final activitiesRepository = ref.read(activitiesRepositoryProvider);
16 | final activities = await activitiesRepository.getActivitiesForTrip(tripId);
17 | return activities;
18 | }
19 |
20 | @override
21 | FutureOr> build(String tripId) async {
22 | return _fetchActivities(tripId);
23 | }
24 |
25 | Future removeActivity(Activity activity) async {
26 | state = const AsyncValue.loading();
27 | state = await AsyncValue.guard(() async {
28 | final activitiesRepository = ref.read(activitiesRepositoryProvider);
29 | await activitiesRepository.delete(activity);
30 |
31 | return _fetchActivities(activity.trip.id);
32 | });
33 | }
34 |
35 | Future add({
36 | required String name,
37 | required String activityDate,
38 | required TimeOfDay activityTime,
39 | required ActivityCategory category,
40 | required Trip trip,
41 | }) async {
42 | final now = DateTime.now();
43 | final time = DateTime(
44 | now.year,
45 | now.month,
46 | now.day,
47 | activityTime.hour,
48 | activityTime.minute,
49 | );
50 |
51 | final activity = Activity(
52 | activityName: name,
53 | activityDate: TemporalDate(DateTime.parse(activityDate)),
54 | activityTime: TemporalTime.fromString(time.format('HH:mm:ss.sss')),
55 | trip: trip,
56 | category: category,
57 | );
58 | state = const AsyncValue.loading();
59 | state = await AsyncValue.guard(() async {
60 | final activitiesRepository = ref.read(activitiesRepositoryProvider);
61 |
62 | await activitiesRepository.add(activity);
63 |
64 | return _fetchActivities(trip.id);
65 | });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/common/ui/the_navigation_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/services/auth_service.dart';
3 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | class TheNavigationDrawer extends ConsumerWidget {
9 | const TheNavigationDrawer({
10 | super.key,
11 | });
12 |
13 | @override
14 | Widget build(BuildContext context, WidgetRef ref) {
15 | return Drawer(
16 | child: ListView(
17 | padding: EdgeInsets.zero,
18 | children: [
19 | const DrawerHeader(
20 | decoration: BoxDecoration(
21 | color: Color(constants.primaryColorDark),
22 | ),
23 | padding: EdgeInsets.all(16),
24 | child: Column(
25 | children: [
26 | SizedBox(height: 10),
27 | Text(
28 | 'Amplify Trips Planner',
29 | style: TextStyle(fontSize: 22, color: Colors.white),
30 | ),
31 | ],
32 | ),
33 | ),
34 | ListTile(
35 | leading: const Icon(Icons.home),
36 | title: const Text('Trips'),
37 | onTap: () {
38 | context.goNamed(
39 | AppRoute.home.name,
40 | );
41 | },
42 | ),
43 | ListTile(
44 | leading: const Icon(Icons.category),
45 | title: const Text('Past Trips'),
46 | onTap: () {
47 | context.goNamed(
48 | AppRoute.pastTrips.name,
49 | );
50 | },
51 | ),
52 | ListTile(
53 | leading: const Icon(Icons.settings),
54 | title: const Text('Settings'),
55 | onTap: () {
56 | context.goNamed(
57 | AppRoute.profile.name,
58 | );
59 | },
60 | ),
61 | ListTile(
62 | leading: const Icon(Icons.exit_to_app),
63 | title: const Text('Logout'),
64 | onTap: () => ref.read(authServiceProvider).signOut(),
65 | ),
66 | ],
67 | ),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/amplify/backend/function/amplifytripsplanner4600aecaPostConfirmation/src/custom.js:
--------------------------------------------------------------------------------
1 | const { Sha256 } = require("@aws-crypto/sha256-js");
2 | const { defaultProvider } = require("@aws-sdk/credential-provider-node");
3 | const { SignatureV4 } = require("@aws-sdk/signature-v4");
4 | const { HttpRequest } = require("@aws-sdk/protocol-http");
5 | const { default: fetch, Request } = require("node-fetch");
6 |
7 | const GRAPHQL_ENDPOINT = process.env.API_AMPLIFYTRIPSPLANNER_GRAPHQLAPIENDPOINTOUTPUT;
8 | const AWS_REGION = process.env.AWS_REGION || 'us-east-1';
9 |
10 | const query = /* GraphQL */ `
11 | mutation createProfile($email: String!,$owner: String!) {
12 | createProfile(input: {
13 | email: $email,
14 | owner: $owner,
15 |
16 | }) {
17 | email
18 | }
19 | }
20 | `;
21 |
22 |
23 |
24 |
25 | /**
26 | * @type {import('@types/aws-lambda').PostConfirmationTriggerHandler}
27 | */
28 | exports.handler = async (event) => {
29 | console.log(`EVENT: ${JSON.stringify(event)}`);
30 |
31 |
32 | const variables = {
33 |
34 | email: event.request.userAttributes.email,
35 | owner: `${event.request.userAttributes.sub}::${event.userName}`
36 |
37 | };
38 |
39 |
40 |
41 |
42 |
43 |
44 | const endpoint = new URL(GRAPHQL_ENDPOINT);
45 |
46 | const signer = new SignatureV4({
47 | credentials: defaultProvider(),
48 | region: AWS_REGION,
49 | service: 'appsync',
50 | sha256: Sha256
51 | });
52 |
53 | const requestToBeSigned = new HttpRequest({
54 | method: 'POST',
55 | headers: {
56 | 'Content-Type': 'application/json',
57 | host: endpoint.host
58 | },
59 | hostname: endpoint.host,
60 | body: JSON.stringify({ query, variables }),
61 | path: endpoint.pathname
62 | });
63 |
64 | const signed = await signer.sign(requestToBeSigned);
65 | const request = new Request(endpoint, signed);
66 |
67 | let statusCode = 200;
68 | let body;
69 | let response;
70 |
71 | try {
72 | response = await fetch(request);
73 | body = await response.json();
74 | if (body.errors) statusCode = 400;
75 | } catch (error) {
76 | statusCode = 500;
77 | body = {
78 | errors: [
79 | {
80 | message: error.message
81 | }
82 | ]
83 | };
84 | }
85 |
86 | console.log(`statusCode: ${statusCode}`);
87 | console.log(`body: ${JSON.stringify(body)}`);
88 |
89 | return {
90 | statusCode,
91 | body: JSON.stringify(body)
92 | };
93 | };
--------------------------------------------------------------------------------
/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 | compileSdkVersion flutter.compileSdkVersion
30 | ndkVersion flutter.ndkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "com.example.amplify_trips_planner"
48 | // You can update the following values to match your application needs.
49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
50 | minSdkVersion 24
51 | targetSdkVersion flutter.targetSdkVersion
52 | versionCode flutterVersionCode.toInteger()
53 | versionName flutterVersionName
54 | }
55 |
56 | buildTypes {
57 | release {
58 | // TODO: Add your own signing config for the release build.
59 | // Signing with the debug keys for now, so `flutter run --release` works.
60 | signingConfig signingConfigs.debug
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
71 | }
72 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trip_page/trip_details.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/features/activity/ui/activities_list/activities_list.dart';
2 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
3 | import 'package:amplify_trips_planner/features/trip/ui/trip_page/selected_trip_card.dart';
4 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | class TripDetails extends ConsumerWidget {
9 | const TripDetails({
10 | required this.trip,
11 | required this.tripId,
12 | super.key,
13 | });
14 |
15 | final AsyncValue trip;
16 | final String tripId;
17 |
18 | @override
19 | Widget build(BuildContext context, WidgetRef ref) {
20 | switch (trip) {
21 | case AsyncData(:final value):
22 | return Column(
23 | crossAxisAlignment: CrossAxisAlignment.stretch,
24 | children: [
25 | const SizedBox(
26 | height: 8,
27 | ),
28 | SelectedTripCard(trip: value),
29 | const SizedBox(
30 | height: 20,
31 | ),
32 | const Divider(
33 | height: 20,
34 | thickness: 5,
35 | indent: 20,
36 | endIndent: 20,
37 | ),
38 | const Text(
39 | 'Your Activities',
40 | textAlign: TextAlign.center,
41 | style: TextStyle(
42 | fontSize: 20,
43 | fontWeight: FontWeight.bold,
44 | ),
45 | ),
46 | const SizedBox(
47 | height: 8,
48 | ),
49 | Expanded(
50 | child: ActivitiesList(
51 | trip: value,
52 | ),
53 | )
54 | ],
55 | );
56 |
57 | case AsyncError():
58 | return Center(
59 | child: Column(
60 | children: [
61 | const Text('Error'),
62 | TextButton(
63 | onPressed: () async {
64 | ref.invalidate(tripControllerProvider(tripId));
65 | },
66 | child: const Text('Try again'),
67 | ),
68 | ],
69 | ),
70 | );
71 | case AsyncLoading():
72 | return const Center(
73 | child: CircularProgressIndicator(),
74 | );
75 |
76 | case _:
77 | return const Center(
78 | child: Text('Error'),
79 | );
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/amplify/backend/auth/amplifytripsplanner4600aeca/cli-inputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1",
3 | "cognitoConfig": {
4 | "identityPoolName": "amplifytripsplanner4600aeca_identitypool_4600aeca",
5 | "allowUnauthenticatedIdentities": false,
6 | "resourceNameTruncated": "amplif4600aeca",
7 | "userPoolName": "amplifytripsplanner4600aeca_userpool_4600aeca",
8 | "autoVerifiedAttributes": [
9 | "email"
10 | ],
11 | "mfaConfiguration": "OFF",
12 | "mfaTypes": [
13 | "SMS Text Message"
14 | ],
15 | "smsAuthenticationMessage": "Your authentication code is {####}",
16 | "smsVerificationMessage": "Your verification code is {####}",
17 | "emailVerificationSubject": "Your verification code",
18 | "emailVerificationMessage": "Your verification code is {####}",
19 | "defaultPasswordPolicy": false,
20 | "passwordPolicyMinLength": 8,
21 | "passwordPolicyCharacters": [],
22 | "requiredAttributes": [
23 | "email"
24 | ],
25 | "aliasAttributes": [],
26 | "userpoolClientGenerateSecret": false,
27 | "userpoolClientRefreshTokenValidity": 30,
28 | "userpoolClientWriteAttributes": [
29 | "email"
30 | ],
31 | "userpoolClientReadAttributes": [
32 | "email"
33 | ],
34 | "userpoolClientLambdaRole": "amplif4600aeca_userpoolclient_lambda_role",
35 | "userpoolClientSetAttributes": false,
36 | "sharedId": "4600aeca",
37 | "resourceName": "amplifytripsplanner4600aeca",
38 | "authSelections": "identityPoolAndUserPool",
39 | "useDefault": "manual",
40 | "usernameAttributes": [
41 | "email"
42 | ],
43 | "userPoolGroupList": [],
44 | "serviceName": "Cognito",
45 | "usernameCaseSensitive": false,
46 | "useEnabledMfas": true,
47 | "authRoleArn": {
48 | "Fn::GetAtt": [
49 | "AuthRole",
50 | "Arn"
51 | ]
52 | },
53 | "unauthRoleArn": {
54 | "Fn::GetAtt": [
55 | "UnauthRole",
56 | "Arn"
57 | ]
58 | },
59 | "breakCircularDependency": true,
60 | "dependsOn": [
61 | {
62 | "category": "function",
63 | "resourceName": "amplifytripsplanner4600aecaPostConfirmation",
64 | "triggerProvider": "Cognito",
65 | "attributes": [
66 | "Arn",
67 | "Name"
68 | ]
69 | }
70 | ],
71 | "thirdPartyAuth": false,
72 | "userPoolGroups": false,
73 | "adminQueries": false,
74 | "triggers": {
75 | "PostConfirmation": [
76 | "custom"
77 | ]
78 | },
79 | "hostedUI": false,
80 | "authProviders": [],
81 | "parentStack": {
82 | "Ref": "AWS::StackId"
83 | },
84 | "authTriggerConnections": "[\n {\n \"triggerType\": \"PostConfirmation\",\n \"lambdaFunctionName\": \"amplifytripsplanner4600aecaPostConfirmation\"\n }\n]",
85 | "permissions": []
86 | }
87 | }
--------------------------------------------------------------------------------
/lib/models/ModelProvider.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License").
5 | * You may not use this file except in compliance with the License.
6 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 |
16 | // NOTE: This file is generated and may not follow lint rules defined in your app
17 | // Generated files can be excluded from analysis in analysis_options.yaml
18 | // For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis
19 |
20 | // ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously
21 |
22 | import 'package:amplify_core/amplify_core.dart';
23 | import 'Activity.dart';
24 | import 'Profile.dart';
25 | import 'Trip.dart';
26 |
27 | export 'Activity.dart';
28 | export 'ActivityCategory.dart';
29 | export 'Profile.dart';
30 | export 'Trip.dart';
31 |
32 | class ModelProvider implements ModelProviderInterface {
33 | @override
34 | String version = "dd0adad0d3bb0b223c3b4be681aea544";
35 | @override
36 | List modelSchemas = [
37 | Activity.schema,
38 | Profile.schema,
39 | Trip.schema
40 | ];
41 | static final ModelProvider _instance = ModelProvider();
42 | @override
43 | List customTypeSchemas = [];
44 |
45 | static ModelProvider get instance => _instance;
46 |
47 | ModelType getModelTypeByModelName(String modelName) {
48 | switch (modelName) {
49 | case "Activity":
50 | return Activity.classType;
51 | case "Profile":
52 | return Profile.classType;
53 | case "Trip":
54 | return Trip.classType;
55 | default:
56 | throw Exception(
57 | "Failed to find model in model provider for model name: " +
58 | modelName);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/features/activity/service/activities_api_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:amplify_api/amplify_api.dart';
4 | import 'package:amplify_flutter/amplify_flutter.dart';
5 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | final activitiesAPIServiceProvider = Provider((ref) {
9 | final service = ActivitiesAPIService();
10 | return service;
11 | });
12 |
13 | class ActivitiesAPIService {
14 | ActivitiesAPIService();
15 |
16 | Future> getActivitiesForTrip(String tripId) async {
17 | try {
18 | final request = ModelQueries.list(
19 | Activity.classType,
20 | where: Activity.TRIP.eq(tripId),
21 | );
22 |
23 | final response = await Amplify.API.query(request: request).response;
24 |
25 | final activites = response.data?.items;
26 | if (activites == null) {
27 | safePrint('errors: ${response.errors}');
28 | return const [];
29 | }
30 | activites.sort(
31 | (a, b) => a!.activityDate
32 | .getDateTime()
33 | .compareTo(b!.activityDate.getDateTime()),
34 | );
35 | return activites.map((e) => e as Activity).toList();
36 | } on Exception catch (error) {
37 | safePrint('getActivitiesForTrip failed: $error');
38 | return const [];
39 | }
40 | }
41 |
42 | Future addActivity(Activity activity) async {
43 | try {
44 | final request = ModelMutations.create(activity);
45 | final response = await Amplify.API.mutate(request: request).response;
46 |
47 | final createdActivity = response.data;
48 | if (createdActivity == null) {
49 | safePrint('errors: ${response.errors}');
50 | return;
51 | }
52 | } on Exception catch (error) {
53 | safePrint('addActivity failed: $error');
54 | }
55 | }
56 |
57 | Future deleteActivity(Activity activity) async {
58 | try {
59 | await Amplify.API
60 | .mutate(
61 | request: ModelMutations.delete(activity),
62 | )
63 | .response;
64 | } on Exception catch (error) {
65 | safePrint('deleteActivity failed: $error');
66 | }
67 | }
68 |
69 | Future updateActivity(Activity updatedActivity) async {
70 | try {
71 | await Amplify.API
72 | .mutate(
73 | request: ModelMutations.update(updatedActivity),
74 | )
75 | .response;
76 | } on Exception catch (error) {
77 | safePrint('updateActivity failed: $error');
78 | }
79 | }
80 |
81 | Future getActivity(String activityId) async {
82 | try {
83 | final request = ModelQueries.get(
84 | Activity.classType,
85 | ActivityModelIdentifier(id: activityId),
86 | );
87 | final response = await Amplify.API.query(request: request).response;
88 |
89 | final activity = response.data!;
90 | return activity;
91 | } on Exception catch (error) {
92 | safePrint('getActivity failed: $error');
93 | rethrow;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activities_list/activities_timeline.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
3 | import 'package:amplify_trips_planner/features/activity/ui/activity_category_icon.dart';
4 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:go_router/go_router.dart';
7 | import 'package:timelines/timelines.dart';
8 |
9 | class ActivitiesTimeline extends StatelessWidget {
10 | const ActivitiesTimeline({
11 | super.key,
12 | required this.activities,
13 | });
14 |
15 | final List activities;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Column(
20 | children: [
21 | Flexible(
22 | child: Timeline.tileBuilder(
23 | builder: TimelineTileBuilder.fromStyle(
24 | oppositeContentsBuilder: (context, index) {
25 | return InkWell(
26 | onTap: () => context.goNamed(
27 | AppRoute.activity.name,
28 | pathParameters: {'id': activities[index].id},
29 | ),
30 | child: Padding(
31 | padding: const EdgeInsets.only(bottom: 15),
32 | child: ActivityCategoryIcon(
33 | activityCategory: activities[index].category,
34 | ),
35 | ),
36 | );
37 | },
38 | contentsAlign: ContentsAlign.alternating,
39 | contentsBuilder: (context, index) => InkWell(
40 | onTap: () => context.goNamed(
41 | AppRoute.activity.name,
42 | pathParameters: {'id': activities[index].id},
43 | ),
44 | child: Padding(
45 | padding: const EdgeInsets.all(24),
46 | child: Column(
47 | children: [
48 | Text(
49 | activities[index].activityName,
50 | style: Theme.of(context).textTheme.titleMedium,
51 | textAlign: TextAlign.center,
52 | ),
53 | const SizedBox(
54 | height: 5,
55 | ),
56 | Text(
57 | activities[index]
58 | .activityDate
59 | .getDateTime()
60 | .format('yyyy-MM-dd'),
61 | style: Theme.of(context).textTheme.bodySmall,
62 | ),
63 | Text(
64 | activities[index]
65 | .activityTime!
66 | .getDateTime()
67 | .format('hh:mm a'),
68 | style: Theme.of(context).textTheme.bodySmall,
69 | ),
70 | ],
71 | ),
72 | ),
73 | ),
74 | itemCount: activities.length,
75 | ),
76 | ),
77 | ),
78 | ],
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/amplify/backend/backend-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "api": {
3 | "amplifytripsplanner": {
4 | "dependsOn": [
5 | {
6 | "attributes": [
7 | "UserPoolId"
8 | ],
9 | "category": "auth",
10 | "resourceName": "amplifytripsplanner4600aeca"
11 | }
12 | ],
13 | "output": {
14 | "authConfig": {
15 | "additionalAuthenticationProviders": [
16 | {
17 | "authenticationType": "AWS_IAM"
18 | }
19 | ],
20 | "defaultAuthentication": {
21 | "authenticationType": "AMAZON_COGNITO_USER_POOLS",
22 | "userPoolConfig": {
23 | "userPoolId": "authamplifytripsplanner4600aeca"
24 | }
25 | }
26 | }
27 | },
28 | "providerPlugin": "awscloudformation",
29 | "service": "AppSync"
30 | }
31 | },
32 | "auth": {
33 | "amplifytripsplanner4600aeca": {
34 | "customAuth": false,
35 | "dependsOn": [
36 | {
37 | "attributes": [
38 | "Arn",
39 | "Name"
40 | ],
41 | "category": "function",
42 | "resourceName": "amplifytripsplanner4600aecaPostConfirmation",
43 | "triggerProvider": "Cognito"
44 | }
45 | ],
46 | "frontendAuthConfig": {
47 | "mfaConfiguration": "OFF",
48 | "mfaTypes": [
49 | "SMS"
50 | ],
51 | "passwordProtectionSettings": {
52 | "passwordPolicyCharacters": [],
53 | "passwordPolicyMinLength": 8
54 | },
55 | "signupAttributes": [
56 | "EMAIL"
57 | ],
58 | "socialProviders": [],
59 | "usernameAttributes": [
60 | "EMAIL"
61 | ],
62 | "verificationMechanisms": [
63 | "EMAIL"
64 | ]
65 | },
66 | "providerPlugin": "awscloudformation",
67 | "service": "Cognito"
68 | }
69 | },
70 | "function": {
71 | "amplifytripsplanner4600aecaPostConfirmation": {
72 | "build": true,
73 | "dependsOn": [
74 | {
75 | "attributes": [
76 | "GraphQLAPIIdOutput",
77 | "GraphQLAPIEndpointOutput"
78 | ],
79 | "category": "api",
80 | "resourceName": "amplifytripsplanner"
81 | }
82 | ],
83 | "providerPlugin": "awscloudformation",
84 | "service": "Lambda"
85 | }
86 | },
87 | "parameters": {
88 | "AMPLIFY_function_amplifytripsplanner4600aecaPostConfirmation_deploymentBucketName": {
89 | "usedBy": [
90 | {
91 | "category": "function",
92 | "resourceName": "amplifytripsplanner4600aecaPostConfirmation"
93 | }
94 | ]
95 | },
96 | "AMPLIFY_function_amplifytripsplanner4600aecaPostConfirmation_s3Key": {
97 | "usedBy": [
98 | {
99 | "category": "function",
100 | "resourceName": "amplifytripsplanner4600aecaPostConfirmation"
101 | }
102 | ]
103 | }
104 | },
105 | "storage": {
106 | "s37d9e620f": {
107 | "dependsOn": [],
108 | "providerPlugin": "awscloudformation",
109 | "service": "S3"
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/lib/common/navigation/router/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/features/activity/ui/activity_page/activity_page.dart';
3 | import 'package:amplify_trips_planner/features/activity/ui/add_activity/add_activity_page.dart';
4 | import 'package:amplify_trips_planner/features/activity/ui/edit_activity/edit_activity_page.dart';
5 | import 'package:amplify_trips_planner/features/profile/ui/profile_page/profile_page.dart';
6 | import 'package:amplify_trips_planner/features/trip/ui/edit_trip_page/edit_trip_page.dart';
7 | import 'package:amplify_trips_planner/features/trip/ui/past_trip_page/past_trip_page.dart';
8 | import 'package:amplify_trips_planner/features/trip/ui/past_trips/past_trips_list.dart';
9 | import 'package:amplify_trips_planner/features/trip/ui/trip_page/trip_page.dart';
10 | import 'package:amplify_trips_planner/features/trip/ui/trips_list/trips_list_page.dart';
11 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
12 | import 'package:flutter/material.dart';
13 | import 'package:go_router/go_router.dart';
14 |
15 | final router = GoRouter(
16 | routes: [
17 | GoRoute(
18 | path: '/',
19 | name: AppRoute.home.name,
20 | builder: (context, state) => const TripsListPage(),
21 | ),
22 | GoRoute(
23 | path: '/trip/:id',
24 | name: AppRoute.trip.name,
25 | builder: (context, state) {
26 | final tripId = state.pathParameters['id']!;
27 | return TripPage(tripId: tripId);
28 | },
29 | ),
30 | GoRoute(
31 | path: '/edittrip/:id',
32 | name: AppRoute.editTrip.name,
33 | builder: (context, state) {
34 | return EditTripPage(
35 | trip: state.extra! as Trip,
36 | );
37 | },
38 | ),
39 | GoRoute(
40 | path: '/pasttrips',
41 | name: AppRoute.pastTrips.name,
42 | builder: (context, state) => const PastTripsList(),
43 | ),
44 | GoRoute(
45 | path: '/pasttrip/:id',
46 | name: AppRoute.pastTrip.name,
47 | builder: (context, state) {
48 | final tripId = state.pathParameters['id']!;
49 | return PastTripPage(tripId: tripId);
50 | },
51 | ),
52 | GoRoute(
53 | path: '/addActivity/:id',
54 | name: AppRoute.addActivity.name,
55 | builder: (context, state) {
56 | final tripId = state.pathParameters['id']!;
57 | return AddActivityPage(tripId: tripId);
58 | },
59 | ),
60 | GoRoute(
61 | path: '/activity/:id',
62 | name: AppRoute.activity.name,
63 | builder: (context, state) {
64 | final activityId = state.pathParameters['id']!;
65 | return ActivityPage(activityId: activityId);
66 | },
67 | ),
68 | GoRoute(
69 | path: '/editactivity/:id',
70 | name: AppRoute.editActivity.name,
71 | builder: (context, state) {
72 | return EditActivityPage(
73 | activity: state.extra! as Activity,
74 | );
75 | },
76 | ),
77 | GoRoute(
78 | path: '/profile',
79 | name: AppRoute.profile.name,
80 | builder: (context, state) => const ProfilePage(),
81 | ),
82 | ],
83 | errorBuilder: (context, state) => Scaffold(
84 | body: Center(
85 | child: Text(state.error.toString()),
86 | ),
87 | ),
88 | );
89 |
--------------------------------------------------------------------------------
/lib/features/profile/ui/profile_page/edit_profile_bottomsheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/ui/bottomsheet_text_form_field.dart';
2 | import 'package:amplify_trips_planner/features/profile/controller/profile_controller.dart';
3 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | class EditProfileBottomSheet extends ConsumerWidget {
9 | EditProfileBottomSheet({
10 | required this.profile,
11 | super.key,
12 | });
13 |
14 | final Profile profile;
15 |
16 | final formGlobalKey = GlobalKey();
17 |
18 | @override
19 | Widget build(BuildContext context, WidgetRef ref) {
20 | final firstNameController = TextEditingController(
21 | text: profile.firstName != null ? profile.firstName! : '',
22 | );
23 | final lastNameController = TextEditingController(
24 | text: profile.lastName != null ? profile.lastName! : '',
25 | );
26 | final homeCityController = TextEditingController(
27 | text: profile.homeCity != null ? profile.homeCity! : '',
28 | );
29 |
30 | return Form(
31 | key: formGlobalKey,
32 | child: Container(
33 | padding: EdgeInsets.only(
34 | top: 15,
35 | left: 15,
36 | right: 15,
37 | bottom: MediaQuery.of(context).viewInsets.bottom + 15,
38 | ),
39 | width: double.infinity,
40 | child: Column(
41 | mainAxisSize: MainAxisSize.min,
42 | crossAxisAlignment: CrossAxisAlignment.start,
43 | children: [
44 | BottomSheetTextFormField(
45 | labelText: 'First Name',
46 | controller: firstNameController,
47 | keyboardType: TextInputType.name,
48 | ),
49 | const SizedBox(
50 | height: 20,
51 | ),
52 | BottomSheetTextFormField(
53 | labelText: 'Last Name',
54 | controller: lastNameController,
55 | keyboardType: TextInputType.name,
56 | ),
57 | const SizedBox(
58 | height: 20,
59 | ),
60 | BottomSheetTextFormField(
61 | labelText: 'Home City',
62 | controller: homeCityController,
63 | keyboardType: TextInputType.name,
64 | ),
65 | const SizedBox(
66 | height: 20,
67 | ),
68 | TextButton(
69 | child: const Text('OK'),
70 | onPressed: () async {
71 | final currentState = formGlobalKey.currentState;
72 | if (currentState == null) {
73 | return;
74 | }
75 | if (currentState.validate()) {
76 | final updatedProfile = profile.copyWith(
77 | firstName: firstNameController.text,
78 | lastName: lastNameController.text,
79 | homeCity: homeCityController.text,
80 | );
81 | await ref
82 | .watch(profileControllerProvider.notifier)
83 | .updateProfile(updatedProfile);
84 |
85 | if (context.mounted) {
86 | context.pop();
87 | }
88 | }
89 | }, //,
90 | ),
91 | ],
92 | ),
93 | ),
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/lib/features/trip/service/trips_api_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:amplify_api/amplify_api.dart';
4 | import 'package:amplify_flutter/amplify_flutter.dart';
5 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | final tripsAPIServiceProvider = Provider((ref) {
9 | final service = TripsAPIService();
10 | return service;
11 | });
12 |
13 | class TripsAPIService {
14 | TripsAPIService();
15 |
16 | Future> getTrips() async {
17 | try {
18 | final request = ModelQueries.list(Trip.classType);
19 | final response = await Amplify.API.query(request: request).response;
20 |
21 | final trips = response.data?.items;
22 | if (trips == null) {
23 | safePrint('getTrips errors: ${response.errors}');
24 | return const [];
25 | }
26 | trips.sort(
27 | (a, b) =>
28 | a!.startDate.getDateTime().compareTo(b!.startDate.getDateTime()),
29 | );
30 | return trips
31 | .map((e) => e as Trip)
32 | .where(
33 | (element) => element.endDate.getDateTime().isAfter(DateTime.now()),
34 | )
35 | .toList();
36 | } on Exception catch (error) {
37 | safePrint('getTrips failed: $error');
38 |
39 | return const [];
40 | }
41 | }
42 |
43 | Future> getPastTrips() async {
44 | try {
45 | final request = ModelQueries.list(Trip.classType);
46 | final response = await Amplify.API.query(request: request).response;
47 |
48 | final trips = response.data?.items;
49 | if (trips == null) {
50 | safePrint('getPastTrips errors: ${response.errors}');
51 | return const [];
52 | }
53 | trips.sort(
54 | (a, b) =>
55 | a!.startDate.getDateTime().compareTo(b!.startDate.getDateTime()),
56 | );
57 | return trips
58 | .map((e) => e as Trip)
59 | .where(
60 | (element) => element.endDate.getDateTime().isBefore(DateTime.now()),
61 | )
62 | .toList();
63 | } on Exception catch (error) {
64 | safePrint('getPastTrips failed: $error');
65 |
66 | return const [];
67 | }
68 | }
69 |
70 | Future addTrip(Trip trip) async {
71 | try {
72 | final request = ModelMutations.create(trip);
73 | final response = await Amplify.API.mutate(request: request).response;
74 |
75 | final createdTrip = response.data;
76 | if (createdTrip == null) {
77 | safePrint('addTrip errors: ${response.errors}');
78 | return;
79 | }
80 | } on Exception catch (error) {
81 | safePrint('addTrip failed: $error');
82 | }
83 | }
84 |
85 | Future deleteTrip(Trip trip) async {
86 | try {
87 | await Amplify.API
88 | .mutate(
89 | request: ModelMutations.delete(trip),
90 | )
91 | .response;
92 | } on Exception catch (error) {
93 | safePrint('deleteTrip failed: $error');
94 | }
95 | }
96 |
97 | Future updateTrip(Trip updatedTrip) async {
98 | try {
99 | await Amplify.API
100 | .mutate(
101 | request: ModelMutations.update(updatedTrip),
102 | )
103 | .response;
104 | } on Exception catch (error) {
105 | safePrint('updateTrip failed: $error');
106 | }
107 | }
108 |
109 | Future getTrip(String tripId) async {
110 | try {
111 | final request = ModelQueries.get(
112 | Trip.classType,
113 | TripModelIdentifier(id: tripId),
114 | );
115 | final response = await Amplify.API.query(request: request).response;
116 |
117 | final trip = response.data!;
118 | return trip;
119 | } on Exception catch (error) {
120 | safePrint('getTrip failed: $error');
121 | rethrow;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/features/trip/controller/trip_controller.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'trip_controller.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$tripControllerHash() => r'324b8d6d419ebd4dcd2b98c95d80563efee596b2';
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 | abstract class _$TripController
33 | extends BuildlessAutoDisposeAsyncNotifier {
34 | late final String tripId;
35 |
36 | FutureOr build(
37 | String tripId,
38 | );
39 | }
40 |
41 | /// See also [TripController].
42 | @ProviderFor(TripController)
43 | const tripControllerProvider = TripControllerFamily();
44 |
45 | /// See also [TripController].
46 | class TripControllerFamily extends Family> {
47 | /// See also [TripController].
48 | const TripControllerFamily();
49 |
50 | /// See also [TripController].
51 | TripControllerProvider call(
52 | String tripId,
53 | ) {
54 | return TripControllerProvider(
55 | tripId,
56 | );
57 | }
58 |
59 | @override
60 | TripControllerProvider getProviderOverride(
61 | covariant TripControllerProvider provider,
62 | ) {
63 | return call(
64 | provider.tripId,
65 | );
66 | }
67 |
68 | static const Iterable? _dependencies = null;
69 |
70 | @override
71 | Iterable? get dependencies => _dependencies;
72 |
73 | static const Iterable? _allTransitiveDependencies = null;
74 |
75 | @override
76 | Iterable? get allTransitiveDependencies =>
77 | _allTransitiveDependencies;
78 |
79 | @override
80 | String? get name => r'tripControllerProvider';
81 | }
82 |
83 | /// See also [TripController].
84 | class TripControllerProvider
85 | extends AutoDisposeAsyncNotifierProviderImpl {
86 | /// See also [TripController].
87 | TripControllerProvider(
88 | this.tripId,
89 | ) : super.internal(
90 | () => TripController()..tripId = tripId,
91 | from: tripControllerProvider,
92 | name: r'tripControllerProvider',
93 | debugGetCreateSourceHash:
94 | const bool.fromEnvironment('dart.vm.product')
95 | ? null
96 | : _$tripControllerHash,
97 | dependencies: TripControllerFamily._dependencies,
98 | allTransitiveDependencies:
99 | TripControllerFamily._allTransitiveDependencies,
100 | );
101 |
102 | final String tripId;
103 |
104 | @override
105 | bool operator ==(Object other) {
106 | return other is TripControllerProvider && other.tripId == tripId;
107 | }
108 |
109 | @override
110 | int get hashCode {
111 | var hash = _SystemHash.combine(0, runtimeType.hashCode);
112 | hash = _SystemHash.combine(hash, tripId.hashCode);
113 |
114 | return _SystemHash.finish(hash);
115 | }
116 |
117 | @override
118 | FutureOr runNotifierBuild(
119 | covariant TripController notifier,
120 | ) {
121 | return notifier.build(
122 | tripId,
123 | );
124 | }
125 | }
126 | // 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
127 |
--------------------------------------------------------------------------------
/lib/features/activity/controller/activities_list.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'activities_list.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$activitiesListHash() => r'fd5b3964748da9d27e248112fab4ecf2dda9adeb';
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 | abstract class _$ActivitiesList
33 | extends BuildlessAutoDisposeAsyncNotifier> {
34 | late final String tripId;
35 |
36 | FutureOr> build(
37 | String tripId,
38 | );
39 | }
40 |
41 | /// See also [ActivitiesList].
42 | @ProviderFor(ActivitiesList)
43 | const activitiesListProvider = ActivitiesListFamily();
44 |
45 | /// See also [ActivitiesList].
46 | class ActivitiesListFamily extends Family>> {
47 | /// See also [ActivitiesList].
48 | const ActivitiesListFamily();
49 |
50 | /// See also [ActivitiesList].
51 | ActivitiesListProvider call(
52 | String tripId,
53 | ) {
54 | return ActivitiesListProvider(
55 | tripId,
56 | );
57 | }
58 |
59 | @override
60 | ActivitiesListProvider getProviderOverride(
61 | covariant ActivitiesListProvider provider,
62 | ) {
63 | return call(
64 | provider.tripId,
65 | );
66 | }
67 |
68 | static const Iterable? _dependencies = null;
69 |
70 | @override
71 | Iterable? get dependencies => _dependencies;
72 |
73 | static const Iterable? _allTransitiveDependencies = null;
74 |
75 | @override
76 | Iterable? get allTransitiveDependencies =>
77 | _allTransitiveDependencies;
78 |
79 | @override
80 | String? get name => r'activitiesListProvider';
81 | }
82 |
83 | /// See also [ActivitiesList].
84 | class ActivitiesListProvider extends AutoDisposeAsyncNotifierProviderImpl<
85 | ActivitiesList, List> {
86 | /// See also [ActivitiesList].
87 | ActivitiesListProvider(
88 | this.tripId,
89 | ) : super.internal(
90 | () => ActivitiesList()..tripId = tripId,
91 | from: activitiesListProvider,
92 | name: r'activitiesListProvider',
93 | debugGetCreateSourceHash:
94 | const bool.fromEnvironment('dart.vm.product')
95 | ? null
96 | : _$activitiesListHash,
97 | dependencies: ActivitiesListFamily._dependencies,
98 | allTransitiveDependencies:
99 | ActivitiesListFamily._allTransitiveDependencies,
100 | );
101 |
102 | final String tripId;
103 |
104 | @override
105 | bool operator ==(Object other) {
106 | return other is ActivitiesListProvider && other.tripId == tripId;
107 | }
108 |
109 | @override
110 | int get hashCode {
111 | var hash = _SystemHash.combine(0, runtimeType.hashCode);
112 | hash = _SystemHash.combine(hash, tripId.hashCode);
113 |
114 | return _SystemHash.finish(hash);
115 | }
116 |
117 | @override
118 | FutureOr> runNotifierBuild(
119 | covariant ActivitiesList notifier,
120 | ) {
121 | return notifier.build(
122 | tripId,
123 | );
124 | }
125 | }
126 | // 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
127 |
--------------------------------------------------------------------------------
/lib/features/activity/controller/activity_controller.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'activity_controller.dart';
4 |
5 | // **************************************************************************
6 | // RiverpodGenerator
7 | // **************************************************************************
8 |
9 | String _$activityControllerHash() =>
10 | r'0c2a1e12050057b93ca2df2ca0d8530d9ee6956f';
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 | abstract class _$ActivityController
34 | extends BuildlessAutoDisposeAsyncNotifier {
35 | late final String activityId;
36 |
37 | FutureOr build(
38 | String activityId,
39 | );
40 | }
41 |
42 | /// See also [ActivityController].
43 | @ProviderFor(ActivityController)
44 | const activityControllerProvider = ActivityControllerFamily();
45 |
46 | /// See also [ActivityController].
47 | class ActivityControllerFamily extends Family> {
48 | /// See also [ActivityController].
49 | const ActivityControllerFamily();
50 |
51 | /// See also [ActivityController].
52 | ActivityControllerProvider call(
53 | String activityId,
54 | ) {
55 | return ActivityControllerProvider(
56 | activityId,
57 | );
58 | }
59 |
60 | @override
61 | ActivityControllerProvider getProviderOverride(
62 | covariant ActivityControllerProvider provider,
63 | ) {
64 | return call(
65 | provider.activityId,
66 | );
67 | }
68 |
69 | static const Iterable? _dependencies = null;
70 |
71 | @override
72 | Iterable? get dependencies => _dependencies;
73 |
74 | static const Iterable? _allTransitiveDependencies = null;
75 |
76 | @override
77 | Iterable? get allTransitiveDependencies =>
78 | _allTransitiveDependencies;
79 |
80 | @override
81 | String? get name => r'activityControllerProvider';
82 | }
83 |
84 | /// See also [ActivityController].
85 | class ActivityControllerProvider
86 | extends AutoDisposeAsyncNotifierProviderImpl {
87 | /// See also [ActivityController].
88 | ActivityControllerProvider(
89 | this.activityId,
90 | ) : super.internal(
91 | () => ActivityController()..activityId = activityId,
92 | from: activityControllerProvider,
93 | name: r'activityControllerProvider',
94 | debugGetCreateSourceHash:
95 | const bool.fromEnvironment('dart.vm.product')
96 | ? null
97 | : _$activityControllerHash,
98 | dependencies: ActivityControllerFamily._dependencies,
99 | allTransitiveDependencies:
100 | ActivityControllerFamily._allTransitiveDependencies,
101 | );
102 |
103 | final String activityId;
104 |
105 | @override
106 | bool operator ==(Object other) {
107 | return other is ActivityControllerProvider &&
108 | other.activityId == activityId;
109 | }
110 |
111 | @override
112 | int get hashCode {
113 | var hash = _SystemHash.combine(0, runtimeType.hashCode);
114 | hash = _SystemHash.combine(hash, activityId.hashCode);
115 |
116 | return _SystemHash.finish(hash);
117 | }
118 |
119 | @override
120 | FutureOr runNotifierBuild(
121 | covariant ActivityController notifier,
122 | ) {
123 | return notifier.build(
124 | activityId,
125 | );
126 | }
127 | }
128 | // 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
129 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trips_gridview/trip_gridview_item_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
2 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
3 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
4 | import 'package:cached_network_image/cached_network_image.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class TripGridViewItemCard extends StatelessWidget {
8 | const TripGridViewItemCard({
9 | required this.trip,
10 | super.key,
11 | });
12 |
13 | final Trip trip;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Card(
18 | clipBehavior: Clip.antiAlias,
19 | shape: RoundedRectangleBorder(
20 | borderRadius: BorderRadius.circular(15),
21 | ),
22 | elevation: 5,
23 | child: Column(
24 | children: [
25 | Expanded(
26 | child: Container(
27 | height: 500,
28 | alignment: Alignment.center,
29 | color: const Color(constants.primaryColorDark),
30 | child: Stack(
31 | children: [
32 | Positioned.fill(
33 | child: trip.tripImageUrl != null
34 | ? Stack(
35 | children: [
36 | const Center(child: CircularProgressIndicator()),
37 | CachedNetworkImage(
38 | errorWidget: (context, url, dynamic error) =>
39 | const Icon(Icons.error_outline_outlined),
40 | imageUrl: trip.tripImageUrl!,
41 | cacheKey: trip.tripImageKey,
42 | width: double.maxFinite,
43 | height: 500,
44 | alignment: Alignment.topCenter,
45 | fit: BoxFit.fill,
46 | ),
47 | ],
48 | )
49 | : Image.asset(
50 | 'images/amplify.png',
51 | fit: BoxFit.contain,
52 | ),
53 | ),
54 | Positioned(
55 | bottom: 16,
56 | left: 16,
57 | right: 16,
58 | child: FittedBox(
59 | fit: BoxFit.scaleDown,
60 | alignment: Alignment.centerLeft,
61 | child: Text(
62 | trip.destination,
63 | style: Theme.of(context)
64 | .textTheme
65 | .headlineSmall!
66 | .copyWith(color: Colors.white),
67 | ),
68 | ),
69 | ),
70 | ],
71 | ),
72 | ),
73 | ),
74 | Padding(
75 | padding: const EdgeInsets.fromLTRB(2, 8, 8, 4),
76 | child: DefaultTextStyle(
77 | softWrap: false,
78 | overflow: TextOverflow.ellipsis,
79 | style: Theme.of(context).textTheme.titleMedium!,
80 | child: Column(
81 | crossAxisAlignment: CrossAxisAlignment.start,
82 | children: [
83 | Padding(
84 | padding: const EdgeInsets.only(bottom: 8),
85 | child: Text(
86 | trip.tripName,
87 | style: Theme.of(context)
88 | .textTheme
89 | .titleMedium!
90 | .copyWith(color: Colors.black54),
91 | ),
92 | ),
93 | Text(
94 | trip.startDate.getDateTime().format('MMMM dd, yyyy'),
95 | style: const TextStyle(fontSize: 12),
96 | ),
97 | Text(
98 | trip.endDate.getDateTime().format('MMMM dd, yyyy'),
99 | style: const TextStyle(fontSize: 12),
100 | ),
101 | ],
102 | ),
103 | ),
104 | ),
105 | ],
106 | ),
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/features/profile/ui/profile_page/profile_listview.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
2 | import 'package:amplify_trips_planner/features/profile/controller/profile_controller.dart';
3 | import 'package:amplify_trips_planner/features/profile/ui/profile_page/edit_profile_bottomsheet.dart';
4 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | class ProfileListView extends ConsumerWidget {
9 | const ProfileListView({
10 | required this.profile,
11 | super.key,
12 | });
13 |
14 | final AsyncValue profile;
15 |
16 | void editProfile(BuildContext context, Profile profile) async {
17 | await showModalBottomSheet(
18 | isScrollControlled: true,
19 | elevation: 5,
20 | context: context,
21 | builder: (BuildContext context) {
22 | return EditProfileBottomSheet(
23 | profile: profile,
24 | );
25 | },
26 | );
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context, WidgetRef ref) {
31 | switch (profile) {
32 | case AsyncData(:final value):
33 | return ListView(
34 | children: [
35 | Card(
36 | child: ListTile(
37 | leading: const Icon(
38 | Icons.verified_user,
39 | size: 50,
40 | color: Color(constants.primaryColorDark),
41 | ),
42 | title: Text(
43 | value.firstName != null ? value.firstName! : 'Add your name',
44 | style: Theme.of(context).textTheme.titleLarge,
45 | ),
46 | subtitle: Text(value.email),
47 | ),
48 | ),
49 | ListTile(
50 | dense: true,
51 | title: Text(
52 | 'Home',
53 | style: Theme.of(context)
54 | .textTheme
55 | .titleSmall!
56 | .copyWith(color: Colors.white),
57 | ),
58 | tileColor: Colors.grey,
59 | ),
60 | Card(
61 | child: ListTile(
62 | title: Text(
63 | value.firstName != null ? value.homeCity! : 'Add your city',
64 | style: Theme.of(context).textTheme.titleLarge,
65 | ),
66 | ),
67 | ),
68 | const ListTile(
69 | dense: true,
70 | tileColor: Colors.grey,
71 | visualDensity: VisualDensity(vertical: -4),
72 | ),
73 | Card(
74 | child: Row(
75 | mainAxisAlignment: MainAxisAlignment.spaceAround,
76 | children: [
77 | TextButton(
78 | style: TextButton.styleFrom(
79 | textStyle: const TextStyle(fontSize: 20),
80 | ),
81 | onPressed: () {
82 | editProfile(context, value);
83 | },
84 | child: const Text('Edit'),
85 | ),
86 | ],
87 | ),
88 | )
89 | ],
90 | );
91 |
92 | case AsyncError():
93 | return Column(
94 | mainAxisSize: MainAxisSize.min,
95 | crossAxisAlignment: CrossAxisAlignment.center,
96 | children: [
97 | Padding(
98 | padding: const EdgeInsets.all(8),
99 | child: Text(
100 | 'Error',
101 | style: Theme.of(context).textTheme.titleMedium,
102 | textAlign: TextAlign.center,
103 | ),
104 | ),
105 | TextButton(
106 | style: TextButton.styleFrom(
107 | textStyle: const TextStyle(fontSize: 20),
108 | ),
109 | onPressed: () async {
110 | ref.invalidate(profileControllerProvider);
111 | },
112 | child: const Text('Try again'),
113 | ),
114 | ],
115 | );
116 | case AsyncLoading():
117 | return const Center(
118 | child: CircularProgressIndicator(),
119 | );
120 |
121 | case _:
122 | return const Center(
123 | child: Text('Error'),
124 | );
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trips_list/add_trip_bottomsheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/ui/bottomsheet_text_form_field.dart';
2 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
3 | import 'package:amplify_trips_planner/features/trip/controller/trips_list.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_riverpod/flutter_riverpod.dart';
6 | import 'package:go_router/go_router.dart';
7 |
8 | class AddTripBottomSheet extends ConsumerWidget {
9 | AddTripBottomSheet({
10 | super.key,
11 | });
12 |
13 | final formGlobalKey = GlobalKey();
14 |
15 | @override
16 | Widget build(BuildContext context, WidgetRef ref) {
17 | final tripNameController = TextEditingController();
18 | final destinationController = TextEditingController();
19 | final startDateController = TextEditingController();
20 | final endDateController = TextEditingController();
21 |
22 | return Form(
23 | key: formGlobalKey,
24 | child: Container(
25 | padding: EdgeInsets.only(
26 | top: 15,
27 | left: 15,
28 | right: 15,
29 | bottom: MediaQuery.of(context).viewInsets.bottom + 15,
30 | ),
31 | width: double.infinity,
32 | child: Column(
33 | mainAxisSize: MainAxisSize.min,
34 | crossAxisAlignment: CrossAxisAlignment.start,
35 | children: [
36 | BottomSheetTextFormField(
37 | labelText: 'Trip Name',
38 | controller: tripNameController,
39 | keyboardType: TextInputType.name,
40 | ),
41 | const SizedBox(
42 | height: 20,
43 | ),
44 | BottomSheetTextFormField(
45 | labelText: 'Trip Destination',
46 | controller: destinationController,
47 | keyboardType: TextInputType.name,
48 | ),
49 | const SizedBox(
50 | height: 20,
51 | ),
52 | BottomSheetTextFormField(
53 | labelText: 'Start Date',
54 | controller: startDateController,
55 | keyboardType: TextInputType.datetime,
56 | onTap: () async {
57 | final pickedDate = await showDatePicker(
58 | context: context,
59 | initialDate: DateTime.now(),
60 | firstDate: DateTime(2000),
61 | lastDate: DateTime(2101),
62 | );
63 |
64 | if (pickedDate != null) {
65 | startDateController.text = pickedDate.format('yyyy-MM-dd');
66 | }
67 | },
68 | ),
69 | const SizedBox(
70 | height: 20,
71 | ),
72 | BottomSheetTextFormField(
73 | labelText: 'End Date',
74 | controller: endDateController,
75 | keyboardType: TextInputType.datetime,
76 | onTap: () async {
77 | if (startDateController.text.isNotEmpty) {
78 | final pickedDate = await showDatePicker(
79 | context: context,
80 | initialDate: DateTime.parse(startDateController.text),
81 | firstDate: DateTime.parse(startDateController.text),
82 | lastDate: DateTime(2101),
83 | );
84 |
85 | if (pickedDate != null) {
86 | endDateController.text = pickedDate.format('yyyy-MM-dd');
87 | }
88 | }
89 | },
90 | ),
91 | const SizedBox(
92 | height: 20,
93 | ),
94 | TextButton(
95 | child: const Text('OK'),
96 | onPressed: () async {
97 | final currentState = formGlobalKey.currentState;
98 | if (currentState == null) {
99 | return;
100 | }
101 | if (currentState.validate()) {
102 | await ref.watch(tripsListProvider.notifier).addTrip(
103 | name: tripNameController.text,
104 | destination: destinationController.text,
105 | startDate: startDateController.text,
106 | endDate: endDateController.text,
107 | );
108 |
109 | if (context.mounted) {
110 | context.pop();
111 | }
112 | }
113 | }, //,
114 | ),
115 | ],
116 | ),
117 | ),
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - amplify_auth_cognito_ios (0.0.1):
3 | - Flutter
4 | - amplify_secure_storage (0.0.1):
5 | - Flutter
6 | - connectivity_plus (0.0.1):
7 | - Flutter
8 | - ReachabilitySwift
9 | - device_info_plus (0.0.1):
10 | - Flutter
11 | - DKImagePickerController/Core (4.3.4):
12 | - DKImagePickerController/ImageDataManager
13 | - DKImagePickerController/Resource
14 | - DKImagePickerController/ImageDataManager (4.3.4)
15 | - DKImagePickerController/PhotoGallery (4.3.4):
16 | - DKImagePickerController/Core
17 | - DKPhotoGallery
18 | - DKImagePickerController/Resource (4.3.4)
19 | - DKPhotoGallery (0.0.17):
20 | - DKPhotoGallery/Core (= 0.0.17)
21 | - DKPhotoGallery/Model (= 0.0.17)
22 | - DKPhotoGallery/Preview (= 0.0.17)
23 | - DKPhotoGallery/Resource (= 0.0.17)
24 | - SDWebImage
25 | - SwiftyGif
26 | - DKPhotoGallery/Core (0.0.17):
27 | - DKPhotoGallery/Model
28 | - DKPhotoGallery/Preview
29 | - SDWebImage
30 | - SwiftyGif
31 | - DKPhotoGallery/Model (0.0.17):
32 | - SDWebImage
33 | - SwiftyGif
34 | - DKPhotoGallery/Preview (0.0.17):
35 | - DKPhotoGallery/Model
36 | - DKPhotoGallery/Resource
37 | - SDWebImage
38 | - SwiftyGif
39 | - DKPhotoGallery/Resource (0.0.17):
40 | - SDWebImage
41 | - SwiftyGif
42 | - file_picker (0.0.1):
43 | - DKImagePickerController/PhotoGallery
44 | - Flutter
45 | - Flutter (1.0.0)
46 | - FMDB (2.7.5):
47 | - FMDB/standard (= 2.7.5)
48 | - FMDB/standard (2.7.5)
49 | - image_picker_ios (0.0.1):
50 | - Flutter
51 | - package_info_plus (0.4.5):
52 | - Flutter
53 | - path_provider_foundation (0.0.1):
54 | - Flutter
55 | - FlutterMacOS
56 | - ReachabilitySwift (5.0.0)
57 | - SDWebImage (5.15.5):
58 | - SDWebImage/Core (= 5.15.5)
59 | - SDWebImage/Core (5.15.5)
60 | - sqflite (0.0.2):
61 | - Flutter
62 | - FMDB (>= 2.7.5)
63 | - SwiftyGif (5.4.4)
64 | - url_launcher_ios (0.0.1):
65 | - Flutter
66 |
67 | DEPENDENCIES:
68 | - amplify_auth_cognito_ios (from `.symlinks/plugins/amplify_auth_cognito_ios/ios`)
69 | - amplify_secure_storage (from `.symlinks/plugins/amplify_secure_storage/ios`)
70 | - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
71 | - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
72 | - file_picker (from `.symlinks/plugins/file_picker/ios`)
73 | - Flutter (from `Flutter`)
74 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
75 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
76 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
77 | - sqflite (from `.symlinks/plugins/sqflite/ios`)
78 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
79 |
80 | SPEC REPOS:
81 | trunk:
82 | - DKImagePickerController
83 | - DKPhotoGallery
84 | - FMDB
85 | - ReachabilitySwift
86 | - SDWebImage
87 | - SwiftyGif
88 |
89 | EXTERNAL SOURCES:
90 | amplify_auth_cognito_ios:
91 | :path: ".symlinks/plugins/amplify_auth_cognito_ios/ios"
92 | amplify_secure_storage:
93 | :path: ".symlinks/plugins/amplify_secure_storage/ios"
94 | connectivity_plus:
95 | :path: ".symlinks/plugins/connectivity_plus/ios"
96 | device_info_plus:
97 | :path: ".symlinks/plugins/device_info_plus/ios"
98 | file_picker:
99 | :path: ".symlinks/plugins/file_picker/ios"
100 | Flutter:
101 | :path: Flutter
102 | image_picker_ios:
103 | :path: ".symlinks/plugins/image_picker_ios/ios"
104 | package_info_plus:
105 | :path: ".symlinks/plugins/package_info_plus/ios"
106 | path_provider_foundation:
107 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
108 | sqflite:
109 | :path: ".symlinks/plugins/sqflite/ios"
110 | url_launcher_ios:
111 | :path: ".symlinks/plugins/url_launcher_ios/ios"
112 |
113 | SPEC CHECKSUMS:
114 | amplify_auth_cognito_ios: f8b7c4d978565313b60596d7dc94fe029b4f7704
115 | amplify_secure_storage: 501cbdac671a050090d48dfcc002d5637d3f4a55
116 | connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
117 | device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
118 | DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
119 | DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
120 | file_picker: ce3938a0df3cc1ef404671531facef740d03f920
121 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
122 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
123 | image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
124 | package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
125 | path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
126 | ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
127 | SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
128 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
129 | SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
130 | url_launcher_ios: fb12c43172927bb5cf75aeebd073f883801f1993
131 |
132 | PODFILE CHECKSUM: a843a48598f7673f7f2af9f25c841666a29f3416
133 |
134 | COCOAPODS: 1.11.3
135 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/trip_page/selected_trip_card.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
4 | import 'package:amplify_trips_planner/common/ui/upload_progress_dialog.dart';
5 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
6 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
7 | import 'package:amplify_trips_planner/features/trip/controller/trips_list.dart';
8 | import 'package:amplify_trips_planner/features/trip/ui/trip_page/delete_trip_dialog.dart';
9 | import 'package:amplify_trips_planner/models/Trip.dart';
10 | import 'package:cached_network_image/cached_network_image.dart';
11 | import 'package:flutter/material.dart';
12 | import 'package:flutter_riverpod/flutter_riverpod.dart';
13 | import 'package:go_router/go_router.dart';
14 | import 'package:image_picker/image_picker.dart';
15 |
16 | class SelectedTripCard extends ConsumerWidget {
17 | const SelectedTripCard({
18 | required this.trip,
19 | super.key,
20 | });
21 |
22 | final Trip trip;
23 |
24 | Future uploadImage({
25 | required BuildContext context,
26 | required WidgetRef ref,
27 | required Trip trip,
28 | }) async {
29 | final picker = ImagePicker();
30 | final pickedFile = await picker.pickImage(source: ImageSource.gallery);
31 | if (pickedFile == null) {
32 | return false;
33 | }
34 | final file = File(pickedFile.path);
35 | if (context.mounted) {
36 | await showDialog(
37 | context: context,
38 | barrierDismissible: false,
39 | builder: (BuildContext context) {
40 | return const UploadProgressDialog();
41 | },
42 | );
43 |
44 | await ref
45 | .watch(tripControllerProvider(trip.id).notifier)
46 | .uploadFile(file, trip);
47 | }
48 |
49 | return true;
50 | }
51 |
52 | Future deleteTrip(
53 | BuildContext context,
54 | WidgetRef ref,
55 | Trip trip,
56 | ) async {
57 | var value = await showDialog(
58 | context: context,
59 | builder: (BuildContext context) {
60 | return const DeleteTripDialog();
61 | },
62 | );
63 | value ??= false;
64 |
65 | if (value) {
66 | await ref.watch(tripsListProvider.notifier).removeTrip(trip);
67 | }
68 | return value;
69 | }
70 |
71 | @override
72 | Widget build(BuildContext context, WidgetRef ref) {
73 | return Card(
74 | clipBehavior: Clip.antiAlias,
75 | shape: RoundedRectangleBorder(
76 | borderRadius: BorderRadius.circular(15),
77 | ),
78 | elevation: 5,
79 | child: Column(
80 | mainAxisSize: MainAxisSize.min,
81 | children: [
82 | Text(
83 | trip.tripName,
84 | textAlign: TextAlign.center,
85 | style: const TextStyle(
86 | fontSize: 20,
87 | fontWeight: FontWeight.bold,
88 | ),
89 | ),
90 | const SizedBox(
91 | height: 8,
92 | ),
93 | Container(
94 | alignment: Alignment.center,
95 | color: const Color(constants.primaryColorDark), //Color(0xffE1E5E4),
96 | height: 150,
97 |
98 | child: trip.tripImageUrl != null
99 | ? Stack(
100 | children: [
101 | const Center(child: CircularProgressIndicator()),
102 | CachedNetworkImage(
103 | cacheKey: trip.tripImageKey,
104 | imageUrl: trip.tripImageUrl!,
105 | width: double.maxFinite,
106 | height: 500,
107 | alignment: Alignment.topCenter,
108 | fit: BoxFit.fill,
109 | ),
110 | ],
111 | )
112 | : Image.asset(
113 | 'images/amplify.png',
114 | fit: BoxFit.contain,
115 | ),
116 | ),
117 | Row(
118 | mainAxisAlignment: MainAxisAlignment.spaceAround,
119 | children: [
120 | IconButton(
121 | onPressed: () {
122 | context.goNamed(
123 | AppRoute.editTrip.name,
124 | pathParameters: {'id': trip.id},
125 | extra: trip,
126 | );
127 | },
128 | icon: const Icon(Icons.edit),
129 | ),
130 | IconButton(
131 | onPressed: () {
132 | uploadImage(
133 | context: context,
134 | trip: trip,
135 | ref: ref,
136 | ).then((value) {
137 | if (value) {
138 | Navigator.of(context, rootNavigator: true).pop();
139 | ref.invalidate(tripControllerProvider(trip.id));
140 | }
141 | });
142 | },
143 | icon: const Icon(Icons.camera_enhance_sharp),
144 | ),
145 | IconButton(
146 | onPressed: () {
147 | deleteTrip(context, ref, trip).then((value) {
148 | if (value) {
149 | context.goNamed(
150 | AppRoute.home.name,
151 | );
152 | }
153 | });
154 | },
155 | icon: const Icon(Icons.delete),
156 | ),
157 | ],
158 | )
159 | ],
160 | ),
161 | );
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/lib/features/trip/ui/edit_trip_page/edit_trip_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_flutter/amplify_flutter.dart';
2 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
3 | import 'package:amplify_trips_planner/common/ui/bottomsheet_text_form_field.dart';
4 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
5 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
6 | import 'package:amplify_trips_planner/features/trip/controller/trip_controller.dart';
7 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_riverpod/flutter_riverpod.dart';
10 | import 'package:go_router/go_router.dart';
11 |
12 | class EditTripPage extends ConsumerWidget {
13 | EditTripPage({
14 | required this.trip,
15 | super.key,
16 | });
17 | final Trip trip;
18 |
19 | final formGlobalKey = GlobalKey();
20 |
21 | @override
22 | Widget build(BuildContext context, WidgetRef ref) {
23 | final tripNameController = TextEditingController(text: trip.tripName);
24 | final destinationController = TextEditingController(text: trip.destination);
25 | final startDateController = TextEditingController(
26 | text: trip.startDate.getDateTime().format('yyyy-MM-dd'),
27 | );
28 |
29 | final endDateController = TextEditingController(
30 | text: trip.endDate.getDateTime().format('yyyy-MM-dd'),
31 | );
32 |
33 | return Scaffold(
34 | appBar: AppBar(
35 | centerTitle: true,
36 | title: const Text(
37 | 'Amplify Trips Planner',
38 | ),
39 | leading: IconButton(
40 | onPressed: () {
41 | context.goNamed(
42 | AppRoute.trip.name,
43 | pathParameters: {'id': trip.id},
44 | );
45 | },
46 | icon: const Icon(Icons.arrow_back),
47 | ),
48 | backgroundColor: const Color(constants.primaryColorDark),
49 | ),
50 | body: SingleChildScrollView(
51 | child: Form(
52 | key: formGlobalKey,
53 | child: Container(
54 | padding: EdgeInsets.only(
55 | top: 15,
56 | left: 15,
57 | right: 15,
58 | bottom: MediaQuery.of(context).viewInsets.bottom + 15,
59 | ),
60 | width: double.infinity,
61 | child: Column(
62 | mainAxisSize: MainAxisSize.min,
63 | crossAxisAlignment: CrossAxisAlignment.start,
64 | children: [
65 | BottomSheetTextFormField(
66 | labelText: 'Trip Name',
67 | controller: tripNameController,
68 | keyboardType: TextInputType.name,
69 | ),
70 | const SizedBox(
71 | height: 20,
72 | ),
73 | BottomSheetTextFormField(
74 | labelText: 'Trip Destination',
75 | controller: destinationController,
76 | keyboardType: TextInputType.name,
77 | ),
78 | const SizedBox(
79 | height: 20,
80 | ),
81 | BottomSheetTextFormField(
82 | labelText: 'Start Date',
83 | controller: startDateController,
84 | keyboardType: TextInputType.datetime,
85 | onTap: () async {
86 | final pickedDate = await showDatePicker(
87 | context: context,
88 | initialDate: DateTime.now(),
89 | firstDate: DateTime(2000),
90 | lastDate: DateTime(2101),
91 | );
92 |
93 | if (pickedDate != null) {
94 | startDateController.text =
95 | pickedDate.format('yyyy-MM-dd');
96 | }
97 | },
98 | ),
99 | const SizedBox(
100 | height: 20,
101 | ),
102 | BottomSheetTextFormField(
103 | labelText: 'End Date',
104 | controller: endDateController,
105 | keyboardType: TextInputType.datetime,
106 | onTap: () async {
107 | if (startDateController.text.isNotEmpty) {
108 | final pickedDate = await showDatePicker(
109 | context: context,
110 | initialDate: DateTime.parse(startDateController.text),
111 | firstDate: DateTime.parse(startDateController.text),
112 | lastDate: DateTime(2101),
113 | );
114 |
115 | if (pickedDate != null) {
116 | endDateController.text =
117 | pickedDate.format('yyyy-MM-dd');
118 | }
119 | }
120 | },
121 | ),
122 | const SizedBox(
123 | height: 20,
124 | ),
125 | TextButton(
126 | child: const Text('OK'),
127 | onPressed: () async {
128 | final currentState = formGlobalKey.currentState;
129 | if (currentState == null) {
130 | return;
131 | }
132 | if (currentState.validate()) {
133 | final updatedTrip = trip.copyWith(
134 | tripName: tripNameController.text,
135 | destination: destinationController.text,
136 | startDate: TemporalDate(
137 | DateTime.parse(startDateController.text),
138 | ),
139 | endDate: TemporalDate(
140 | DateTime.parse(endDateController.text),
141 | ),
142 | );
143 |
144 | await ref
145 | .watch(tripControllerProvider(trip.id).notifier)
146 | .updateTrip(updatedTrip);
147 | if (context.mounted) {
148 | context.goNamed(
149 | AppRoute.trip.name,
150 | pathParameters: {'id': trip.id},
151 | extra: updatedTrip,
152 | );
153 | }
154 | }
155 | }, //,
156 | ),
157 | ],
158 | ),
159 | ),
160 | ),
161 | ),
162 | );
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/add_activity/add_activity_form.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
2 | import 'package:amplify_trips_planner/common/ui/bottomsheet_text_form_field.dart';
3 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
4 | import 'package:amplify_trips_planner/features/activity/controller/activities_list.dart';
5 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_riverpod/flutter_riverpod.dart';
8 | import 'package:go_router/go_router.dart';
9 |
10 | class AddActivityForm extends ConsumerWidget {
11 | AddActivityForm({
12 | required this.trip,
13 | super.key,
14 | });
15 |
16 | final AsyncValue trip;
17 |
18 | final formGlobalKey = GlobalKey();
19 |
20 | @override
21 | Widget build(BuildContext context, WidgetRef ref) {
22 | final activityNameController = TextEditingController();
23 | final activityDateController = TextEditingController();
24 | final activityTimeController = TextEditingController();
25 | var activityCategory = ActivityCategory.Flight;
26 | var activityTime = TimeOfDay.now();
27 | switch (trip) {
28 | case AsyncData(:final value):
29 | return SingleChildScrollView(
30 | child: Form(
31 | key: formGlobalKey,
32 | child: Container(
33 | padding: EdgeInsets.only(
34 | top: 15,
35 | left: 15,
36 | right: 15,
37 | bottom: MediaQuery.of(context).viewInsets.bottom + 15,
38 | ),
39 | width: double.infinity,
40 | child: Column(
41 | mainAxisSize: MainAxisSize.min,
42 | crossAxisAlignment: CrossAxisAlignment.start,
43 | children: [
44 | BottomSheetTextFormField(
45 | labelText: 'Activity Name',
46 | controller: activityNameController,
47 | keyboardType: TextInputType.name,
48 | ),
49 | const SizedBox(
50 | height: 20,
51 | ),
52 | DropdownButtonFormField(
53 | onChanged: (value) {
54 | activityCategory = value!;
55 | },
56 | value: activityCategory,
57 | decoration: const InputDecoration(
58 | labelText: 'Category',
59 | ),
60 | items: [
61 | for (var category in ActivityCategory.values)
62 | DropdownMenuItem(
63 | value: category,
64 | child: Text(category.name),
65 | ),
66 | ],
67 | ),
68 | const SizedBox(
69 | height: 20,
70 | ),
71 | BottomSheetTextFormField(
72 | labelText: 'Activity Date',
73 | controller: activityDateController,
74 | keyboardType: TextInputType.datetime,
75 | onTap: () async {
76 | final pickedDate = await showDatePicker(
77 | context: context,
78 | initialDate: DateTime.parse(value.startDate.toString()),
79 | firstDate: DateTime.parse(value.startDate.toString()),
80 | lastDate: DateTime.parse(value.endDate.toString()),
81 | );
82 |
83 | if (pickedDate != null) {
84 | activityDateController.text =
85 | pickedDate.format('yyyy-MM-dd');
86 | } else {}
87 | },
88 | ),
89 | const SizedBox(
90 | height: 20,
91 | ),
92 | BottomSheetTextFormField(
93 | labelText: 'Activity Time',
94 | controller: activityTimeController,
95 | keyboardType: TextInputType.datetime,
96 | onTap: () async {
97 | await showTimePicker(
98 | context: context,
99 | initialTime: activityTime,
100 | initialEntryMode: TimePickerEntryMode.dial,
101 | ).then((timeOfDay) {
102 | if (timeOfDay != null) {
103 | final localizations =
104 | MaterialLocalizations.of(context);
105 | final formattedTimeOfDay =
106 | localizations.formatTimeOfDay(timeOfDay);
107 |
108 | activityTimeController.text = formattedTimeOfDay;
109 | activityTime = timeOfDay;
110 | }
111 | });
112 | },
113 | ),
114 | const SizedBox(
115 | height: 20,
116 | ),
117 | TextButton(
118 | child: const Text('OK'),
119 | onPressed: () async {
120 | final currentState = formGlobalKey.currentState;
121 | if (currentState == null) {
122 | return;
123 | }
124 | if (currentState.validate()) {
125 | await ref
126 | .watch(activitiesListProvider(value.id).notifier)
127 | .add(
128 | name: activityNameController.text,
129 | activityDate: activityDateController.text,
130 | activityTime: activityTime,
131 | category: activityCategory,
132 | trip: value,
133 | );
134 | if (context.mounted) {
135 | context.goNamed(
136 | AppRoute.trip.name,
137 | pathParameters: {'id': value.id},
138 | );
139 | }
140 | }
141 | }, //,
142 | ),
143 | ],
144 | ),
145 | ),
146 | ),
147 | );
148 |
149 | case AsyncError():
150 | return const Center(
151 | child: Text('Error'),
152 | );
153 | case AsyncLoading():
154 | return const Center(
155 | child: CircularProgressIndicator(),
156 | );
157 |
158 | case _:
159 | return const Center(
160 | child: Text('Error'),
161 | );
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/edit_activity/edit_activity_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:amplify_flutter/amplify_flutter.dart';
2 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
3 | import 'package:amplify_trips_planner/common/ui/bottomsheet_text_form_field.dart';
4 | import 'package:amplify_trips_planner/common/utils/colors.dart' as constants;
5 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
6 | import 'package:amplify_trips_planner/features/activity/controller/activity_controller.dart';
7 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_riverpod/flutter_riverpod.dart';
10 | import 'package:go_router/go_router.dart';
11 | import 'package:intl/intl.dart';
12 |
13 | class EditActivityPage extends ConsumerWidget {
14 | EditActivityPage({
15 | required this.activity,
16 | super.key,
17 | });
18 |
19 | final Activity activity;
20 |
21 | final formGlobalKey = GlobalKey();
22 |
23 | @override
24 | Widget build(BuildContext context, WidgetRef ref) {
25 | final activityNameController =
26 | TextEditingController(text: activity.activityName);
27 | final activityDateController = TextEditingController(
28 | text: activity.activityDate.getDateTime().format('yyyy-MM-dd'),
29 | );
30 |
31 | var activityTime =
32 | TimeOfDay.fromDateTime(activity.activityTime!.getDateTime());
33 | final activityTimeController = TextEditingController(
34 | text: activity.activityTime!.getDateTime().format('hh:mm a'),
35 | );
36 |
37 | final activityCategoryController =
38 | TextEditingController(text: activity.category.name);
39 |
40 | var activityCategory = activity.category;
41 |
42 | return Scaffold(
43 | appBar: AppBar(
44 | centerTitle: true,
45 | title: const Text(
46 | 'Amplify Trips Planner',
47 | ),
48 | leading: IconButton(
49 | onPressed: () {
50 | context.goNamed(
51 | AppRoute.activity.name,
52 | pathParameters: {'id': activity.id},
53 | );
54 | },
55 | icon: const Icon(Icons.arrow_back),
56 | ),
57 | backgroundColor: const Color(constants.primaryColorDark),
58 | ),
59 | body: SingleChildScrollView(
60 | child: Form(
61 | key: formGlobalKey,
62 | child: Container(
63 | padding: EdgeInsets.only(
64 | top: 15,
65 | left: 15,
66 | right: 15,
67 | bottom: MediaQuery.of(context).viewInsets.bottom + 15,
68 | ),
69 | width: double.infinity,
70 | child: Column(
71 | mainAxisSize: MainAxisSize.min,
72 | crossAxisAlignment: CrossAxisAlignment.start,
73 | children: [
74 | BottomSheetTextFormField(
75 | labelText: 'Activity Name',
76 | controller: activityNameController,
77 | keyboardType: TextInputType.name,
78 | ),
79 | const SizedBox(
80 | height: 20,
81 | ),
82 | DropdownButtonFormField(
83 | onChanged: (value) {
84 | activityCategoryController.text = value!.name;
85 | activityCategory = value;
86 | },
87 | value: activityCategory,
88 | decoration: const InputDecoration(
89 | labelText: 'Category',
90 | ),
91 | items: [
92 | for (var category in ActivityCategory.values)
93 | DropdownMenuItem(
94 | value: category,
95 | child: Text(category.name),
96 | ),
97 | ],
98 | ),
99 | const SizedBox(
100 | height: 20,
101 | ),
102 | BottomSheetTextFormField(
103 | labelText: 'Activity Date',
104 | controller: activityDateController,
105 | keyboardType: TextInputType.datetime,
106 | onTap: () async {
107 | final pickedDate = await showDatePicker(
108 | context: context,
109 | initialDate:
110 | DateTime.parse(activity.activityDate.toString()),
111 | firstDate:
112 | DateTime.parse(activity.trip.startDate.toString()),
113 | lastDate:
114 | DateTime.parse(activity.trip.endDate.toString()),
115 | );
116 |
117 | if (pickedDate != null) {
118 | activityDateController.text =
119 | pickedDate.format('yyyy-MM-dd');
120 | } else {}
121 | },
122 | ),
123 | const SizedBox(
124 | height: 20,
125 | ),
126 | BottomSheetTextFormField(
127 | labelText: 'Activity Time',
128 | controller: activityTimeController,
129 | keyboardType: TextInputType.datetime,
130 | onTap: () async {
131 | await showTimePicker(
132 | context: context,
133 | initialTime: activityTime,
134 | initialEntryMode: TimePickerEntryMode.dial,
135 | ).then((timeOfDay) {
136 | if (timeOfDay != null) {
137 | final localizations = MaterialLocalizations.of(context);
138 | final formattedTimeOfDay =
139 | localizations.formatTimeOfDay(timeOfDay);
140 |
141 | activityTimeController.text = formattedTimeOfDay;
142 | activityTime = timeOfDay;
143 | }
144 | });
145 | },
146 | ),
147 | const SizedBox(
148 | height: 20,
149 | ),
150 | TextButton(
151 | child: const Text('OK'),
152 | onPressed: () async {
153 | final currentState = formGlobalKey.currentState;
154 | if (currentState == null) {
155 | return;
156 | }
157 | if (currentState.validate()) {
158 | final format = DateFormat.jm();
159 |
160 | activityTime = TimeOfDay.fromDateTime(
161 | format.parse(activityTimeController.text),
162 | );
163 |
164 | final now = DateTime.now();
165 | final time = DateTime(
166 | now.year,
167 | now.month,
168 | now.day,
169 | activityTime.hour,
170 | activityTime.minute,
171 | );
172 |
173 | final updatedActivity = activity.copyWith(
174 | category: ActivityCategory.values
175 | .byName(activityCategoryController.text),
176 | activityName: activityNameController.text,
177 | activityDate: TemporalDate(
178 | DateTime.parse(activityDateController.text),
179 | ),
180 | activityTime: TemporalTime.fromString(
181 | time.format('HH:mm:ss.sss'),
182 | ),
183 | );
184 |
185 | await ref
186 | .watch(
187 | activityControllerProvider(activity.id).notifier,
188 | )
189 | .updateActivity(updatedActivity);
190 | if (context.mounted) {
191 | context.goNamed(
192 | AppRoute.activity.name,
193 | pathParameters: {'id': activity.id},
194 | );
195 | }
196 | }
197 | }, //,
198 | ),
199 | ],
200 | ),
201 | ),
202 | ),
203 | ),
204 | );
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/lib/features/activity/ui/activity_page/activity_listview.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:amplify_trips_planner/common/navigation/router/routes.dart';
4 | import 'package:amplify_trips_planner/common/ui/upload_progress_dialog.dart';
5 | import 'package:amplify_trips_planner/common/utils/date_time_formatter.dart';
6 | import 'package:amplify_trips_planner/features/activity/controller/activities_list.dart';
7 | import 'package:amplify_trips_planner/features/activity/controller/activity_controller.dart';
8 | import 'package:amplify_trips_planner/features/activity/ui/activity_category_icon.dart';
9 | import 'package:amplify_trips_planner/features/activity/ui/activity_page/delete_activity_dialog.dart';
10 | import 'package:amplify_trips_planner/models/ModelProvider.dart';
11 | import 'package:file_picker/file_picker.dart';
12 | import 'package:flutter/material.dart';
13 | import 'package:flutter_riverpod/flutter_riverpod.dart';
14 | import 'package:go_router/go_router.dart';
15 | import 'package:url_launcher/url_launcher.dart';
16 |
17 | class ActivityListView extends ConsumerWidget {
18 | const ActivityListView({
19 | required this.activity,
20 | super.key,
21 | });
22 |
23 | final AsyncValue activity;
24 |
25 | Future deleteActivity(
26 | BuildContext context,
27 | WidgetRef ref,
28 | Activity activity,
29 | ) async {
30 | var value = await showDialog(
31 | context: context,
32 | builder: (BuildContext context) {
33 | return const DeleteActivityDialog();
34 | },
35 | );
36 | value ??= false;
37 |
38 | if (value) {
39 | await ref
40 | .watch(activitiesListProvider(activity.trip.id).notifier)
41 | .removeActivity(activity);
42 | }
43 |
44 | return value;
45 | }
46 |
47 | Future openFile({
48 | required BuildContext context,
49 | required WidgetRef ref,
50 | required Activity activity,
51 | }) async {
52 | final fileUrl = await ref
53 | .watch(activityControllerProvider(activity.id).notifier)
54 | .getFileUrl(activity);
55 |
56 | final url = Uri.parse(fileUrl);
57 | await launchUrl(url);
58 | }
59 |
60 | Future uploadFile({
61 | required BuildContext context,
62 | required WidgetRef ref,
63 | required Activity activity,
64 | }) async {
65 | final result = await FilePicker.platform.pickFiles(
66 | type: FileType.custom,
67 | allowedExtensions: ['jpg', 'pdf', 'png'],
68 | );
69 |
70 | if (result == null) {
71 | return false;
72 | }
73 |
74 | final platformFile = result.files.first;
75 |
76 | final file = File(platformFile.path!);
77 | if (context.mounted) {
78 | await showDialog(
79 | context: context,
80 | barrierDismissible: false,
81 | builder: (BuildContext context) {
82 | return const UploadProgressDialog();
83 | },
84 | );
85 | await ref
86 | .watch(activityControllerProvider(activity.id).notifier)
87 | .uploadFile(file, activity);
88 | }
89 | return true;
90 | }
91 |
92 | @override
93 | Widget build(BuildContext context, WidgetRef ref) {
94 | switch (activity) {
95 | case AsyncData(:final value):
96 | return ListView(
97 | children: [
98 | Card(
99 | child: ListTile(
100 | leading: ActivityCategoryIcon(activityCategory: value.category),
101 | title: Text(
102 | value.activityName,
103 | style: Theme.of(context).textTheme.titleLarge,
104 | ),
105 | subtitle: Text(value.category.name),
106 | ),
107 | ),
108 | ListTile(
109 | dense: true,
110 | title: Text(
111 | 'Activity Date',
112 | style: Theme.of(context)
113 | .textTheme
114 | .titleSmall!
115 | .copyWith(color: Colors.white),
116 | ),
117 | tileColor: Colors.grey,
118 | ),
119 | Card(
120 | child: ListTile(
121 | title: Text(
122 | value.activityDate.getDateTime().format('EE MMMM dd'),
123 | style: Theme.of(context).textTheme.titleLarge,
124 | ),
125 | subtitle: Text(
126 | value.activityTime!.getDateTime().format('hh:mm a'),
127 | ),
128 | ),
129 | ),
130 | ListTile(
131 | dense: true,
132 | title: Text(
133 | 'Documents',
134 | style: Theme.of(context)
135 | .textTheme
136 | .titleSmall!
137 | .copyWith(color: Colors.white),
138 | ),
139 | tileColor: Colors.grey,
140 | ),
141 | Card(
142 | child: value.activityImageUrl != null
143 | ? Row(
144 | mainAxisAlignment: MainAxisAlignment.spaceAround,
145 | children: [
146 | TextButton(
147 | style: TextButton.styleFrom(
148 | textStyle: const TextStyle(fontSize: 20),
149 | ),
150 | onPressed: () {
151 | openFile(
152 | context: context,
153 | ref: ref,
154 | activity: value,
155 | );
156 | },
157 | child: const Text('Open'),
158 | ),
159 | TextButton(
160 | style: TextButton.styleFrom(
161 | textStyle: const TextStyle(fontSize: 20),
162 | ),
163 | onPressed: () {
164 | uploadFile(
165 | context: context,
166 | activity: value,
167 | ref: ref,
168 | ).then(
169 | (isUploaded) => isUploaded ? context.pop() : null,
170 | );
171 | },
172 | child: const Text('Replace'),
173 | ),
174 | ],
175 | )
176 | : ListTile(
177 | title: TextButton(
178 | style: TextButton.styleFrom(
179 | textStyle: const TextStyle(fontSize: 20),
180 | ),
181 | onPressed: () {
182 | uploadFile(
183 | context: context,
184 | activity: value,
185 | ref: ref,
186 | ).then(
187 | (isUploaded) => isUploaded ? context.pop() : null,
188 | );
189 | // Navigator.of(context, rootNavigator: true)
190 | // .pop());
191 | },
192 | child: const Text('Attach a PDF or photo'),
193 | ),
194 | ),
195 | ),
196 | const ListTile(
197 | dense: true,
198 | tileColor: Colors.grey,
199 | visualDensity: VisualDensity(vertical: -4),
200 | ),
201 | Card(
202 | child: Row(
203 | mainAxisAlignment: MainAxisAlignment.spaceAround,
204 | children: [
205 | TextButton(
206 | style: TextButton.styleFrom(
207 | textStyle: const TextStyle(fontSize: 20),
208 | ),
209 | onPressed: () {
210 | context.goNamed(
211 | AppRoute.editActivity.name,
212 | pathParameters: {'id': value.id},
213 | extra: activity,
214 | );
215 | },
216 | child: const Text('Edit'),
217 | ),
218 | TextButton(
219 | style: TextButton.styleFrom(
220 | textStyle: const TextStyle(fontSize: 20),
221 | ),
222 | onPressed: () {
223 | deleteActivity(context, ref, value).then(
224 | (isDeleted) {
225 | if (isDeleted) {
226 | context.goNamed(
227 | AppRoute.trip.name,
228 | pathParameters: {'id': value.trip.id},
229 | );
230 | }
231 | },
232 | );
233 | },
234 | child: const Text('Delete'),
235 | ),
236 | ],
237 | ),
238 | )
239 | ],
240 | );
241 |
242 | case AsyncError():
243 | return const Center(
244 | child: Text('Error'),
245 | );
246 | case AsyncLoading():
247 | return const Center(
248 | child: CircularProgressIndicator(),
249 | );
250 |
251 | case _:
252 | return const Center(
253 | child: Text('Error'),
254 | );
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------