├── .eslintignore ├── .gitignore ├── .metadata ├── README.md ├── amplify ├── .config │ └── project-config.json ├── README.md ├── backend │ ├── analytics │ │ └── amplifyrecipe │ │ │ ├── parameters.json │ │ │ └── pinpoint-cloudformation-template.json │ ├── api │ │ └── amplifyrecipe │ │ │ ├── cli-inputs.json │ │ │ ├── parameters.json │ │ │ ├── resolvers │ │ │ └── README.md │ │ │ ├── schema.graphql │ │ │ ├── stacks │ │ │ └── CustomResources.json │ │ │ └── transform.conf.json │ ├── auth │ │ └── amplifyrecipe86a8efe0 │ │ │ └── cli-inputs.json │ ├── backend-config.json │ ├── function │ │ └── amplifyrecipefb7252f0 │ │ │ ├── amplify.state │ │ │ ├── amplifyrecipefb7252f0-cloudformation-template.json │ │ │ ├── custom-policies.json │ │ │ ├── function-parameters.json │ │ │ └── src │ │ │ ├── event.json │ │ │ ├── index.js │ │ │ ├── package-lock.json │ │ │ └── package.json │ ├── storage │ │ ├── Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev │ │ │ └── parameters.json │ │ └── s3b350e034 │ │ │ └── cli-inputs.json │ ├── tags.json │ └── types │ │ └── amplify-dependent-resources-ref.d.ts ├── cli.json ├── hooks │ └── README.md └── team-provider-info.json ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── amplify_recipe │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── icons │ ├── Add Friends.svg │ ├── Apple.svg │ ├── Arrow.svg │ ├── At.svg │ ├── Back.svg │ ├── Bookmark.svg │ ├── Calendar.svg │ ├── Camera.svg │ ├── Checklist.svg │ ├── Collaboration.svg │ ├── Comment.svg │ ├── Delete.svg │ ├── Eyes Close.svg │ ├── Eyes Open.svg │ ├── Faceboook.svg │ ├── Google.svg │ ├── Help.svg │ ├── Home.svg │ ├── Kebab.svg │ ├── Language.svg │ ├── Logout.svg │ ├── Moon.svg │ ├── Notification.svg │ ├── Outlined Bookmark.svg │ ├── Pencil.svg │ ├── Plus in Box.svg │ ├── Plus with container.svg │ ├── Plus.svg │ ├── Profile.svg │ ├── Remove.svg │ ├── Search.svg │ ├── Trash.svg │ └── clock.svg └── images │ └── onboard_image.png ├── binary └── macos │ ├── librealm_dart.dylib │ └── realm_version.txt ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ ├── Runner.entitlements │ └── RunnerDebug.entitlements ├── lib ├── features │ ├── authentication │ │ ├── screens │ │ │ ├── forgot_password_screen.dart │ │ │ ├── onboarding_screen.dart │ │ │ ├── register_screen.dart │ │ │ ├── resend_email_screen.dart │ │ │ └── sign_in_screen.dart │ │ └── widgets │ │ │ ├── forgot_password_form.dart │ │ │ ├── onboard_content.dart │ │ │ ├── password_confirmation_form.dart │ │ │ ├── sign_in_form.dart │ │ │ ├── sign_up_form.dart │ │ │ ├── socal_login_buttons.dart │ │ │ ├── terms_and_policy.dart │ │ │ └── user_confirmation_form.dart │ ├── details │ │ ├── components │ │ │ ├── ingredient_tile.dart │ │ │ └── ingredients.dart │ │ └── screens │ │ │ └── recipe_details_screen.dart │ ├── entry_point.dart │ ├── favorite │ │ └── screens │ │ │ └── favorite_screen.dart │ ├── home │ │ ├── screens │ │ │ └── home_screen.dart │ │ └── widgets │ │ │ └── search_container.dart │ ├── notifications │ │ └── screens │ │ │ └── notifications_screen.dart │ ├── profile │ │ ├── components │ │ │ └── settings.dart │ │ └── screens │ │ │ ├── edit_profile_screen.dart │ │ │ └── profile_screen.dart │ └── recipes │ │ └── screens │ │ └── search │ │ ├── screen │ │ ├── add_recipe_screen.dart │ │ ├── all_recipes_screen.dart │ │ └── search_screen.dart │ │ └── widgets │ │ └── recent_search_tile.dart ├── main.dart ├── models │ ├── Category.dart │ ├── Duration.dart │ ├── ModelProvider.dart │ └── Recipe.dart ├── shared │ ├── constants │ │ ├── constants.dart │ │ └── gaps.dart │ ├── data │ │ ├── authentication_repository.dart │ │ ├── database │ │ │ └── isar_provider.dart │ │ ├── implementation │ │ │ ├── cognito_authentication_repository.dart │ │ │ ├── local_notification_repository.dart │ │ │ ├── local_remote_recipe_repository.dart │ │ │ ├── local_search_repository.dart │ │ │ └── s3_storage_repository.dart │ │ ├── model │ │ │ ├── notification.dart │ │ │ ├── notification.g.dart │ │ │ ├── recipe.dart │ │ │ ├── recipe.g.dart │ │ │ ├── search_item.dart │ │ │ ├── search_item.g.dart │ │ │ ├── user.dart │ │ │ └── user.g.dart │ │ ├── notification_repository.dart │ │ ├── recipe_repository.dart │ │ ├── search_repository.dart │ │ └── storage_repository.dart │ ├── navigation │ │ └── routes.dart │ ├── utils │ │ └── form_utils.dart │ └── widgets │ │ ├── async_image_loader.dart │ │ ├── frosted_glass_container.dart │ │ ├── recipe_card.dart │ │ └── section_list_tile.dart └── themes │ ├── app_colors.dart │ └── app_theme.dart ├── pubspec.lock └── pubspec.yaml /.eslintignore: -------------------------------------------------------------------------------- 1 | amplify-codegen-temp/models -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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: 12cb4eb7a009f52b347b62ade7cb4854b926af72 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: 12cb4eb7a009f52b347b62ade7cb4854b926af72 17 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 18 | - platform: android 19 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 20 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 21 | - platform: ios 22 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 23 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 24 | - platform: linux 25 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 26 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 27 | - platform: macos 28 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 29 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 30 | - platform: web 31 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 32 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 33 | - platform: windows 34 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 35 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Complete Flutter Course: Take Your Flutter Skills to the Next Level for FREE! 2 | 3 | Welcome to our Complete Flutter Course. If you've been searching for the best Flutter course online, look no further. This comprehensive guide will not only teach you the basics but will elevate your Flutter skills to the next level. 4 | 5 | ### [Watch the full course on YouTube for FREE!](https://youtu.be/2j5ONMbX7xE) 6 | 7 | 8 | This is your one-stop spot to master on:\ 9 | 📁 Project Structure \ 10 | 🔐 Email & Social Logins \ 11 | 🔔 Push Notifications \ 12 | 🛠 CRUD Operations \ 13 | ☁️ AWSAmplify \ 14 | ...and that's just scratching the surface! We cover many more advanced topics to give you an edge in Flutter app development. 15 | 16 | 17 | **Useful links:** 18 | 19 | [🔗 Function link: ](https://gist.github.com/salihgueler/e9be9fa6cc9ece30ba05764146850f74) \ 20 | [🔗 Custom policy:](https://gist.github.com/salihgueler/2b44dd5494bdbfbd288a033af21bff9d) \ 21 | [🔥 Complate source code:](https://cutt.ly/PwkRT3yZ) 22 | 23 | [☁️ AWS Amplify:](https://aws.amazon.com/amplify/) \ 24 | [📄 AWS Amplify Doc:](https://docs.amplify.aws/lib/q/platform/flutter/) 25 | 26 | **👨‍💻 Instructor:** 27 | 28 | Muhammed Salih Guler (Sr. Dev Advocate at AWS Amplify | Flutter/Dart GDE) \ 29 | Follow him on [Twitter](https://twitter.com/salihgueler) & [Linkedin](https://www.linkedin.com/in/salihgueler/) 30 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "amplifyrecipe", 3 | "version": "3.1", 4 | "frontend": "flutter", 5 | "flutter": { 6 | "config": { 7 | "ResDir": "./lib/" 8 | } 9 | }, 10 | "providers": [ 11 | "awscloudformation" 12 | ] 13 | } -------------------------------------------------------------------------------- /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/backend/analytics/amplifyrecipe/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "amplifyrecipe", 3 | "roleName": "pinpointLambdaRolec51987f6", 4 | "cloudformationPolicyName": "cloudformationPolicyc51987f6", 5 | "cloudWatchPolicyName": "cloudWatchPolicyc51987f6", 6 | "pinpointPolicyName": "pinpointPolicyc51987f6", 7 | "authPolicyName": "pinpoint_amplify_c51987f6", 8 | "unauthPolicyName": "pinpoint_amplify_c51987f6", 9 | "authRoleName": { 10 | "Ref": "AuthRoleName" 11 | }, 12 | "unauthRoleName": { 13 | "Ref": "UnauthRoleName" 14 | }, 15 | "authRoleArn": { 16 | "Fn::GetAtt": [ 17 | "AuthRole", 18 | "Arn" 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifyrecipe/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "serviceConfiguration": { 4 | "apiName": "amplifyrecipe", 5 | "serviceName": "AppSync", 6 | "defaultAuthType": { 7 | "mode": "AMAZON_COGNITO_USER_POOLS", 8 | "cognitoUserPoolId": "authamplifyrecipe86a8efe0" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifyrecipe/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSyncApiName": "amplifyrecipe", 3 | "DynamoDBBillingMode": "PAY_PER_REQUEST", 4 | "DynamoDBEnableServerSideEncryption": false, 5 | "AuthCognitoUserPoolId": { 6 | "Fn::GetAtt": [ 7 | "authamplifyrecipe86a8efe0", 8 | "Outputs.UserPoolId" 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifyrecipe/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) -------------------------------------------------------------------------------- /amplify/backend/api/amplifyrecipe/schema.graphql: -------------------------------------------------------------------------------- 1 | enum Category { 2 | SOUP 3 | DESSERT 4 | SALAD 5 | APPETIZER 6 | FISH 7 | MAIN_COURSE 8 | } 9 | 10 | enum Duration { 11 | MINUTE 12 | HOUR 13 | } 14 | 15 | type Recipe @model @auth(rules: [{allow: private}]) { 16 | id: ID! 17 | title: String! 18 | serve: Int! 19 | duration: Duration! 20 | category: Category! 21 | description: String! 22 | image: String! 23 | duration_unit: Int! 24 | ingredients: [String!]! 25 | } -------------------------------------------------------------------------------- /amplify/backend/api/amplifyrecipe/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 | -------------------------------------------------------------------------------- /amplify/backend/api/amplifyrecipe/transform.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 5, 3 | "ElasticsearchWarning": true 4 | } -------------------------------------------------------------------------------- /amplify/backend/auth/amplifyrecipe86a8efe0/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "cognitoConfig": { 4 | "identityPoolName": "amplifyrecipe86a8efe0_identitypool_86a8efe0", 5 | "allowUnauthenticatedIdentities": true, 6 | "resourceNameTruncated": "amplif86a8efe0", 7 | "userPoolName": "amplifyrecipe86a8efe0_userpool_86a8efe0", 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": "amplif86a8efe0_userpoolclient_lambda_role", 35 | "userpoolClientSetAttributes": false, 36 | "sharedId": "86a8efe0", 37 | "resourceName": "amplifyrecipe86a8efe0", 38 | "authSelections": "identityPoolAndUserPool", 39 | "useDefault": "defaultSocial", 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 | "hostedUI": true, 62 | "hostedUIDomainName": "amplifyrecipe752a9874-752a9874", 63 | "authProvidersUserPool": [ 64 | "Facebook", 65 | "Google" 66 | ], 67 | "hostedUIProviderMeta": "[{\"ProviderName\":\"Facebook\",\"authorize_scopes\":\"email,public_profile\",\"AttributeMapping\":{\"email\":\"email\",\"username\":\"id\"}},{\"ProviderName\":\"Google\",\"authorize_scopes\":\"openid email profile\",\"AttributeMapping\":{\"email\":\"email\",\"username\":\"sub\"}}]", 68 | "authProviders": [], 69 | "oAuthMetadata": "{\"AllowedOAuthFlows\":[\"code\"],\"AllowedOAuthScopes\":[\"phone\",\"email\",\"openid\",\"profile\",\"aws.cognito.signin.user.admin\"],\"CallbackURLs\":[\"amplifyrecipe://\"],\"LogoutURLs\":[\"amplifyrecipe://\"]}" 70 | } 71 | } -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "analytics": { 3 | "amplifyrecipe": { 4 | "providerPlugin": "awscloudformation", 5 | "service": "Pinpoint" 6 | } 7 | }, 8 | "api": { 9 | "amplifyrecipe": { 10 | "dependsOn": [ 11 | { 12 | "attributes": [ 13 | "UserPoolId" 14 | ], 15 | "category": "auth", 16 | "resourceName": "amplifyrecipe86a8efe0" 17 | } 18 | ], 19 | "output": { 20 | "authConfig": { 21 | "additionalAuthenticationProviders": [], 22 | "defaultAuthentication": { 23 | "authenticationType": "AMAZON_COGNITO_USER_POOLS", 24 | "userPoolConfig": { 25 | "userPoolId": "authamplifyrecipe86a8efe0" 26 | } 27 | } 28 | } 29 | }, 30 | "providerPlugin": "awscloudformation", 31 | "service": "AppSync" 32 | } 33 | }, 34 | "auth": { 35 | "amplifyrecipe86a8efe0": { 36 | "customAuth": false, 37 | "dependsOn": [], 38 | "frontendAuthConfig": { 39 | "mfaConfiguration": "OFF", 40 | "mfaTypes": [ 41 | "SMS" 42 | ], 43 | "passwordProtectionSettings": { 44 | "passwordPolicyCharacters": [], 45 | "passwordPolicyMinLength": 8 46 | }, 47 | "signupAttributes": [ 48 | "EMAIL" 49 | ], 50 | "socialProviders": [ 51 | "FACEBOOK", 52 | "GOOGLE" 53 | ], 54 | "usernameAttributes": [ 55 | "EMAIL" 56 | ], 57 | "verificationMechanisms": [ 58 | "EMAIL" 59 | ] 60 | }, 61 | "providerPlugin": "awscloudformation", 62 | "service": "Cognito" 63 | } 64 | }, 65 | "function": { 66 | "amplifyrecipefb7252f0": { 67 | "build": true, 68 | "dependsOn": [ 69 | { 70 | "attributes": [ 71 | "GraphQLAPIIdOutput", 72 | "GraphQLAPIEndpointOutput" 73 | ], 74 | "category": "api", 75 | "resourceName": "amplifyrecipe" 76 | } 77 | ], 78 | "providerPlugin": "awscloudformation", 79 | "service": "Lambda" 80 | } 81 | }, 82 | "notifications": { 83 | "amplifyrecipe": { 84 | "channels": [ 85 | "APNS", 86 | "FCM" 87 | ], 88 | "service": "Pinpoint" 89 | } 90 | }, 91 | "parameters": { 92 | "AMPLIFY_analytics_Pinpoint_Id": { 93 | "usedBy": [ 94 | { 95 | "category": "analytics", 96 | "resourceName": "Pinpoint" 97 | } 98 | ] 99 | }, 100 | "AMPLIFY_analytics_Pinpoint_Name": { 101 | "usedBy": [ 102 | { 103 | "category": "analytics", 104 | "resourceName": "Pinpoint" 105 | } 106 | ] 107 | }, 108 | "AMPLIFY_analytics_Pinpoint_Region": { 109 | "usedBy": [ 110 | { 111 | "category": "analytics", 112 | "resourceName": "Pinpoint" 113 | } 114 | ] 115 | }, 116 | "AMPLIFY_function_amplifyrecipefb7252f0_deploymentBucketName": { 117 | "usedBy": [ 118 | { 119 | "category": "function", 120 | "resourceName": "amplifyrecipefb7252f0" 121 | } 122 | ] 123 | }, 124 | "AMPLIFY_function_amplifyrecipefb7252f0_s3Key": { 125 | "usedBy": [ 126 | { 127 | "category": "function", 128 | "resourceName": "amplifyrecipefb7252f0" 129 | } 130 | ] 131 | }, 132 | "AMPLIFY_notifications_Pinpoint_Id": { 133 | "usedBy": [ 134 | { 135 | "category": "notifications", 136 | "resourceName": "Pinpoint" 137 | } 138 | ] 139 | }, 140 | "AMPLIFY_notifications_Pinpoint_Name": { 141 | "usedBy": [ 142 | { 143 | "category": "notifications", 144 | "resourceName": "Pinpoint" 145 | } 146 | ] 147 | }, 148 | "AMPLIFY_notifications_Pinpoint_Region": { 149 | "usedBy": [ 150 | { 151 | "category": "notifications", 152 | "resourceName": "Pinpoint" 153 | } 154 | ] 155 | }, 156 | "AMPLIFY_storage_Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev_arn": { 157 | "usedBy": [ 158 | { 159 | "category": "storage", 160 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev" 161 | } 162 | ] 163 | }, 164 | "AMPLIFY_storage_Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev_partitionKeyName": { 165 | "usedBy": [ 166 | { 167 | "category": "storage", 168 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev" 169 | } 170 | ] 171 | }, 172 | "AMPLIFY_storage_Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev_partitionKeyType": { 173 | "usedBy": [ 174 | { 175 | "category": "storage", 176 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev" 177 | } 178 | ] 179 | }, 180 | "AMPLIFY_storage_Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev_region": { 181 | "usedBy": [ 182 | { 183 | "category": "storage", 184 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev" 185 | } 186 | ] 187 | }, 188 | "AMPLIFY_storage_Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev_streamArn": { 189 | "usedBy": [ 190 | { 191 | "category": "storage", 192 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev" 193 | } 194 | ] 195 | }, 196 | "AMPLIFY_storage_Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev_tableName": { 197 | "usedBy": [ 198 | { 199 | "category": "storage", 200 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev" 201 | } 202 | ] 203 | } 204 | }, 205 | "storage": { 206 | "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev": { 207 | "dependsOn": [], 208 | "providerPlugin": "awscloudformation", 209 | "service": "DynamoDB", 210 | "serviceType": "imported" 211 | }, 212 | "s3b350e034": { 213 | "dependsOn": [], 214 | "providerPlugin": "awscloudformation", 215 | "service": "S3" 216 | } 217 | } 218 | } -------------------------------------------------------------------------------- /amplify/backend/function/amplifyrecipefb7252f0/amplify.state: -------------------------------------------------------------------------------- 1 | { 2 | "pluginId": "amplify-nodejs-function-runtime-provider", 3 | "functionRuntime": "nodejs", 4 | "useLegacyBuild": true, 5 | "defaultEditorFile": "src/index.js" 6 | } -------------------------------------------------------------------------------- /amplify/backend/function/amplifyrecipefb7252f0/custom-policies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Effect": "Allow", 4 | "Action": [ 5 | "mobiletargeting:GetCampaigns", 6 | "mobiletargeting:CreateCampaign", 7 | "mobiletargeting:SendMessages" 8 | ], 9 | "Resource": [ 10 | "arn:aws:mobiletargeting:*:345161578575:apps/*" 11 | ] 12 | } 13 | ] -------------------------------------------------------------------------------- /amplify/backend/function/amplifyrecipefb7252f0/function-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambdaLayers": [] 3 | } -------------------------------------------------------------------------------- /amplify/backend/function/amplifyrecipefb7252f0/src/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "value1", 3 | "key2": "value2", 4 | "key3": "value3" 5 | } 6 | -------------------------------------------------------------------------------- /amplify/backend/function/amplifyrecipefb7252f0/src/index.js: -------------------------------------------------------------------------------- 1 | const { PinpointClient, CreateCampaignCommand } = require("@aws-sdk/client-pinpoint"); 2 | 3 | const pinpointClient = new PinpointClient({ region: "eu-central-1" }); 4 | 5 | /* 6 | * @type { import('@types/aws-lambda').APIGatewayProxyHandler } 7 | */ 8 | 9 | exports.handler = async (event) => { 10 | console.log(`EVENT: ${JSON.stringify(event)}`); 11 | for (const record of event.Records) { 12 | if (record.eventName === 'INSERT') { 13 | try { 14 | let recipeTitle = record.dynamodb.NewImage.title.S; 15 | let recipeDescription = record.dynamodb.NewImage.description.S; 16 | let recipeId = record.dynamodb.NewImage.id.S; 17 | const createCampaingCommand = new CreateCampaignCommand({ 18 | ApplicationId: "357c824c055f4213a6632f9486666550", 19 | WriteCampaignRequest: { 20 | AdditionalTreatments: [], 21 | Name: "Campaign: " + recipeId.substring(0, 50) + "!", 22 | SegmentId: "afced6a65fdd467d8e11e6848331e1ee", 23 | SegmentVersion: 1, 24 | HoldoutPercent: 0, 25 | TemplateConfiguration: {}, 26 | Limits: { 27 | MessagesPerSecond: 20000, 28 | MaximumDuration: 10800, 29 | Daily: 0, 30 | Total: 0, 31 | }, 32 | Schedule: { 33 | IsLocalTime: false, 34 | QuietTime: {}, 35 | StartTime: "IMMEDIATE", 36 | Timezone: "UTC", 37 | }, 38 | MessageConfiguration: { 39 | DefaultMessage: { 40 | Action: "OPEN_APP", 41 | Title: "New Recipe!", 42 | Body: "Check out: " + recipeTitle + "!", 43 | JsonBody: JSON.stringify({ 44 | recipeId: recipeId, 45 | recipeTitle: recipeTitle, 46 | recipeDescription: recipeDescription, 47 | }), 48 | }, 49 | APNSMessage: { 50 | Action: "OPEN_APP", 51 | Title: "New Recipe!", 52 | Body: "Check out: " + recipeTitle + "!", 53 | JsonBody: JSON.stringify({ 54 | recipeId: recipeId, 55 | recipeTitle: recipeTitle, 56 | recipeDescription: recipeDescription, 57 | }), 58 | }, 59 | GCMMessage: { 60 | Action: "OPEN_APP", 61 | Title: "New Recipe!", 62 | Body: "Check out: " + recipeTitle + "!", 63 | JsonBody: JSON.stringify({ 64 | recipeId: recipeId, 65 | recipeTitle: recipeTitle, 66 | recipeDescription: recipeDescription, 67 | }), 68 | }, 69 | BaiduMessage: { 70 | Action: "OPEN_APP", 71 | Title: "New Recipe!", 72 | Body: "Check out: " + recipeTitle + "!", 73 | JsonBody: JSON.stringify({ 74 | recipeId: recipeId, 75 | recipeTitle: recipeTitle, 76 | recipeDescription: recipeDescription, 77 | }), 78 | }, 79 | ADMMessage: { 80 | Action: "OPEN_APP", 81 | Title: "New Recipe!", 82 | Body: "Check out: " + recipeTitle + "!", 83 | JsonBody: JSON.stringify({ 84 | recipeId: recipeId, 85 | recipeTitle: recipeTitle, 86 | recipeDescription: recipeDescription, 87 | }), 88 | } 89 | } 90 | }, 91 | }); 92 | const data = await pinpointClient.send(createCampaingCommand); 93 | console.log('Send Message Respond: %j', data); 94 | } catch (error) { 95 | console.log('Pinpoint error: %j', error); 96 | } finally { 97 | console.log('Pinpoint finally finished.'); 98 | } 99 | } 100 | } 101 | }; -------------------------------------------------------------------------------- /amplify/backend/function/amplifyrecipefb7252f0/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amplifyrecipefb7252f0", 3 | "version": "2.0.0", 4 | "description": "Lambda function generated by Amplify", 5 | "main": "index.js", 6 | "license": "Apache-2.0", 7 | "devDependencies": { 8 | "@types/aws-lambda": "^8.10.92" 9 | }, 10 | "dependencies": { 11 | "@aws-sdk/client-pinpoint": "^3.391.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /amplify/backend/storage/Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceName": "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev", 3 | "serviceType": "imported" 4 | } -------------------------------------------------------------------------------- /amplify/backend/storage/s3b350e034/cli-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceName": "s3b350e034", 3 | "policyUUID": "b350e034", 4 | "bucketName": "amplifyrecipe108a68fcdfe84b1ebda1a9542810e694", 5 | "storageAccess": "auth", 6 | "guestAccess": [], 7 | "authAccess": [ 8 | "CREATE_AND_UPDATE", 9 | "READ", 10 | "DELETE" 11 | ], 12 | "triggerFunction": "NONE", 13 | "groupAccess": {} 14 | } -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /amplify/backend/types/amplify-dependent-resources-ref.d.ts: -------------------------------------------------------------------------------- 1 | export type AmplifyDependentResourcesAttributes = { 2 | "analytics": { 3 | "amplifyrecipe": { 4 | "Id": "string", 5 | "Region": "string", 6 | "appName": "string" 7 | } 8 | }, 9 | "api": { 10 | "amplifyrecipe": { 11 | "GraphQLAPIEndpointOutput": "string", 12 | "GraphQLAPIIdOutput": "string" 13 | } 14 | }, 15 | "auth": { 16 | "amplifyrecipe86a8efe0": { 17 | "AppClientID": "string", 18 | "AppClientIDWeb": "string", 19 | "HostedUIDomain": "string", 20 | "IdentityPoolId": "string", 21 | "IdentityPoolName": "string", 22 | "OAuthMetadata": "string", 23 | "UserPoolArn": "string", 24 | "UserPoolId": "string", 25 | "UserPoolName": "string" 26 | } 27 | }, 28 | "function": { 29 | "amplifyrecipefb7252f0": { 30 | "Arn": "string", 31 | "LambdaExecutionRole": "string", 32 | "LambdaExecutionRoleArn": "string", 33 | "Name": "string", 34 | "Region": "string" 35 | } 36 | }, 37 | "storage": { 38 | "s3b350e034": { 39 | "BucketName": "string", 40 | "Region": "string" 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/team-provider-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "awscloudformation": { 4 | "AuthRoleName": "amplify-amplifyrecipe-dev-145708-authRole", 5 | "UnauthRoleArn": "arn:aws:iam::345161578575:role/amplify-amplifyrecipe-dev-145708-unauthRole", 6 | "AuthRoleArn": "arn:aws:iam::345161578575:role/amplify-amplifyrecipe-dev-145708-authRole", 7 | "Region": "eu-central-1", 8 | "DeploymentBucketName": "amplify-amplifyrecipe-dev-145708-deployment", 9 | "UnauthRoleName": "amplify-amplifyrecipe-dev-145708-unauthRole", 10 | "StackName": "amplify-amplifyrecipe-dev-145708", 11 | "StackId": "arn:aws:cloudformation:eu-central-1:345161578575:stack/amplify-amplifyrecipe-dev-145708/34142f90-383e-11ee-b9da-069104f8e03c", 12 | "AmplifyAppId": "d1ni8t9fi3je8z" 13 | }, 14 | "categories": { 15 | "auth": { 16 | "amplifyrecipe86a8efe0": {} 17 | }, 18 | "api": { 19 | "amplifyrecipe": {} 20 | }, 21 | "storage": { 22 | "s3b350e034": {}, 23 | "Recipe3dqfxi2ts5g6lnj72cqbgk72dqdev": { 24 | "tableName": "Recipe-3dqfxi2ts5g6lnj72cqbgk72dq-dev", 25 | "region": "eu-central-1", 26 | "arn": "arn:aws:dynamodb:eu-central-1:345161578575:table/Recipe-3dqfxi2ts5g6lnj72cqbgk72dq-dev", 27 | "streamArn": "arn:aws:dynamodb:eu-central-1:345161578575:table/Recipe-3dqfxi2ts5g6lnj72cqbgk72dq-dev/stream/2023-08-14T14:17:44.962", 28 | "partitionKeyName": "id", 29 | "partitionKeyType": "S" 30 | } 31 | }, 32 | "analytics": { 33 | "Pinpoint": { 34 | "Name": "amplifyrecipe-dev", 35 | "Id": "357c824c055f4213a6632f9486666550", 36 | "Region": "eu-central-1" 37 | }, 38 | "amplifyrecipe": {} 39 | }, 40 | "notifications": { 41 | "Pinpoint": { 42 | "Name": "amplifyrecipe-dev", 43 | "Id": "357c824c055f4213a6632f9486666550", 44 | "Region": "eu-central-1" 45 | } 46 | }, 47 | "function": { 48 | "amplifyrecipefb7252f0": { 49 | "deploymentBucketName": "amplify-amplifyrecipe-dev-145708-deployment", 50 | "s3Key": "amplify-builds/amplifyrecipefb7252f0-66684f49457461496359-build.zip" 51 | } 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | 31 | analyzer: 32 | exclude: 33 | - lib/models 34 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | apply plugin: 'com.android.application' 24 | apply plugin: 'kotlin-android' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion flutter.compileSdkVersion 29 | ndkVersion flutter.ndkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.example.amplify_recipe" 47 | // You can update the following values to match your application needs. 48 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 49 | minSdkVersion 24 50 | targetSdkVersion flutter.targetSdkVersion 51 | versionCode flutterVersionCode.toInteger() 52 | versionName flutterVersionName 53 | } 54 | 55 | buildTypes { 56 | release { 57 | // TODO: Add your own signing config for the release build. 58 | // Signing with the debug keys for now, so `flutter run --release` works. 59 | signingConfig signingConfigs.debug 60 | } 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies { 69 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 70 | } 71 | 72 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1035948754199", 4 | "project_id": "amplifyrecipe", 5 | "storage_bucket": "amplifyrecipe.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:1035948754199:android:46a58667cbed85e6606352", 11 | "android_client_info": { 12 | "package_name": "com.example.amplify_recipe" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "1035948754199-ur7h7cfojm47lfpjeer2nn66vji9gi1t.apps.googleusercontent.com", 18 | "client_type": 1, 19 | "android_info": { 20 | "package_name": "com.example.amplify_recipe", 21 | "certificate_hash": "647e2c23cb7c558f1f95b00276ac206c23bc1bef" 22 | } 23 | }, 24 | { 25 | "client_id": "1035948754199-99c4efv7utj1t2fmsd7dkeuhb11dsbed.apps.googleusercontent.com", 26 | "client_type": 3 27 | } 28 | ], 29 | "api_key": [ 30 | { 31 | "current_key": "AIzaSyCTwkU7yRa6KHgqReIpYTWF3_MoyZbtwI8" 32 | } 33 | ], 34 | "services": { 35 | "appinvite_service": { 36 | "other_platform_oauth_client": [ 37 | { 38 | "client_id": "1035948754199-99c4efv7utj1t2fmsd7dkeuhb11dsbed.apps.googleusercontent.com", 39 | "client_type": 3 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | ], 46 | "configuration_version": "1" 47 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | 9 | 13 | 21 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/amplify_recipe/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.amplify_recipe 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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.4.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.15' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | tasks.register("clean", Delete) { 31 | delete rootProject.buildDir 32 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/icons/Add Friends.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/Arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/At.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Checklist.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Collaboration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Eyes Close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Eyes Open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Faceboook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /assets/icons/Google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/icons/Help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Kebab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Language.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/icons/Moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Notification.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Outlined Bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Plus in Box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/Plus with container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/Plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/Remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/Trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/images/onboard_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/assets/images/onboard_image.png -------------------------------------------------------------------------------- /binary/macos/librealm_dart.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/binary/macos/librealm_dart.dylib -------------------------------------------------------------------------------- /binary/macos/realm_version.txt: -------------------------------------------------------------------------------- 1 | 1.0.3 -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | 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 | 43 | post_install do |installer| 44 | installer.generated_projects.each do |project| 45 | project.targets.each do |target| 46 | target.build_configurations.each do |config| 47 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' 48 | end 49 | end 50 | end 51 | installer.pods_project.targets.each do |target| 52 | flutter_additional_ios_build_settings(target) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - amplify_auth_cognito (0.0.1): 3 | - Flutter 4 | - FlutterMacOS 5 | - amplify_push_notifications (0.0.1): 6 | - AmplifyUtilsNotifications 7 | - Flutter 8 | - amplify_secure_storage (0.0.1): 9 | - Flutter 10 | - AmplifyUtilsNotifications (1.1.0) 11 | - connectivity_plus (0.0.1): 12 | - Flutter 13 | - ReachabilitySwift 14 | - device_info_plus (0.0.1): 15 | - Flutter 16 | - Flutter (1.0.0) 17 | - image_picker_ios (0.0.1): 18 | - Flutter 19 | - isar_flutter_libs (1.0.0): 20 | - Flutter 21 | - package_info_plus (0.4.5): 22 | - Flutter 23 | - path_provider_foundation (0.0.1): 24 | - Flutter 25 | - FlutterMacOS 26 | - ReachabilitySwift (5.0.0) 27 | - shared_preferences_foundation (0.0.1): 28 | - Flutter 29 | - FlutterMacOS 30 | 31 | DEPENDENCIES: 32 | - amplify_auth_cognito (from `.symlinks/plugins/amplify_auth_cognito/darwin`) 33 | - amplify_push_notifications (from `.symlinks/plugins/amplify_push_notifications/ios`) 34 | - amplify_secure_storage (from `.symlinks/plugins/amplify_secure_storage/ios`) 35 | - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) 36 | - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) 37 | - Flutter (from `Flutter`) 38 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) 39 | - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) 40 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 41 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 42 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 43 | 44 | SPEC REPOS: 45 | trunk: 46 | - AmplifyUtilsNotifications 47 | - ReachabilitySwift 48 | 49 | EXTERNAL SOURCES: 50 | amplify_auth_cognito: 51 | :path: ".symlinks/plugins/amplify_auth_cognito/darwin" 52 | amplify_push_notifications: 53 | :path: ".symlinks/plugins/amplify_push_notifications/ios" 54 | amplify_secure_storage: 55 | :path: ".symlinks/plugins/amplify_secure_storage/ios" 56 | connectivity_plus: 57 | :path: ".symlinks/plugins/connectivity_plus/ios" 58 | device_info_plus: 59 | :path: ".symlinks/plugins/device_info_plus/ios" 60 | Flutter: 61 | :path: Flutter 62 | image_picker_ios: 63 | :path: ".symlinks/plugins/image_picker_ios/ios" 64 | isar_flutter_libs: 65 | :path: ".symlinks/plugins/isar_flutter_libs/ios" 66 | package_info_plus: 67 | :path: ".symlinks/plugins/package_info_plus/ios" 68 | path_provider_foundation: 69 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 70 | shared_preferences_foundation: 71 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 72 | 73 | SPEC CHECKSUMS: 74 | amplify_auth_cognito: 76b5a54f05f66f69966b468b8121a0dd33a32c70 75 | amplify_push_notifications: 694e708ce7b2a9f39ff6cd2d4de69005040011ca 76 | amplify_secure_storage: 501cbdac671a050090d48dfcc002d5637d3f4a55 77 | AmplifyUtilsNotifications: 56221a17675c052b82d06981797aae8e0735820e 78 | connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a 79 | device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea 80 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 81 | image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 82 | isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 83 | package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 84 | path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 85 | ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 86 | shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 87 | 88 | PODFILE CHECKSUM: fa61b0820d8a526e68a48f3d621f636f34ba4f43 89 | 90 | COCOAPODS: 1.12.1 91 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/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/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abuanwar072/Flutter-Course-Amplify-Recipe-App/d3925995bb4b069d9ba2de8c109a6cf1407f5778/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Amplify Recipe 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | amplify_recipe 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSCameraUsageDescription 30 | This app requires access to the camera. 31 | NSMicrophoneUsageDescription 32 | This app requires access to the microphone. 33 | NSPhotoLibraryUsageDescription 34 | This app requires access to the photo library. 35 | UIApplicationSupportsIndirectInputEvents 36 | 37 | UIBackgroundModes 38 | 39 | fetch 40 | remote-notification 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UIViewControllerBasedStatusBarAppearance 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/RunnerDebug.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/authentication/screens/forgot_password_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/features/authentication/widgets/forgot_password_form.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../shared/constants/constants.dart'; 5 | import '../../../shared/constants/gaps.dart'; 6 | import '../../../themes/app_colors.dart'; 7 | 8 | class ForgotPasswordScreen extends StatelessWidget { 9 | const ForgotPasswordScreen({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Forgot Password'), 16 | ), 17 | body: SafeArea( 18 | child: SingleChildScrollView( 19 | padding: const EdgeInsets.all(defaultPadding), 20 | child: Column( 21 | crossAxisAlignment: CrossAxisAlignment.start, 22 | children: [ 23 | Text( 24 | 'Forgot Password', 25 | style: Theme.of(context).textTheme.headlineSmall!.copyWith( 26 | color: AppColors.text, 27 | ), 28 | ), 29 | gapH16, 30 | const Text( 31 | 'Enter your email address and we will send you a reset instructions.', 32 | ), 33 | gapH24, 34 | const ForgotPassForm(), 35 | ], 36 | ), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/features/authentication/screens/onboarding_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import '../widgets/onboard_content.dart'; 5 | 6 | class OnboardingScreen extends StatelessWidget { 7 | const OnboardingScreen({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | extendBodyBehindAppBar: true, 13 | appBar: AppBar( 14 | systemOverlayStyle: SystemUiOverlayStyle.light, 15 | backgroundColor: Colors.transparent, 16 | ), 17 | body: Stack( 18 | fit: StackFit.expand, 19 | children: [ 20 | Image.asset( 21 | 'assets/images/onboard_image.png', 22 | fit: BoxFit.fitHeight, 23 | ), 24 | Container( 25 | decoration: const BoxDecoration( 26 | gradient: LinearGradient( 27 | begin: Alignment.topCenter, 28 | end: Alignment.bottomCenter, 29 | stops: [0.35, 1], 30 | colors: [ 31 | Colors.transparent, 32 | Colors.black, 33 | ], 34 | ), 35 | ), 36 | ), 37 | const OnboardContent(), 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/features/authentication/screens/register_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/constants/constants.dart'; 2 | import 'package:amplify_recipe/shared/constants/gaps.dart'; 3 | import 'package:amplify_recipe/themes/app_colors.dart'; 4 | import 'package:flutter/gestures.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:go_router/go_router.dart'; 7 | 8 | import '../widgets/sign_up_form.dart'; 9 | import '../widgets/socal_login_buttons.dart'; 10 | import '../widgets/terms_and_policy.dart'; 11 | 12 | class RegisterScreen extends StatelessWidget { 13 | const RegisterScreen({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | body: SafeArea( 19 | child: SingleChildScrollView( 20 | padding: const EdgeInsets.all(defaultPadding), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Text( 25 | 'Create Account', 26 | style: Theme.of(context).textTheme.headlineSmall!.copyWith( 27 | color: AppColors.text, 28 | ), 29 | ), 30 | gapH16, 31 | Text.rich( 32 | TextSpan( 33 | text: 'Enter your name, email and password for sign up. ', 34 | children: [ 35 | TextSpan( 36 | text: 'Already have an account?', 37 | recognizer: TapGestureRecognizer() 38 | ..onTap = () { 39 | context.push('/sign-in'); 40 | }, 41 | style: const TextStyle( 42 | color: AppColors.primary, 43 | height: 1.5, 44 | ), 45 | ), 46 | ], 47 | ), 48 | ), 49 | gapH24, 50 | const SignUpForm(), 51 | gapH20, 52 | const TermsAndPolicy(), 53 | gapH16, 54 | const SocalLoginButtons() 55 | ], 56 | ), 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/features/authentication/screens/resend_email_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../shared/constants/constants.dart'; 5 | import '../../../shared/constants/gaps.dart'; 6 | import '../../../themes/app_colors.dart'; 7 | 8 | class EmailResendScreen extends StatelessWidget { 9 | const EmailResendScreen({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Forgot Password'), 16 | ), 17 | body: SafeArea( 18 | child: SingleChildScrollView( 19 | padding: const EdgeInsets.all(defaultPadding), 20 | child: Column( 21 | crossAxisAlignment: CrossAxisAlignment.start, 22 | children: [ 23 | Text( 24 | 'Email Sent', 25 | style: Theme.of(context).textTheme.headlineSmall!.copyWith( 26 | color: AppColors.text, 27 | ), 28 | ), 29 | gapH16, 30 | Text.rich( 31 | TextSpan( 32 | text: 'Not receiving email? check on promotion page, spam. ', 33 | children: [ 34 | TextSpan( 35 | text: 'Having problem?', 36 | recognizer: TapGestureRecognizer()..onTap = () {}, 37 | style: const TextStyle( 38 | color: AppColors.primary, 39 | height: 1.5, 40 | ), 41 | ), 42 | ], 43 | ), 44 | ), 45 | gapH24, 46 | ElevatedButton( 47 | onPressed: () {}, 48 | child: const Text('Resend'), 49 | ), 50 | ], 51 | ), 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/features/authentication/screens/sign_in_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/features/authentication/widgets/sign_in_form.dart'; 2 | import 'package:amplify_recipe/shared/constants/constants.dart'; 3 | import 'package:amplify_recipe/shared/constants/gaps.dart'; 4 | import 'package:amplify_recipe/themes/app_colors.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:go_router/go_router.dart'; 7 | 8 | import '../widgets/socal_login_buttons.dart'; 9 | 10 | class SignInScreen extends StatelessWidget { 11 | const SignInScreen({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | body: SafeArea( 17 | child: SingleChildScrollView( 18 | padding: const EdgeInsets.all(defaultPadding), 19 | child: Column( 20 | crossAxisAlignment: CrossAxisAlignment.start, 21 | children: [ 22 | Text( 23 | 'Welcome', 24 | style: Theme.of(context).textTheme.headlineSmall!.copyWith( 25 | color: AppColors.text, 26 | ), 27 | ), 28 | gapH16, 29 | const Text('Enter your email address for sign in'), 30 | gapH24, 31 | const SignInForm(), 32 | Align( 33 | alignment: Alignment.centerRight, 34 | child: TextButton( 35 | onPressed: () { 36 | context.push('/forgot-password'); 37 | }, 38 | child: Text( 39 | 'Forgot password?', 40 | style: Theme.of(context).textTheme.bodyMedium, 41 | ), 42 | ), 43 | ), 44 | gapH16, 45 | const SocalLoginButtons() 46 | ], 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/forgot_password_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/main.dart'; 2 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | 6 | import '../../../shared/constants/gaps.dart'; 7 | import '../../../shared/utils/form_utils.dart'; 8 | 9 | class ForgotPassForm extends StatefulWidget { 10 | const ForgotPassForm({ 11 | super.key, 12 | }); 13 | 14 | @override 15 | State createState() => _ForgotPassFormState(); 16 | } 17 | 18 | class _ForgotPassFormState extends State { 19 | final TextEditingController _emailController = TextEditingController(); 20 | 21 | @override 22 | void dispose() { 23 | _emailController.dispose(); 24 | super.dispose(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Form( 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Text( 34 | 'Email address', 35 | style: Theme.of(context).textTheme.titleSmall, 36 | ), 37 | gapH8, 38 | TextFormField( 39 | controller: _emailController, 40 | validator: FormUtils.emailValidator, 41 | keyboardType: TextInputType.emailAddress, 42 | decoration: const InputDecoration(hintText: 'test@mail.com'), 43 | ), 44 | gapH24, 45 | ElevatedButton( 46 | onPressed: () async { 47 | await getIt 48 | .get() 49 | .forgotPassword(_emailController.text); 50 | if (mounted) { 51 | context.push('/password-confirmation/${_emailController.text}'); 52 | } 53 | }, 54 | child: const Text('Reset Password'), 55 | ), 56 | ], 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/onboard_content.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/main.dart'; 2 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | 6 | import '../../../shared/constants/constants.dart'; 7 | import '../../../shared/constants/gaps.dart'; 8 | 9 | class OnboardContent extends StatefulWidget { 10 | const OnboardContent({ 11 | super.key, 12 | }); 13 | 14 | @override 15 | State createState() => _OnboardContentState(); 16 | } 17 | 18 | class _OnboardContentState extends State { 19 | late final Future futureOperation; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | futureOperation = getIt.get().isUserLoggedIn(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return SafeArea( 30 | child: Padding( 31 | padding: const EdgeInsets.all(defaultPadding * 2), 32 | child: Column( 33 | mainAxisAlignment: MainAxisAlignment.end, 34 | children: [ 35 | Text( 36 | 'Cooking with \ngreat experiences', 37 | textAlign: TextAlign.center, 38 | style: Theme.of(context).textTheme.headlineMedium!.copyWith( 39 | color: Colors.white, 40 | fontWeight: FontWeight.w600, 41 | ), 42 | ), 43 | gapH16, 44 | const SizedBox( 45 | width: 250, 46 | child: Text( 47 | 'The best experience is given based on the ingredients you have at home', 48 | textAlign: TextAlign.center, 49 | style: TextStyle(color: Color(0xFFADADAD)), 50 | ), 51 | ), 52 | gapH24, 53 | FutureBuilder( 54 | future: futureOperation, 55 | builder: (context, snapshot) { 56 | if (snapshot.data == true) { 57 | WidgetsBinding.instance.addPostFrameCallback((_) { 58 | context.go('/entry-point'); 59 | }); 60 | } 61 | return AnimatedSwitcher( 62 | duration: const Duration(milliseconds: 300), 63 | child: snapshot.data == false 64 | ? Column( 65 | children: [ 66 | ElevatedButton( 67 | onPressed: () { 68 | context.push('/register'); 69 | }, 70 | child: const Text('Register'), 71 | ), 72 | gapH8, 73 | OutlinedButton( 74 | onPressed: () { 75 | context.push('/sign-in'); 76 | }, 77 | style: OutlinedButton.styleFrom( 78 | foregroundColor: Colors.white, 79 | side: 80 | const BorderSide(color: Colors.transparent), 81 | ), 82 | child: const Text('Sign in'), 83 | ), 84 | ], 85 | ) 86 | : const SizedBox.shrink(), 87 | ); 88 | }, 89 | ), 90 | ], 91 | ), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/password_confirmation_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 2 | import 'package:amplify_recipe/main.dart'; 3 | import 'package:flutter/gestures.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:go_router/go_router.dart'; 6 | 7 | import '../../../shared/constants/constants.dart'; 8 | import '../../../shared/constants/gaps.dart'; 9 | import '../../../shared/utils/form_utils.dart'; 10 | import '../../../themes/app_colors.dart'; 11 | 12 | class PasswordConfirmationForm extends StatefulWidget { 13 | const PasswordConfirmationForm({ 14 | required this.email, 15 | super.key, 16 | }); 17 | 18 | final String email; 19 | 20 | @override 21 | State createState() => _PasswordConfirmationFormState(); 22 | } 23 | 24 | class _PasswordConfirmationFormState extends State { 25 | late final TextEditingController confirmationCodeController = 26 | TextEditingController(); 27 | late final TextEditingController newPasswordController = 28 | TextEditingController(); 29 | final _formKey = GlobalKey(); 30 | bool _isEnabled = true; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | body: SafeArea( 36 | child: Padding( 37 | padding: const EdgeInsets.all(defaultPadding), 38 | child: Form( 39 | key: _formKey, 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | children: [ 43 | Text( 44 | 'Confirmation Code', 45 | style: Theme.of(context).textTheme.titleSmall, 46 | ), 47 | gapH8, 48 | TextFormField( 49 | controller: confirmationCodeController, 50 | validator: FormUtils.requireFieldValidator, 51 | decoration: 52 | const InputDecoration(hintText: 'Confirmation Code'), 53 | ), 54 | gapH16, 55 | Text( 56 | 'New Password', 57 | style: Theme.of(context).textTheme.titleSmall, 58 | ), 59 | gapH8, 60 | TextFormField( 61 | controller: newPasswordController, 62 | validator: FormUtils.requireFieldValidator, 63 | obscureText: true, 64 | decoration: 65 | const InputDecoration(hintText: 'New Password'), 66 | ), 67 | gapH24, 68 | ElevatedButton( 69 | onPressed: _isEnabled 70 | ? () { 71 | if (_formKey.currentState!.validate()) { 72 | setState(() { 73 | _isEnabled = false; 74 | }); 75 | getIt 76 | .get() 77 | .confirmPasswordReset( 78 | widget.email, 79 | newPasswordController.text, 80 | confirmationCodeController.text, 81 | ) 82 | .then((value) { 83 | context.push('/sign-in'); 84 | }).onError((error, stackTrace) { 85 | setState(() { 86 | _isEnabled = true; 87 | }); 88 | ScaffoldMessenger.of(context).showSnackBar( 89 | SnackBar( 90 | content: Text(error.toString()), 91 | ), 92 | ); 93 | }); 94 | } 95 | } 96 | : null, 97 | child: const Text('Confirm New Password'), 98 | ), 99 | gapH24, 100 | Text.rich( 101 | TextSpan( 102 | text: 103 | 'Not receiving email? check on promotion page or spam.\n', 104 | children: [ 105 | TextSpan( 106 | text: 'Or click here to send the code again.', 107 | recognizer: TapGestureRecognizer()..onTap = () {}, 108 | style: const TextStyle( 109 | color: AppColors.primary, 110 | height: 1.5, 111 | ), 112 | ), 113 | ], 114 | ), 115 | ), 116 | ], 117 | ), 118 | ), 119 | ), 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/sign_in_form.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_import 2 | 3 | import 'package:amplify_recipe/features/authentication/widgets/user_confirmation_form.dart'; 4 | import 'package:amplify_recipe/features/entry_point.dart'; 5 | import 'package:amplify_recipe/main.dart'; 6 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 7 | import 'package:amplify_recipe/shared/data/notification_repository.dart'; 8 | import 'package:amplify_recipe/shared/data/recipe_repository.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:go_router/go_router.dart'; 11 | 12 | import '../../../shared/constants/gaps.dart'; 13 | import '../../../shared/utils/form_utils.dart'; 14 | 15 | class SignInForm extends StatefulWidget { 16 | const SignInForm({ 17 | super.key, 18 | }); 19 | 20 | @override 21 | State createState() => _SignInFormState(); 22 | } 23 | 24 | class _SignInFormState extends State { 25 | late final TextEditingController emailController = TextEditingController(); 26 | late final TextEditingController passwordController = TextEditingController(); 27 | late final _formKey = GlobalKey(); 28 | bool _isEnabled = true; 29 | 30 | @override 31 | void dispose() { 32 | emailController.dispose(); 33 | passwordController.dispose(); 34 | super.dispose(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Form( 40 | key: _formKey, 41 | child: Column( 42 | crossAxisAlignment: CrossAxisAlignment.start, 43 | children: [ 44 | Text( 45 | 'Email address', 46 | style: Theme.of(context).textTheme.titleSmall, 47 | ), 48 | gapH8, 49 | TextFormField( 50 | controller: emailController, 51 | validator: FormUtils.emailValidator, 52 | keyboardType: TextInputType.emailAddress, 53 | textInputAction: TextInputAction.next, 54 | decoration: const InputDecoration(hintText: 'user@example.com'), 55 | ), 56 | gapH16, 57 | Text( 58 | 'Password', 59 | style: Theme.of(context).textTheme.titleSmall, 60 | ), 61 | gapH8, 62 | TextFormField( 63 | controller: passwordController, 64 | validator: FormUtils.passwordValidator, 65 | obscureText: true, 66 | decoration: const InputDecoration(hintText: 'Enter your password'), 67 | ), 68 | gapH24, 69 | ElevatedButton( 70 | onPressed: _isEnabled 71 | ? () { 72 | if (_formKey.currentState!.validate()) { 73 | setState(() { 74 | _isEnabled = false; 75 | }); 76 | getIt 77 | .get() 78 | .logInWithCredentials( 79 | emailController.text, 80 | passwordController.text, 81 | ) 82 | .then((value) { 83 | context.go('/entry-point'); 84 | }).onError((error, stackTrace) { 85 | setState(() { 86 | _isEnabled = true; 87 | }); 88 | }); 89 | } 90 | } 91 | : null, 92 | child: const Text('Login'), 93 | ), 94 | ], 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/sign_up_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 2 | import 'package:amplify_recipe/main.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | 6 | import '../../../shared/constants/gaps.dart'; 7 | import '../../../shared/utils/form_utils.dart'; 8 | 9 | class SignUpForm extends StatefulWidget { 10 | const SignUpForm({ 11 | super.key, 12 | }); 13 | 14 | @override 15 | State createState() => _SignUpFormState(); 16 | } 17 | 18 | class _SignUpFormState extends State { 19 | late final TextEditingController nameController = TextEditingController(); 20 | late final TextEditingController emailController = TextEditingController(); 21 | late final TextEditingController passwordController = TextEditingController(); 22 | final _formKey = GlobalKey(); 23 | bool _isEnabled = true; 24 | 25 | @override 26 | void dispose() { 27 | nameController.dispose(); 28 | emailController.dispose(); 29 | passwordController.dispose(); 30 | super.dispose(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Form( 36 | key: _formKey, 37 | child: Column( 38 | crossAxisAlignment: CrossAxisAlignment.start, 39 | children: [ 40 | Text( 41 | 'Full Name', 42 | style: Theme.of(context).textTheme.titleSmall, 43 | ), 44 | gapH8, 45 | TextFormField( 46 | controller: nameController, 47 | validator: FormUtils.requireFieldValidator, 48 | textInputAction: TextInputAction.next, 49 | decoration: const InputDecoration(hintText: 'Enter your name'), 50 | ), 51 | gapH16, 52 | Text( 53 | 'Email address', 54 | style: Theme.of(context).textTheme.titleSmall, 55 | ), 56 | gapH8, 57 | TextFormField( 58 | controller: emailController, 59 | validator: FormUtils.emailValidator, 60 | keyboardType: TextInputType.emailAddress, 61 | textInputAction: TextInputAction.next, 62 | decoration: const InputDecoration(hintText: 'test@mail.com'), 63 | ), 64 | gapH16, 65 | Text( 66 | 'Password', 67 | style: Theme.of(context).textTheme.titleSmall, 68 | ), 69 | gapH8, 70 | TextFormField( 71 | controller: passwordController, 72 | validator: FormUtils.passwordValidator, 73 | obscureText: true, 74 | decoration: const InputDecoration(hintText: 'Enter your password'), 75 | ), 76 | gapH24, 77 | ElevatedButton( 78 | onPressed: _isEnabled 79 | ? () { 80 | if (_formKey.currentState!.validate()) { 81 | setState(() { 82 | _isEnabled = false; 83 | }); 84 | getIt 85 | .get() 86 | .signUp( 87 | emailController.text, 88 | passwordController.text, 89 | nameController.text, 90 | ) 91 | .then((value) { 92 | context 93 | .go('/user-confirmation/${emailController.text}'); 94 | }).onError( 95 | (error, stackTrace) { 96 | setState(() { 97 | _isEnabled = true; 98 | }); 99 | ScaffoldMessenger.of(context).showSnackBar( 100 | SnackBar( 101 | content: Text(error.toString()), 102 | ), 103 | ); 104 | }, 105 | ); 106 | } 107 | } 108 | : null, 109 | child: const Text('Sign up'), 110 | ), 111 | ], 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/socal_login_buttons.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_import 2 | 3 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 4 | import 'package:amplify_recipe/features/entry_point.dart'; 5 | import 'package:amplify_recipe/main.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_svg/flutter_svg.dart'; 8 | import 'package:go_router/go_router.dart'; 9 | 10 | import '../../../shared/constants/constants.dart'; 11 | import '../../../shared/constants/gaps.dart'; 12 | 13 | class SocalLoginButtons extends StatelessWidget { 14 | const SocalLoginButtons({ 15 | super.key, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Column( 21 | children: [ 22 | const Row( 23 | children: [ 24 | Expanded( 25 | child: Divider(), 26 | ), 27 | Padding( 28 | padding: EdgeInsets.symmetric(horizontal: defaultPadding / 1.5), 29 | child: Text('or continue with'), 30 | ), 31 | Expanded( 32 | child: Divider(), 33 | ), 34 | ], 35 | ), 36 | gapH16, 37 | OutlinedButton.icon( 38 | onPressed: () { 39 | getIt.get().logInWithGoogle().then( 40 | (value) { 41 | context.go('/entry-point'); 42 | }, 43 | ).onError((error, stackTrace) { 44 | ScaffoldMessenger.of(context).showSnackBar( 45 | SnackBar( 46 | content: Text(error.toString()), 47 | ), 48 | ); 49 | }); 50 | }, 51 | icon: SvgPicture.asset('assets/icons/Google.svg'), 52 | label: const Text('Sign Up with Google'), 53 | ), 54 | gapH16, 55 | OutlinedButton.icon( 56 | onPressed: () { 57 | getIt.get().logInWithFacebook().then( 58 | (value) { 59 | context.go('/entry-point'); 60 | }, 61 | ).onError((error, stackTrace) { 62 | ScaffoldMessenger.of(context).showSnackBar( 63 | SnackBar( 64 | content: Text(error.toString()), 65 | ), 66 | ); 67 | }); 68 | }, 69 | icon: SvgPicture.asset('assets/icons/Faceboook.svg'), 70 | label: const Text('Sign Up with Facebook'), 71 | ), 72 | ], 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/terms_and_policy.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../themes/app_colors.dart'; 5 | 6 | class TermsAndPolicy extends StatelessWidget { 7 | const TermsAndPolicy({ 8 | super.key, 9 | }); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Text.rich( 14 | TextSpan(text: 'By signing up, you agree to our ', children: [ 15 | TextSpan( 16 | text: 'Terms of Service', 17 | recognizer: TapGestureRecognizer()..onTap = () {}, 18 | style: const TextStyle( 19 | color: AppColors.primary, 20 | height: 1.5, 21 | ), 22 | ), 23 | const TextSpan(text: ' and '), 24 | TextSpan( 25 | text: 'Privacy Policy', 26 | recognizer: TapGestureRecognizer()..onTap = () {}, 27 | style: const TextStyle( 28 | color: AppColors.primary, 29 | height: 1.5, 30 | ), 31 | ), 32 | ]), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/authentication/widgets/user_confirmation_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 2 | import 'package:amplify_recipe/main.dart'; 3 | import 'package:flutter/gestures.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:go_router/go_router.dart'; 6 | 7 | import '../../../shared/constants/constants.dart'; 8 | import '../../../shared/constants/gaps.dart'; 9 | import '../../../shared/utils/form_utils.dart'; 10 | import '../../../themes/app_colors.dart'; 11 | 12 | class UserConfirmationForm extends StatefulWidget { 13 | const UserConfirmationForm({ 14 | required this.email, 15 | super.key, 16 | }); 17 | 18 | final String email; 19 | 20 | @override 21 | State createState() => _UserConfirmationFormState(); 22 | } 23 | 24 | class _UserConfirmationFormState extends State { 25 | late final TextEditingController confirmationCodeController = 26 | TextEditingController(); 27 | final _formKey = GlobalKey(); 28 | bool _isEnabled = true; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | body: SafeArea( 34 | child: Padding( 35 | padding: const EdgeInsets.all(defaultPadding), 36 | child: Form( 37 | key: _formKey, 38 | child: Column( 39 | crossAxisAlignment: CrossAxisAlignment.start, 40 | children: [ 41 | Text( 42 | 'Confirmation Code', 43 | style: Theme.of(context).textTheme.titleSmall, 44 | ), 45 | gapH8, 46 | TextFormField( 47 | controller: confirmationCodeController, 48 | validator: FormUtils.requireFieldValidator, 49 | keyboardType: TextInputType.emailAddress, 50 | decoration: 51 | const InputDecoration(hintText: 'Confirmation Code'), 52 | ), 53 | gapH24, 54 | ElevatedButton( 55 | onPressed: _isEnabled 56 | ? () { 57 | if (_formKey.currentState!.validate()) { 58 | setState(() { 59 | _isEnabled = false; 60 | }); 61 | getIt 62 | .get() 63 | .confirmUser( 64 | widget.email, 65 | confirmationCodeController.text, 66 | ) 67 | .then((value) { 68 | context.push('/sign-in'); 69 | }).onError((error, stackTrace) { 70 | setState(() { 71 | _isEnabled = true; 72 | }); 73 | ScaffoldMessenger.of(context).showSnackBar( 74 | SnackBar( 75 | content: Text(error.toString()), 76 | ), 77 | ); 78 | }); 79 | } 80 | } 81 | : null, 82 | child: const Text('Confirm User'), 83 | ), 84 | gapH24, 85 | Text.rich( 86 | TextSpan( 87 | text: 88 | 'Not receiving email? check on promotion page or spam.\n', 89 | children: [ 90 | TextSpan( 91 | text: 'Or click here to send the code again.', 92 | recognizer: TapGestureRecognizer()..onTap = () {}, 93 | style: const TextStyle( 94 | color: AppColors.primary, 95 | height: 1.5, 96 | ), 97 | ), 98 | ], 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ), 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/features/details/components/ingredient_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../shared/constants/constants.dart'; 4 | import '../../../themes/app_colors.dart'; 5 | 6 | class IngredientTile extends StatelessWidget { 7 | const IngredientTile({ 8 | super.key, 9 | required this.name, 10 | required this.quantity, 11 | }); 12 | 13 | final String name, quantity; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | decoration: const BoxDecoration( 19 | color: AppColors.inputfieldBg, 20 | borderRadius: BorderRadius.all(Radius.circular(defaultBorderRadius)), 21 | ), 22 | child: ListTile( 23 | title: Text(name), 24 | trailing: Text(quantity), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/features/details/components/ingredients.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/constants/constants.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../shared/constants/gaps.dart'; 5 | import 'ingredient_tile.dart'; 6 | 7 | class Ingredients extends StatelessWidget { 8 | const Ingredients({ 9 | required this.ingredients, 10 | super.key, 11 | }); 12 | 13 | final List ingredients; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | gapH24, 21 | Text( 22 | 'Ingredients ${ingredients.length}', 23 | style: Theme.of(context).textTheme.titleLarge, 24 | ), 25 | ...ingredients.map((ingredient) { 26 | final ingredientSplit = ingredient.split('-'); 27 | return Padding( 28 | padding: const EdgeInsets.only(top: defaultPadding), 29 | child: IngredientTile( 30 | name: ingredientSplit[1], 31 | quantity: ingredientSplit[0], 32 | ), 33 | ); 34 | }) 35 | ], 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/features/entry_point.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/features/favorite/screens/favorite_screen.dart'; 2 | import 'package:amplify_recipe/features/home/screens/home_screen.dart'; 3 | import 'package:amplify_recipe/features/profile/screens/profile_screen.dart'; 4 | import 'package:amplify_recipe/themes/app_colors.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_svg/flutter_svg.dart'; 7 | 8 | class EntryPoint extends StatefulWidget { 9 | const EntryPoint({super.key}); 10 | 11 | @override 12 | State createState() => _EntryPointState(); 13 | } 14 | 15 | class _EntryPointState extends State { 16 | int _currentPage = 0; 17 | final List _pages = [ 18 | const HomeScreen(), 19 | const FavoriteScreen(), 20 | const ProfileScreen(), 21 | ]; 22 | static const _iconColor = Color(0xffADADAD); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | body: _pages[_currentPage], 28 | bottomNavigationBar: BottomNavigationBar( 29 | currentIndex: _currentPage, 30 | onTap: (index) { 31 | setState(() { 32 | _currentPage = index; 33 | }); 34 | }, 35 | showSelectedLabels: false, 36 | showUnselectedLabels: false, 37 | items: [ 38 | BottomNavigationBarItem( 39 | icon: SvgPicture.asset( 40 | 'assets/icons/Home.svg', 41 | colorFilter: const ColorFilter.mode( 42 | _iconColor, 43 | BlendMode.srcIn, 44 | ), 45 | ), 46 | activeIcon: SvgPicture.asset( 47 | 'assets/icons/Home.svg', 48 | colorFilter: const ColorFilter.mode( 49 | AppColors.primary, 50 | BlendMode.srcIn, 51 | ), 52 | ), 53 | label: 'Home', 54 | ), 55 | BottomNavigationBarItem( 56 | icon: SvgPicture.asset( 57 | 'assets/icons/Bookmark.svg', 58 | colorFilter: const ColorFilter.mode( 59 | _iconColor, 60 | BlendMode.srcIn, 61 | ), 62 | ), 63 | activeIcon: SvgPicture.asset( 64 | 'assets/icons/Bookmark.svg', 65 | colorFilter: const ColorFilter.mode( 66 | AppColors.primary, 67 | BlendMode.srcIn, 68 | ), 69 | ), 70 | label: 'Bookmarked', 71 | ), 72 | BottomNavigationBarItem( 73 | icon: SvgPicture.asset( 74 | 'assets/icons/Profile.svg', 75 | colorFilter: const ColorFilter.mode( 76 | _iconColor, 77 | BlendMode.srcIn, 78 | ), 79 | ), 80 | activeIcon: SvgPicture.asset( 81 | 'assets/icons/Profile.svg', 82 | colorFilter: const ColorFilter.mode( 83 | AppColors.primary, 84 | BlendMode.srcIn, 85 | ), 86 | ), 87 | label: 'Profile', 88 | ), 89 | ], 90 | ), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/features/favorite/screens/favorite_screen.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_import 2 | 3 | import 'package:amplify_recipe/shared/data/model/recipe.dart'; 4 | import 'package:amplify_recipe/shared/data/recipe_repository.dart'; 5 | import 'package:amplify_recipe/features/details/screens/recipe_details_screen.dart'; 6 | import 'package:amplify_recipe/main.dart'; 7 | import 'package:amplify_recipe/shared/constants/gaps.dart'; 8 | import 'package:amplify_recipe/shared/widgets/recipe_card.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:go_router/go_router.dart'; 11 | 12 | import '../../../shared/constants/constants.dart'; 13 | 14 | class FavoriteScreen extends StatelessWidget { 15 | const FavoriteScreen({super.key}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | body: SafeArea( 21 | child: Padding( 22 | padding: const EdgeInsets.symmetric(horizontal: defaultPadding), 23 | child: Column( 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | gapH24, 27 | Text( 28 | 'Saved 😍', 29 | style: Theme.of(context).textTheme.titleLarge, 30 | ), 31 | gapH8, 32 | Expanded( 33 | child: StreamBuilder>( 34 | stream: 35 | getIt.get().listenFavoritedRecipes(), 36 | builder: (context, snapshot) { 37 | if (snapshot.hasData) { 38 | final recipes = snapshot.data!; 39 | if (recipes.isEmpty) { 40 | return Center( 41 | child: Text( 42 | 'No saved recipes yet', 43 | style: Theme.of(context).textTheme.titleLarge, 44 | ), 45 | ); 46 | } 47 | return ListView.builder( 48 | itemCount: recipes.length, 49 | itemBuilder: (context, index) { 50 | final recipe = recipes[index]; 51 | return Padding( 52 | padding: 53 | const EdgeInsets.only(bottom: defaultPadding), 54 | child: RecipeCard( 55 | press: () { 56 | context.push( 57 | '/recipe/${recipe.id}', 58 | ); 59 | }, 60 | onBookmarked: () { 61 | getIt 62 | .get() 63 | .toggleFavoriteForRecipe( 64 | id: recipe.id, 65 | isFavorited: !recipe.isFavorited, 66 | ); 67 | }, 68 | title: recipe.title, 69 | image: recipe.image, 70 | category: recipe.category, 71 | duration: recipe.duration, 72 | serve: recipe.serve, 73 | isFavorited: recipe.isFavorited, 74 | ), 75 | ); 76 | }, 77 | ); 78 | } else if (snapshot.hasError) { 79 | return Center( 80 | child: Text( 81 | snapshot.error.toString(), 82 | style: Theme.of(context).textTheme.bodyMedium, 83 | ), 84 | ); 85 | } else { 86 | return const Center( 87 | child: CircularProgressIndicator(), 88 | ); 89 | } 90 | }, 91 | ), 92 | ) 93 | ], 94 | ), 95 | ), 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/features/home/widgets/search_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | 5 | import '../../../shared/constants/constants.dart'; 6 | import '../../../shared/constants/gaps.dart'; 7 | import '../../../themes/app_colors.dart'; 8 | 9 | class SearchContaner extends StatelessWidget { 10 | const SearchContaner({ 11 | super.key, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return GestureDetector( 17 | onTap: () { 18 | context.push('/search-recipes'); 19 | }, 20 | child: Container( 21 | height: 60, 22 | padding: const EdgeInsets.symmetric(horizontal: defaultPadding), 23 | decoration: const BoxDecoration( 24 | color: AppColors.inputfieldBg, 25 | borderRadius: BorderRadius.all(Radius.circular(defaultBorderRadius)), 26 | ), 27 | child: Row( 28 | children: [ 29 | SvgPicture.asset('assets/icons/Search.svg'), 30 | gapW8, 31 | const Text('Type to find recipes..'), 32 | ], 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/features/notifications/screens/notifications_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/main.dart'; 2 | import 'package:amplify_recipe/shared/data/model/notification.dart'; 3 | import 'package:amplify_recipe/shared/data/notification_repository.dart'; 4 | import 'package:flutter/material.dart' hide Notification; 5 | import 'package:go_router/go_router.dart'; 6 | 7 | class NotificationsScreen extends StatelessWidget { 8 | const NotificationsScreen({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: const Text('Notifications'), 15 | ), 16 | body: FutureBuilder>( 17 | future: getIt.get().fetchAllNotifications(), 18 | builder: (context, snapshot) { 19 | if (snapshot.hasData) { 20 | if (snapshot.data!.isEmpty) { 21 | return Center( 22 | child: Text( 23 | 'You don\'t have any notifications (yet).', 24 | style: Theme.of(context).textTheme.titleLarge, 25 | ), 26 | ); 27 | } 28 | return ListView( 29 | children: [ 30 | for (final notification in snapshot.data!) 31 | ListTile( 32 | title: Text(notification.title), 33 | subtitle: Text(notification.description), 34 | tileColor: notification.isSeen 35 | ? Colors.transparent 36 | : Theme.of(context) 37 | .colorScheme 38 | .primary 39 | .withOpacity(0.1), 40 | onTap: () { 41 | getIt 42 | .get() 43 | .markAsSeen(notification.id); 44 | context.push('/recipe/${notification.recipeId}'); 45 | }, 46 | ) 47 | ], 48 | ); 49 | } else { 50 | return const Center( 51 | child: CircularProgressIndicator(), 52 | ); 53 | } 54 | }, 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/features/profile/components/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 2 | import 'package:amplify_recipe/main.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_svg/flutter_svg.dart'; 6 | import 'package:go_router/go_router.dart'; 7 | 8 | import '../../../shared/constants/constants.dart'; 9 | import '../../../shared/constants/gaps.dart'; 10 | import '../../../themes/app_colors.dart'; 11 | 12 | class Settings extends StatelessWidget { 13 | const Settings({ 14 | super.key, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Column( 20 | children: [ 21 | gapH8, 22 | Container( 23 | color: const Color(0xFF757575).withOpacity(0.08), 24 | child: const ListTile( 25 | title: Text('Settings'), 26 | ), 27 | ), 28 | gapH16, 29 | SettingListTile( 30 | onTap: () {}, 31 | title: 'Notifications', 32 | iconSrc: 'assets/icons/Notification.svg', 33 | ), 34 | SettingListTile( 35 | onTap: () {}, 36 | title: 'Language', 37 | trailingText: 'English', 38 | iconSrc: 'assets/icons/Language.svg', 39 | ), 40 | SettingListTile( 41 | onTap: () {}, 42 | title: 'Theme', 43 | trailingText: 'Light', 44 | iconSrc: 'assets/icons/Moon.svg', 45 | ), 46 | SettingListTile( 47 | onTap: () {}, 48 | title: 'Help', 49 | iconSrc: 'assets/icons/Help.svg', 50 | ), 51 | gapH8, 52 | SettingListTile( 53 | onTap: () { 54 | getIt.get().signOut().then((value) { 55 | context.go('/'); 56 | }); 57 | }, 58 | title: 'Log out', 59 | isLogout: true, 60 | iconSrc: 'assets/icons/Logout.svg', 61 | ), 62 | ], 63 | ); 64 | } 65 | } 66 | 67 | class SettingListTile extends StatelessWidget { 68 | const SettingListTile({ 69 | super.key, 70 | required this.title, 71 | required this.iconSrc, 72 | this.trailingText, 73 | this.isLogout = false, 74 | required this.onTap, 75 | }); 76 | 77 | final String title, iconSrc; 78 | final String? trailingText; 79 | final bool isLogout; 80 | final VoidCallback onTap; 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return Padding( 85 | padding: const EdgeInsets.only(bottom: defaultPadding / 2), 86 | child: ListTile( 87 | onTap: onTap, 88 | minLeadingWidth: 20, 89 | leading: SvgPicture.asset( 90 | iconSrc, 91 | colorFilter: ColorFilter.mode( 92 | isLogout ? AppColors.error : Colors.grey.shade500, 93 | BlendMode.srcIn), 94 | height: 24, 95 | width: 24, 96 | ), 97 | title: Text( 98 | title, 99 | style: TextStyle(color: isLogout ? AppColors.error : null), 100 | ), 101 | trailing: isLogout 102 | ? const SizedBox() 103 | : SizedBox( 104 | width: 100, 105 | child: Row( 106 | mainAxisAlignment: MainAxisAlignment.end, 107 | children: [ 108 | trailingText != null 109 | ? Text(trailingText!) 110 | : const SizedBox(), 111 | gapW4, 112 | const Icon(CupertinoIcons.forward), 113 | ], 114 | ), 115 | ), 116 | ), 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/features/profile/screens/profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 2 | import 'package:amplify_recipe/main.dart'; 3 | import 'package:amplify_recipe/shared/constants/constants.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:go_router/go_router.dart'; 7 | 8 | import '../components/settings.dart'; 9 | 10 | class ProfileScreen extends StatelessWidget { 11 | const ProfileScreen({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: const Text('Profile'), 18 | ), 19 | body: Column( 20 | children: [ 21 | Container( 22 | color: const Color(0xFF757575).withOpacity(0.08), 23 | child: const ListTile( 24 | title: Text('Profile'), 25 | ), 26 | ), 27 | Padding( 28 | padding: const EdgeInsets.symmetric(vertical: defaultPadding), 29 | child: ListTile( 30 | onTap: () { 31 | context.push('/edit-profile'); 32 | }, 33 | minLeadingWidth: 60, 34 | leading: CircleAvatar( 35 | radius: 30, 36 | backgroundColor: Colors.grey.shade300, 37 | backgroundImage: NetworkImage( 38 | getIt 39 | .get() 40 | .currentUser 41 | ?.profilePicture ?? 42 | 'https://picsum.photos/200', 43 | ), 44 | ), 45 | title: Text(getIt.get().fullName), 46 | trailing: const Icon(CupertinoIcons.forward), 47 | ), 48 | ), 49 | const Settings() 50 | ], 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/features/recipes/screens/search/screen/all_recipes_screen.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_import 2 | 3 | import 'package:amplify_recipe/shared/data/model/recipe.dart'; 4 | import 'package:amplify_recipe/shared/data/recipe_repository.dart'; 5 | import 'package:amplify_recipe/features/details/screens/recipe_details_screen.dart'; 6 | import 'package:amplify_recipe/main.dart'; 7 | import 'package:amplify_recipe/shared/constants/gaps.dart'; 8 | import 'package:amplify_recipe/shared/widgets/recipe_card.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:go_router/go_router.dart'; 11 | 12 | import '../../../../../shared/constants/constants.dart'; 13 | 14 | class AllRecipesScreen extends StatelessWidget { 15 | const AllRecipesScreen({super.key}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final recipeRepository = getIt.get(); 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text( 23 | 'All Recipes 👾', 24 | style: Theme.of(context).textTheme.titleLarge, 25 | ), 26 | ), 27 | body: SafeArea( 28 | child: Padding( 29 | padding: const EdgeInsets.symmetric(horizontal: defaultPadding), 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | gapH8, 34 | Expanded( 35 | child: StreamBuilder>( 36 | stream: recipeRepository.listenRecipes(), 37 | builder: (context, snapshot) { 38 | if (snapshot.hasData) { 39 | final recipes = snapshot.data!; 40 | return ListView.builder( 41 | itemCount: recipes.length, 42 | itemBuilder: (context, index) { 43 | final recipe = recipes[index]; 44 | return Padding( 45 | padding: 46 | const EdgeInsets.only(bottom: defaultPadding), 47 | child: RecipeCard( 48 | press: () { 49 | context.push( 50 | '/recipe/${recipe.id}', 51 | ); 52 | }, 53 | onBookmarked: () { 54 | recipeRepository.toggleFavoriteForRecipe( 55 | id: recipe.id, 56 | isFavorited: !recipe.isFavorited, 57 | ); 58 | }, 59 | title: recipe.title, 60 | image: recipe.image, 61 | category: recipe.category, 62 | duration: recipe.duration, 63 | serve: recipe.serve, 64 | isFavorited: recipe.isFavorited, 65 | ), 66 | ); 67 | }, 68 | ); 69 | } else if (snapshot.hasError) { 70 | return Center( 71 | child: Text( 72 | snapshot.error.toString(), 73 | style: Theme.of(context).textTheme.bodyMedium, 74 | ), 75 | ); 76 | } else { 77 | return const Center( 78 | child: CircularProgressIndicator(), 79 | ); 80 | } 81 | }, 82 | ), 83 | ) 84 | ], 85 | ), 86 | ), 87 | ), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/features/recipes/screens/search/widgets/recent_search_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | 4 | import '../../../../../themes/app_colors.dart'; 5 | 6 | class RecentSearchTile extends StatelessWidget { 7 | const RecentSearchTile({ 8 | super.key, 9 | required this.title, 10 | required this.onDeleted, 11 | this.onTap, 12 | }); 13 | 14 | final String title; 15 | final VoidCallback onDeleted; 16 | final VoidCallback? onTap; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return SizedBox( 21 | height: 48, 22 | child: ListTile( 23 | onTap: onTap, 24 | contentPadding: EdgeInsets.zero, 25 | title: Text( 26 | title, 27 | style: const TextStyle(color: AppColors.bodyText), 28 | ), 29 | trailing: IconButton( 30 | onPressed: onDeleted, 31 | icon: SvgPicture.asset('assets/icons/Remove.svg', 32 | colorFilter: const ColorFilter.mode( 33 | AppColors.bodyText, 34 | BlendMode.srcIn, 35 | )), 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; 2 | import 'package:amplify_flutter/amplify_flutter.dart'; 3 | import 'package:amplify_api/amplify_api.dart'; 4 | import 'package:amplify_push_notifications_pinpoint/amplify_push_notifications_pinpoint.dart'; 5 | import 'package:amplify_recipe/amplifyconfiguration.dart'; 6 | import 'package:amplify_recipe/models/ModelProvider.dart'; 7 | import 'package:amplify_recipe/shared/data/database/isar_provider.dart'; 8 | import 'package:amplify_recipe/shared/data/implementation/local_notification_repository.dart'; 9 | import 'package:amplify_recipe/shared/data/implementation/local_remote_recipe_repository.dart'; 10 | import 'package:amplify_recipe/shared/data/implementation/local_search_repository.dart'; 11 | import 'package:amplify_recipe/shared/data/implementation/cognito_authentication_repository.dart'; 12 | import 'package:amplify_recipe/shared/data/implementation/s3_storage_repository.dart'; 13 | import 'package:amplify_recipe/shared/data/notification_repository.dart'; 14 | import 'package:amplify_recipe/shared/data/storage_repository.dart'; 15 | import 'package:amplify_recipe/shared/data/authentication_repository.dart'; 16 | import 'package:amplify_recipe/shared/data/recipe_repository.dart'; 17 | import 'package:amplify_recipe/shared/data/search_repository.dart'; 18 | import 'package:amplify_recipe/shared/navigation/routes.dart'; 19 | import 'package:amplify_recipe/themes/app_theme.dart'; 20 | import 'package:amplify_storage_s3/amplify_storage_s3.dart'; 21 | import 'package:flutter/material.dart'; 22 | import 'package:get_it/get_it.dart'; 23 | 24 | final getIt = GetIt.instance; 25 | 26 | Future main() async { 27 | WidgetsFlutterBinding.ensureInitialized(); 28 | await _configureAmplify(); 29 | await _registerData(); 30 | await getIt.get().handlePermissions(); 31 | await getIt.get().listenNotifications(); 32 | runApp(const MyApp()); 33 | } 34 | 35 | Future _configureAmplify() async { 36 | try { 37 | await Amplify.addPlugins( 38 | [ 39 | AmplifyAuthCognito(), 40 | AmplifyAPI(modelProvider: ModelProvider.instance), 41 | AmplifyStorageS3(), 42 | AmplifyPushNotificationsPinpoint(), 43 | ], 44 | ); 45 | await Amplify.configure(amplifyconfig); 46 | } on AmplifyException catch (e) { 47 | safePrint(e); 48 | } 49 | } 50 | 51 | Future _registerData() async { 52 | await IsarProvider.open(); 53 | getIt.registerSingleton( 54 | CognitoAuthenticationRepository(), 55 | ); 56 | getIt.registerSingleton( 57 | S3StorageRepository(), 58 | ); 59 | getIt.registerSingleton( 60 | LocalRemoteRecipeRepository(), 61 | ); 62 | getIt.registerSingleton( 63 | LocalSearchRepository(), 64 | ); 65 | getIt.registerSingleton( 66 | LocalNotificationRepository(), 67 | ); 68 | } 69 | 70 | class MyApp extends StatelessWidget { 71 | const MyApp({super.key}); 72 | 73 | // This widget is the root of your application. 74 | @override 75 | Widget build(BuildContext context) { 76 | return MaterialApp.router( 77 | title: 'AWS Amplify Recipe App', 78 | theme: AppTheme.lightTheme(context), 79 | debugShowCheckedModeBanner: false, 80 | routerConfig: routerConfig, 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/models/Category.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 Category { 23 | SOUP, 24 | DESSERT, 25 | SALAD, 26 | APPETIZER, 27 | FISH, 28 | MAIN_COURSE 29 | } -------------------------------------------------------------------------------- /lib/models/Duration.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 Duration { 23 | MINUTE, 24 | HOUR 25 | } -------------------------------------------------------------------------------- /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' as amplify_core; 23 | import 'Recipe.dart'; 24 | 25 | export 'Category.dart'; 26 | export 'Duration.dart'; 27 | export 'Recipe.dart'; 28 | 29 | class ModelProvider implements amplify_core.ModelProviderInterface { 30 | @override 31 | String version = "7d742fe4b47906e294c936dce271bc37"; 32 | @override 33 | List modelSchemas = [Recipe.schema]; 34 | @override 35 | List customTypeSchemas = []; 36 | static final ModelProvider _instance = ModelProvider(); 37 | 38 | static ModelProvider get instance => _instance; 39 | 40 | amplify_core.ModelType getModelTypeByModelName(String modelName) { 41 | switch(modelName) { 42 | case "Recipe": 43 | return Recipe.classType; 44 | default: 45 | throw Exception("Failed to find model in model provider for model name: " + modelName); 46 | } 47 | } 48 | } 49 | 50 | 51 | class ModelFieldValue { 52 | const ModelFieldValue.value(this.value); 53 | 54 | final T value; 55 | } 56 | -------------------------------------------------------------------------------- /lib/shared/constants/constants.dart: -------------------------------------------------------------------------------- 1 | const double defaultPadding = 16.0; 2 | const double defaultBorderRadius = 14.0; 3 | -------------------------------------------------------------------------------- /lib/shared/constants/gaps.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const gapH8 = SizedBox(height: 8); 4 | const gapH16 = SizedBox(height: 16); 5 | const gapH20 = SizedBox(height: 20); 6 | const gapH24 = SizedBox(height: 24); 7 | 8 | const gapW4 = SizedBox(width: 4); 9 | const gapW8 = SizedBox(width: 8); 10 | const gapW16 = SizedBox(width: 16); 11 | const gapW20 = SizedBox(width: 20); 12 | const gapW24 = SizedBox(width: 24); 13 | -------------------------------------------------------------------------------- /lib/shared/data/authentication_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/model/user.dart'; 2 | 3 | abstract class AuthenticationRepository { 4 | late String name; 5 | 6 | late String fullName; 7 | 8 | late String email; 9 | 10 | User? currentUser; 11 | 12 | Future logInWithCredentials(String email, String password); 13 | 14 | Future logInWithGoogle(); 15 | 16 | Future logInWithFacebook(); 17 | 18 | Future signUp(String email, String password, String name); 19 | 20 | Future forgotPassword(String email); 21 | 22 | Future confirmPasswordReset( 23 | String email, 24 | String newPassword, 25 | String confirmationCode, 26 | ); 27 | 28 | Future signOut(); 29 | 30 | Future confirmUser(String email, String confirmationCode); 31 | 32 | Future isUserLoggedIn(); 33 | 34 | Future generateCurrentUserInformation(); 35 | } 36 | -------------------------------------------------------------------------------- /lib/shared/data/database/isar_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/model/notification.dart'; 2 | import 'package:amplify_recipe/shared/data/model/recipe.dart'; 3 | import 'package:amplify_recipe/shared/data/model/search_item.dart'; 4 | import 'package:isar/isar.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | 7 | class IsarProvider { 8 | IsarProvider._(); 9 | 10 | static late Isar isar; 11 | 12 | void close() { 13 | isar.close(); 14 | } 15 | 16 | static Future open() async { 17 | final dir = await getApplicationDocumentsDirectory(); 18 | isar = Isar.open( 19 | schemas: [ 20 | NotificationSchema, 21 | RecipeSchema, 22 | SearchItemSchema, 23 | ], 24 | directory: dir.path, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/shared/data/implementation/local_notification_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:amplify_flutter/amplify_flutter.dart'; 4 | import 'package:amplify_recipe/shared/data/database/isar_provider.dart'; 5 | import 'package:amplify_recipe/shared/data/model/notification.dart'; 6 | import 'package:amplify_recipe/shared/data/notification_repository.dart'; 7 | import 'package:isar/isar.dart'; 8 | import 'package:uuid/uuid.dart'; 9 | 10 | class LocalNotificationRepository extends NotificationRepository { 11 | LocalNotificationRepository() { 12 | isar = IsarProvider.isar; 13 | } 14 | 15 | late Isar isar; 16 | 17 | @override 18 | Future> fetchAllNotifications() { 19 | return isar.readAsync>((isar) { 20 | return isar.notifications.where().findAll(); 21 | }); 22 | } 23 | 24 | @override 25 | Stream listenUnseenNotifications() { 26 | return isar.read>((isar) { 27 | return isar.notifications 28 | .where() 29 | .isSeenEqualTo(false) 30 | .build() 31 | .watch(fireImmediately: true) 32 | .map((notifications) => notifications.isNotEmpty); 33 | }); 34 | } 35 | 36 | @override 37 | Future listenNotifications() async { 38 | Amplify.Notifications.Push.onNotificationReceivedInForeground.listen( 39 | (notification) { 40 | final pinpointBody = notification.data['pinpoint.jsonBody'] as String?; 41 | if (pinpointBody != null) { 42 | final recipeJson = json.decode(pinpointBody); 43 | final recipeTitle = recipeJson['recipeTitle'] as String; 44 | final recipeDescription = recipeJson['recipeDescription'] as String; 45 | final recipeId = recipeJson['recipeId'] as String; 46 | saveNotification( 47 | notification.title as String, 48 | notification.body as String, 49 | recipeId, 50 | recipeTitle, 51 | recipeDescription, 52 | null, 53 | ); 54 | } 55 | }, 56 | ); 57 | 58 | Amplify.Notifications.Push.onNotificationOpened.listen( 59 | (notification) { 60 | final pinpointBody = notification.data['pinpoint.jsonBody'] as String?; 61 | if (pinpointBody != null) { 62 | final recipeJson = json.decode(pinpointBody); 63 | final recipeTitle = recipeJson['recipeTitle'] as String; 64 | final recipeDescription = recipeJson['recipeDescription'] as String; 65 | final recipeId = recipeJson['recipeId'] as String; 66 | saveNotification( 67 | notification.title as String, 68 | notification.body as String, 69 | recipeId, 70 | recipeTitle, 71 | recipeDescription, 72 | null, 73 | ); 74 | } 75 | }, 76 | ); 77 | } 78 | 79 | @override 80 | Future markAllAsSeen() async { 81 | isar.write((isar) { 82 | isar.notifications 83 | .where() 84 | .isSeenEqualTo(false) 85 | .build() 86 | .findAll() 87 | .forEach((notification) { 88 | isar.notifications.update( 89 | id: notification.id, 90 | isSeen: true, 91 | ); 92 | }); 93 | }); 94 | } 95 | 96 | @override 97 | Future markAsSeen(String id) async { 98 | isar.write((isar) { 99 | isar.notifications.update(id: id, isSeen: true); 100 | }); 101 | } 102 | 103 | @override 104 | Future saveNotification( 105 | String title, 106 | String description, 107 | String recipeId, 108 | String recipeTitle, 109 | String recipeDescription, 110 | String? deepLink, 111 | ) async { 112 | isar.write((isar) { 113 | isar.notifications.put( 114 | Notification( 115 | id: const Uuid().v4(), 116 | title: title, 117 | description: description, 118 | recipeId: recipeId, 119 | recipeTitle: recipeTitle, 120 | recipeDescription: recipeDescription, 121 | deepLink: deepLink, 122 | isSeen: false, 123 | ), 124 | ); 125 | }); 126 | } 127 | 128 | @override 129 | Future handlePermissions() async { 130 | final status = await Amplify.Notifications.Push.getPermissionStatus(); 131 | if (status == PushNotificationPermissionStatus.granted) { 132 | // no further action is required, user has already granted permissions 133 | return; 134 | } 135 | if (status == PushNotificationPermissionStatus.denied) { 136 | return; 137 | } 138 | if (status == PushNotificationPermissionStatus.shouldRequest) { 139 | // go ahead and request permissions from the user 140 | await Amplify.Notifications.Push.requestPermissions(); 141 | } 142 | if (status == PushNotificationPermissionStatus.shouldExplainThenRequest) { 143 | // then request permissions 144 | await Amplify.Notifications.Push.requestPermissions(); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/shared/data/implementation/local_search_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/database/isar_provider.dart'; 2 | import 'package:amplify_recipe/shared/data/model/recipe.dart'; 3 | import 'package:amplify_recipe/shared/data/model/search_item.dart'; 4 | import 'package:amplify_recipe/shared/data/search_repository.dart'; 5 | import 'package:isar/isar.dart'; 6 | import 'package:uuid/uuid.dart'; 7 | 8 | class LocalSearchRepository extends SearchRepository { 9 | LocalSearchRepository() { 10 | isar = IsarProvider.isar; 11 | } 12 | 13 | late Isar isar; 14 | 15 | @override 16 | Future deleteAllSearchItems() async { 17 | isar.write((isar) => isar.searchItems.clear()); 18 | } 19 | 20 | @override 21 | Future saveSearchItem(String searchItem) async { 22 | isar.write((isar) { 23 | isar.searchItems.put( 24 | SearchItem( 25 | id: const Uuid().v4(), 26 | searchItem: searchItem, 27 | createdAt: DateTime.now(), 28 | ), 29 | ); 30 | }); 31 | } 32 | 33 | @override 34 | Future deleteSearchItemWithId(String id) async { 35 | isar.write((isar) { 36 | isar.searchItems.delete(id); 37 | }); 38 | } 39 | 40 | @override 41 | Future> getSearchItems() { 42 | return isar.readAsync((isar) => 43 | isar.searchItems.where().sortByCreatedAtDesc().findAll(limit: 3)); 44 | } 45 | 46 | @override 47 | Future> searchRecipes(String searchTerm) async { 48 | return isar.recipes 49 | .where() 50 | .titleContains(searchTerm, caseSensitive: false) 51 | .or() 52 | .descriptionContains(searchTerm, caseSensitive: false) 53 | .findAll(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/shared/data/implementation/s3_storage_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_flutter/amplify_flutter.dart'; 2 | import 'package:amplify_recipe/shared/data/storage_repository.dart'; 3 | 4 | class S3StorageRepository extends StorageRepository { 5 | @override 6 | Future generateDownloadUrl(String key) async { 7 | if (generatedImageLinksPerKey.containsKey(key)) { 8 | return generatedImageLinksPerKey[key]!; 9 | } 10 | final result = await Amplify.Storage.getUrl( 11 | key: key, 12 | options: const StorageGetUrlOptions( 13 | accessLevel: StorageAccessLevel.guest, 14 | ), 15 | ).result; 16 | final link = result.url.toString(); 17 | generatedImageLinksPerKey.putIfAbsent(key, () => link); 18 | return link; 19 | } 20 | 21 | @override 22 | Future uploadImage( 23 | String path, 24 | void Function(double progress) onProgressUpdate, 25 | ) async { 26 | final key = UUID.getUUID(); 27 | await Amplify.Storage.uploadFile( 28 | localFile: AWSFile.fromPath(path), 29 | key: '$key.jpg', 30 | options: const StorageUploadFileOptions( 31 | accessLevel: StorageAccessLevel.guest, 32 | ), 33 | onProgress: (progress) { 34 | onProgressUpdate(progress.fractionCompleted); 35 | }, 36 | ).result; 37 | return key; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/shared/data/model/notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | part 'notification.g.dart'; 4 | 5 | @collection 6 | class Notification { 7 | Notification({ 8 | required this.id, 9 | required this.title, 10 | required this.description, 11 | required this.recipeId, 12 | required this.recipeTitle, 13 | required this.recipeDescription, 14 | required this.isSeen, 15 | this.deepLink, 16 | }); 17 | 18 | @Id() 19 | final String id; 20 | final String title; 21 | final String description; 22 | final String recipeId; 23 | final String recipeTitle; 24 | final String recipeDescription; 25 | final String? deepLink; 26 | final bool isSeen; 27 | } 28 | -------------------------------------------------------------------------------- /lib/shared/data/model/recipe.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | part 'recipe.g.dart'; 4 | 5 | @collection 6 | class Recipe { 7 | Recipe({ 8 | required this.id, 9 | required this.title, 10 | required this.description, 11 | required this.serve, 12 | required this.duration, 13 | required this.category, 14 | required this.image, 15 | required this.isFavorited, 16 | required this.ingredients, 17 | required this.createdAt, 18 | this.isSynced = false, 19 | }); 20 | 21 | @Id() 22 | final String id; 23 | final String title; 24 | final String description; 25 | final int serve; 26 | final String duration; 27 | final String category; 28 | final String image; 29 | final bool isFavorited; 30 | final List ingredients; 31 | final DateTime createdAt; 32 | final bool isSynced; 33 | } 34 | -------------------------------------------------------------------------------- /lib/shared/data/model/search_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | part 'search_item.g.dart'; 4 | 5 | @collection 6 | class SearchItem { 7 | SearchItem({ 8 | required this.id, 9 | required this.searchItem, 10 | required this.createdAt, 11 | }); 12 | 13 | @Id() 14 | final String id; 15 | final String searchItem; 16 | final DateTime createdAt; 17 | } 18 | -------------------------------------------------------------------------------- /lib/shared/data/model/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | part 'user.g.dart'; 4 | 5 | @collection 6 | class User { 7 | User({ 8 | required this.id, 9 | required this.name, 10 | required this.email, 11 | this.profilePicture, 12 | }); 13 | 14 | @Id() 15 | final String id; 16 | final String name; 17 | final String email; 18 | final String? profilePicture; 19 | } 20 | -------------------------------------------------------------------------------- /lib/shared/data/notification_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/model/notification.dart'; 2 | 3 | abstract class NotificationRepository { 4 | /// Save notification to the local database 5 | Future saveNotification( 6 | String title, 7 | String description, 8 | String recipeId, 9 | String recipeTitle, 10 | String recipeDescription, 11 | String? deepLink, 12 | ); 13 | 14 | Future> fetchAllNotifications(); 15 | 16 | /// Mark all notifications as seen 17 | Future markAllAsSeen(); 18 | 19 | /// Mark the notification by id as seen 20 | Future markAsSeen(String id); 21 | 22 | /// Listens to all notifications 23 | Future listenNotifications(); 24 | 25 | /// Checks if there is any unseen notification 26 | Stream listenUnseenNotifications(); 27 | 28 | Future handlePermissions(); 29 | } 30 | -------------------------------------------------------------------------------- /lib/shared/data/recipe_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/model/recipe.dart'; 2 | 3 | abstract class RecipeRepository { 4 | Future toggleFavoriteForRecipe({ 5 | required String id, 6 | required bool isFavorited, 7 | }); 8 | 9 | Future deleteRecipe(String id); 10 | 11 | Future updateRecipe(Recipe recipe); 12 | 13 | Future getRecipe(String id); 14 | 15 | Future addRecipe( 16 | String title, 17 | String description, 18 | String duration, 19 | String durationUnit, 20 | String category, 21 | String serves, 22 | String imageKey, 23 | List<(String, String)> ingredients, 24 | ); 25 | 26 | Stream> listenRecipes(); 27 | 28 | Stream> listenLatestRecipes(); 29 | 30 | Stream> listenFavoritedRecipes(); 31 | } 32 | -------------------------------------------------------------------------------- /lib/shared/data/search_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/data/model/recipe.dart'; 2 | import 'package:amplify_recipe/shared/data/model/search_item.dart'; 3 | 4 | abstract class SearchRepository { 5 | Future> getSearchItems(); 6 | 7 | Future> searchRecipes(String searchTerm); 8 | 9 | Future deleteAllSearchItems(); 10 | 11 | Future saveSearchItem(String searchItem); 12 | 13 | Future deleteSearchItemWithId(String id); 14 | } 15 | -------------------------------------------------------------------------------- /lib/shared/data/storage_repository.dart: -------------------------------------------------------------------------------- 1 | abstract class StorageRepository { 2 | 3 | final generatedImageLinksPerKey = {}; 4 | 5 | Future uploadImage( 6 | String path, 7 | void Function(double progress) onProgressUpdate, 8 | ); 9 | 10 | Future generateDownloadUrl(String key); 11 | } 12 | -------------------------------------------------------------------------------- /lib/shared/navigation/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/features/authentication/screens/forgot_password_screen.dart'; 2 | import 'package:amplify_recipe/features/authentication/screens/onboarding_screen.dart'; 3 | import 'package:amplify_recipe/features/authentication/screens/register_screen.dart'; 4 | import 'package:amplify_recipe/features/authentication/screens/resend_email_screen.dart'; 5 | import 'package:amplify_recipe/features/authentication/screens/sign_in_screen.dart'; 6 | import 'package:amplify_recipe/features/authentication/widgets/password_confirmation_form.dart'; 7 | import 'package:amplify_recipe/features/authentication/widgets/user_confirmation_form.dart'; 8 | import 'package:amplify_recipe/features/details/screens/recipe_details_screen.dart'; 9 | import 'package:amplify_recipe/features/entry_point.dart'; 10 | import 'package:amplify_recipe/features/favorite/screens/favorite_screen.dart'; 11 | import 'package:amplify_recipe/features/notifications/screens/notifications_screen.dart'; 12 | import 'package:amplify_recipe/features/profile/screens/edit_profile_screen.dart'; 13 | import 'package:amplify_recipe/features/profile/screens/profile_screen.dart'; 14 | import 'package:amplify_recipe/features/recipes/screens/search/screen/all_recipes_screen.dart'; 15 | import 'package:amplify_recipe/features/recipes/screens/search/screen/search_screen.dart'; 16 | import 'package:go_router/go_router.dart'; 17 | 18 | final routerConfig = GoRouter( 19 | initialLocation: '/', 20 | routes: [ 21 | GoRoute( 22 | path: '/', 23 | builder: (context, state) => const OnboardingScreen(), 24 | ), 25 | GoRoute( 26 | path: '/entry-point', 27 | builder: (context, state) => const EntryPoint(), 28 | ), 29 | GoRoute( 30 | path: '/sign-in', 31 | builder: (context, state) => const SignInScreen(), 32 | ), 33 | GoRoute( 34 | path: '/register', 35 | builder: (context, state) => const RegisterScreen(), 36 | ), 37 | GoRoute( 38 | path: '/forgot-password', 39 | builder: (context, state) => const ForgotPasswordScreen(), 40 | ), 41 | GoRoute( 42 | path: '/password-confirmation/:email', 43 | builder: (context, state) { 44 | final email = state.pathParameters['email']; 45 | if (email == null) { 46 | throw Exception('Recipe ID is missing'); 47 | } 48 | return PasswordConfirmationForm(email: email); 49 | }, 50 | ), 51 | GoRoute( 52 | path: '/resend-email-verification', 53 | builder: (context, state) => const EmailResendScreen(), 54 | ), 55 | GoRoute( 56 | path: '/user-confirmation/:email', 57 | builder: (context, state) { 58 | final email = state.pathParameters['email']; 59 | if (email == null) { 60 | throw Exception('Recipe ID is missing'); 61 | } 62 | return UserConfirmationForm(email: email); 63 | }, 64 | ), 65 | GoRoute( 66 | path: '/favorite', 67 | builder: (context, state) => const FavoriteScreen(), 68 | ), 69 | GoRoute( 70 | path: '/recipe/:id', 71 | builder: (context, state) { 72 | final id = state.pathParameters['id']; 73 | if (id == null) { 74 | throw Exception('Recipe ID or Favorite state is missing'); 75 | } 76 | return RecipeDetailsScreen( 77 | id: id, 78 | ); 79 | }, 80 | ), 81 | GoRoute( 82 | path: '/profile', 83 | builder: (context, state) => const ProfileScreen(), 84 | ), 85 | GoRoute( 86 | path: '/edit-profile', 87 | builder: (context, state) => const EditProfileScreen(), 88 | ), 89 | GoRoute( 90 | path: '/all-recipes', 91 | builder: (context, state) => const AllRecipesScreen(), 92 | ), 93 | GoRoute( 94 | path: '/search-recipes', 95 | builder: (context, state) => const SearchScreen(), 96 | ), 97 | GoRoute( 98 | path: '/notifications', 99 | builder: (context, state) => const NotificationsScreen(), 100 | ), 101 | ], 102 | ); 103 | -------------------------------------------------------------------------------- /lib/shared/utils/form_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:form_field_validator/form_field_validator.dart'; 2 | 3 | class FormUtils { 4 | static final requireFieldValidator = 5 | RequiredValidator(errorText: 'This field is required'); 6 | static final emailValidator = MultiValidator( 7 | [ 8 | RequiredValidator(errorText: 'Email is required'), 9 | EmailValidator(errorText: 'Enter a valid email address'), 10 | ], 11 | ); 12 | static final passwordValidator = MultiValidator( 13 | [ 14 | RequiredValidator(errorText: 'Password is required'), 15 | MinLengthValidator(8, 16 | errorText: 'Password must be at least 8 digits long'), 17 | ], 18 | ); 19 | static MatchValidator matchPassValidator = 20 | MatchValidator(errorText: 'passwords do not match'); 21 | } 22 | -------------------------------------------------------------------------------- /lib/shared/widgets/async_image_loader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:amplify_recipe/main.dart'; 4 | import 'package:amplify_recipe/shared/constants/gaps.dart'; 5 | import 'package:amplify_recipe/shared/data/storage_repository.dart'; 6 | import 'package:amplify_recipe/themes/app_colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:go_router/go_router.dart'; 9 | import 'package:image_picker/image_picker.dart'; 10 | 11 | class AsyncImageLoader extends StatefulWidget { 12 | const AsyncImageLoader({ 13 | required this.keyOrUrl, 14 | this.onImageChanged, 15 | super.key, 16 | }); 17 | 18 | final String keyOrUrl; 19 | final ValueSetter? onImageChanged; 20 | 21 | @override 22 | State createState() => _AsyncImageLoaderState(); 23 | } 24 | 25 | class _AsyncImageLoaderState extends State { 26 | String? updatedImage; 27 | final imagePicker = ImagePicker(); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return WillPopScope( 32 | onWillPop: () { 33 | if (updatedImage != null) { 34 | showDialog( 35 | context: context, 36 | builder: (context) { 37 | return SimpleDialog( 38 | title: const Text('Updated Image'), 39 | children: [ 40 | Padding( 41 | padding: const EdgeInsets.all(8.0), 42 | child: Text( 43 | 'Do you want to update the image?', 44 | style: Theme.of(context).textTheme.titleMedium, 45 | ), 46 | ), 47 | gapH16, 48 | Padding( 49 | padding: const EdgeInsets.all(8.0), 50 | child: ElevatedButton( 51 | onPressed: () { 52 | widget.onImageChanged?.call(updatedImage); 53 | context.pop('/entry-point'); 54 | }, 55 | child: const Text('Yes'), 56 | ), 57 | ), 58 | Padding( 59 | padding: const EdgeInsets.all(8.0), 60 | child: ElevatedButton( 61 | onPressed: () { 62 | context.go('/entry-point'); 63 | }, 64 | child: const Text('No'), 65 | ), 66 | ), 67 | ], 68 | ); 69 | }, 70 | ); 71 | } 72 | return Future.value(true); 73 | }, 74 | child: GestureDetector( 75 | onTap: () async { 76 | final image = await imagePicker.pickImage( 77 | source: ImageSource.gallery, 78 | ); 79 | setState(() { 80 | updatedImage = image?.path; 81 | }); 82 | }, 83 | child: updatedImage != null 84 | ? Image.file( 85 | File(updatedImage!), 86 | fit: BoxFit.cover, 87 | ) 88 | : widget.keyOrUrl.startsWith('http') 89 | ? Image.network( 90 | widget.keyOrUrl, 91 | fit: BoxFit.cover, 92 | ) 93 | : FutureBuilder( 94 | future: getIt 95 | .get() 96 | .generateDownloadUrl(widget.keyOrUrl), 97 | builder: (context, snapshot) { 98 | if (snapshot.hasData) { 99 | return Image.network( 100 | snapshot.data!, 101 | fit: BoxFit.cover, 102 | ); 103 | } else if (snapshot.hasError) { 104 | return Center( 105 | child: Column( 106 | mainAxisAlignment: MainAxisAlignment.center, 107 | children: [ 108 | const Icon( 109 | Icons.error_outline, 110 | color: AppColors.error, 111 | ), 112 | gapH8, 113 | Text( 114 | 'Error loading image', 115 | style: Theme.of(context) 116 | .textTheme 117 | .bodyLarge! 118 | .copyWith( 119 | color: AppColors.error, 120 | ), 121 | ), 122 | ], 123 | ), 124 | ); 125 | } 126 | return const Center( 127 | child: CircularProgressIndicator(), 128 | ); 129 | }, 130 | ), 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/shared/widgets/frosted_glass_container.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../constants/constants.dart'; 6 | 7 | class FrostedGlassContainer extends StatelessWidget { 8 | const FrostedGlassContainer({ 9 | super.key, 10 | this.size = const Size(40, 40), 11 | required this.child, 12 | this.borderRadius = defaultBorderRadius / 2, 13 | }); 14 | 15 | final Size size; 16 | final Widget child; 17 | final double borderRadius; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ClipRRect( 22 | borderRadius: BorderRadius.circular(borderRadius), 23 | child: BackdropFilter( 24 | filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), 25 | child: Container( 26 | height: size.height, 27 | width: size.width, 28 | decoration: BoxDecoration( 29 | color: Colors.white.withOpacity(0.2), 30 | borderRadius: BorderRadius.circular(borderRadius), 31 | ), 32 | child: child, 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/shared/widgets/recipe_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:amplify_recipe/shared/widgets/async_image_loader.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../themes/app_colors.dart'; 5 | import '../constants/constants.dart'; 6 | import 'frosted_glass_container.dart'; 7 | 8 | class RecipeCard extends StatelessWidget { 9 | const RecipeCard({ 10 | super.key, 11 | required this.title, 12 | required this.image, 13 | required this.category, 14 | required this.duration, 15 | this.serve = 1, 16 | this.isFavorited = false, 17 | required this.press, 18 | required this.onBookmarked, 19 | this.onDismissed, 20 | }); 21 | 22 | final String title, image, category, duration; 23 | final int serve; 24 | final bool isFavorited; 25 | final VoidCallback press; 26 | final VoidCallback onBookmarked; 27 | final DismissDirectionCallback? onDismissed; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return GestureDetector( 32 | onTap: press, 33 | child: Container( 34 | height: 200, 35 | width: double.infinity, 36 | clipBehavior: Clip.hardEdge, 37 | decoration: BoxDecoration( 38 | borderRadius: BorderRadius.circular(defaultBorderRadius), 39 | ), 40 | child: Dismissible( 41 | key: ValueKey(title), 42 | background: Container( 43 | padding: const EdgeInsets.only( 44 | right: 16, 45 | ), 46 | color: Theme.of(context).colorScheme.error, 47 | alignment: Alignment.centerRight, 48 | child: const Icon( 49 | Icons.delete, 50 | color: Colors.white, 51 | ), 52 | ), 53 | onDismissed: onDismissed, 54 | child: Stack( 55 | fit: StackFit.expand, 56 | children: [ 57 | AsyncImageLoader(keyOrUrl: image), 58 | Container( 59 | decoration: const BoxDecoration( 60 | gradient: LinearGradient( 61 | begin: Alignment.topCenter, 62 | end: Alignment.bottomCenter, 63 | colors: [ 64 | Colors.transparent, 65 | Colors.black54, 66 | ], 67 | ), 68 | ), 69 | ), 70 | Positioned( 71 | left: 0, 72 | right: 0, 73 | bottom: 0, 74 | child: ListTile( 75 | title: Text( 76 | title, 77 | style: Theme.of(context) 78 | .textTheme 79 | .titleLarge! 80 | .copyWith(color: Colors.white), 81 | ), 82 | subtitle: Text( 83 | '$duration | $serve Serve', 84 | style: const TextStyle(color: Colors.white), 85 | ), 86 | trailing: GestureDetector( 87 | onTap: onBookmarked, 88 | child: FrostedGlassContainer( 89 | child: Icon( 90 | Icons.bookmark_border, 91 | color: isFavorited ? AppColors.success : Colors.white, 92 | ), 93 | ), 94 | ), 95 | ), 96 | ), 97 | Positioned( 98 | top: 16, 99 | left: 16, 100 | height: 32, 101 | width: 72, 102 | child: FrostedGlassContainer( 103 | borderRadius: 20, 104 | child: Center( 105 | child: Text( 106 | category, 107 | style: const TextStyle(color: Colors.white), 108 | ), 109 | ), 110 | ), 111 | ) 112 | ], 113 | ), 114 | ), 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/shared/widgets/section_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../themes/app_colors.dart'; 4 | 5 | class SectionListTile extends StatelessWidget { 6 | const SectionListTile({ 7 | super.key, 8 | required this.title, 9 | this.trailingText = 'See all', 10 | required this.press, 11 | }); 12 | 13 | final String title, trailingText; 14 | final VoidCallback press; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Row( 19 | children: [ 20 | Text( 21 | title, 22 | style: Theme.of(context) 23 | .textTheme 24 | .titleMedium! 25 | .copyWith(fontWeight: FontWeight.w600), 26 | ), 27 | const Spacer(), 28 | TextButton( 29 | onPressed: press, 30 | style: TextButton.styleFrom(foregroundColor: AppColors.bodyText), 31 | child: Text(trailingText), 32 | ) 33 | ], 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/themes/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | static const Color primary = Color(0xFF22A45D); 5 | static const Color text = Color(0xFF010F07); 6 | static const Color bodyText = Color(0xFF757575); 7 | static const Color inputfieldBg = Color(0xFFF6F8FC); 8 | static const Color success = Color(0xFF43B726); 9 | static const Color error = Color(0xFFE23D24); 10 | } 11 | -------------------------------------------------------------------------------- /lib/themes/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | 5 | import '../shared/constants/constants.dart'; 6 | import 'app_colors.dart'; 7 | 8 | class AppTheme { 9 | static ThemeData lightTheme(BuildContext context) { 10 | return ThemeData( 11 | primaryColor: AppColors.primary, 12 | scaffoldBackgroundColor: Colors.white, 13 | textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme) 14 | .copyWith(bodyMedium: const TextStyle(color: AppColors.bodyText)), 15 | appBarTheme: AppBarTheme( 16 | backgroundColor: Colors.white, 17 | elevation: 0, 18 | systemOverlayStyle: SystemUiOverlayStyle.dark, 19 | foregroundColor: AppColors.text, 20 | titleTextStyle: Theme.of(context).textTheme.titleLarge!.copyWith( 21 | fontWeight: FontWeight.normal, 22 | ), 23 | ), 24 | floatingActionButtonTheme: const FloatingActionButtonThemeData( 25 | backgroundColor: AppColors.primary, 26 | ), 27 | elevatedButtonTheme: ElevatedButtonThemeData( 28 | style: ElevatedButton.styleFrom( 29 | backgroundColor: AppColors.primary, 30 | minimumSize: const Size(double.infinity, 48), 31 | shape: RoundedRectangleBorder( 32 | borderRadius: BorderRadius.circular(defaultBorderRadius), 33 | ), 34 | ), 35 | ), 36 | outlinedButtonTheme: OutlinedButtonThemeData( 37 | style: OutlinedButton.styleFrom( 38 | minimumSize: const Size(double.infinity, 48), 39 | foregroundColor: AppColors.text, 40 | shape: RoundedRectangleBorder( 41 | borderRadius: BorderRadius.circular(defaultBorderRadius), 42 | ), 43 | side: const BorderSide(color: AppColors.bodyText), 44 | ), 45 | ), 46 | inputDecorationTheme: const InputDecorationTheme( 47 | filled: true, 48 | fillColor: AppColors.inputfieldBg, 49 | border: defaultOutlineInputBorder, 50 | enabledBorder: defaultOutlineInputBorder, 51 | focusedBorder: defaultOutlineInputBorder, 52 | ), 53 | ); 54 | } 55 | } 56 | 57 | const defaultOutlineInputBorder = OutlineInputBorder( 58 | borderRadius: BorderRadius.all(Radius.circular(defaultBorderRadius)), 59 | borderSide: BorderSide.none, 60 | ); 61 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: amplify_recipe 2 | description: A new Flutter project. 3 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ">=3.0.0 <4.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | cupertino_icons: ^1.0.5 15 | flutter_svg: ^2.0.5 16 | google_fonts: ^4.0.4 17 | form_field_validator: ^1.1.0 18 | intl: ^0.18.1 19 | image_picker: ^1.0.1 20 | get_it: ^7.6.0 21 | isar: ^4.0.0-0 22 | isar_flutter_libs: ^4.0.0-0 23 | path_provider: ^2.0.15 24 | go_router: ^9.1.0 25 | uuid: ^3.0.7 26 | amplify_flutter: ^1.3.1 27 | amplify_auth_cognito: ^1.3.1 28 | amplify_api: ^1.3.2 29 | amplify_storage_s3: ^1.3.2 30 | amplify_push_notifications_pinpoint: ^1.3.1 31 | 32 | dev_dependencies: 33 | flutter_lints: ^2.0.1 34 | build_runner: ^2.4.6 35 | flutter: 36 | uses-material-design: true 37 | 38 | assets: 39 | - assets/icons/ 40 | - assets/images/ --------------------------------------------------------------------------------