├── lib ├── helpers │ ├── validator.dart │ └── helpers.dart ├── store │ ├── store.dart │ └── shared_preference_helper.dart ├── models │ ├── models.dart │ ├── menu_option_model.dart │ └── user_model.dart ├── ui │ ├── ui.dart │ ├── auth │ │ ├── auth.dart │ │ ├── update_profile_ui.dart │ │ ├── sign_up_ui.dart │ │ └── sign_in_ui.dart │ ├── components │ │ ├── form_vertical_spacing.dart │ │ ├── components.dart │ │ ├── label_button.dart │ │ ├── primary_button.dart │ │ ├── avatar.dart │ │ ├── logo_graphic_header.dart │ │ ├── segmented_selector.dart │ │ ├── form_input_field_with_icon.dart │ │ ├── loading_screen.dart │ │ └── sliding_segmented_control.dart │ ├── home_ui.dart │ └── settings_ui.dart ├── constants │ ├── constants.dart │ ├── routes.dart │ └── app_themes.dart ├── services │ ├── services.dart │ ├── auth_widget_builder.dart │ ├── theme_provider.dart │ └── auth_service.dart └── main.dart ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── project.pbxproj ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist └── .gitignore ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── manifest.json └── index.html ├── assets └── images │ ├── s1.png │ ├── s2.png │ ├── s3.png │ ├── s4.png │ ├── default.png │ ├── defaultDark.png │ ├── defaultold.png │ └── default.svg ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── waterproject_v3 │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── .metadata ├── .vscode └── launch.json ├── README.md ├── pubspec.yaml ├── .gitignore ├── test └── widget_test.dart └── pubspec.lock /lib/helpers/validator.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/helpers/helpers.dart: -------------------------------------------------------------------------------- 1 | export 'validator.dart'; 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /lib/store/store.dart: -------------------------------------------------------------------------------- 1 | export 'shared_preference_helper.dart'; 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/models/models.dart: -------------------------------------------------------------------------------- 1 | export 'user_model.dart'; 2 | export 'menu_option_model.dart'; 3 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/web/favicon.png -------------------------------------------------------------------------------- /lib/ui/ui.dart: -------------------------------------------------------------------------------- 1 | export 'home_ui.dart'; 2 | export 'settings_ui.dart'; 3 | export 'auth/auth.dart'; 4 | -------------------------------------------------------------------------------- /assets/images/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/s1.png -------------------------------------------------------------------------------- /assets/images/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/s2.png -------------------------------------------------------------------------------- /assets/images/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/s3.png -------------------------------------------------------------------------------- /assets/images/s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/s4.png -------------------------------------------------------------------------------- /lib/constants/constants.dart: -------------------------------------------------------------------------------- 1 | export 'routes.dart'; 2 | //export 'globals.dart'; 3 | export 'app_themes.dart'; 4 | -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/default.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/images/defaultDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/defaultDark.png -------------------------------------------------------------------------------- /assets/images/defaultold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/assets/images/defaultold.png -------------------------------------------------------------------------------- /lib/services/services.dart: -------------------------------------------------------------------------------- 1 | export 'theme_provider.dart'; 2 | export 'auth_service.dart'; 3 | export 'auth_widget_builder.dart'; 4 | -------------------------------------------------------------------------------- /lib/ui/auth/auth.dart: -------------------------------------------------------------------------------- 1 | export 'sign_in_ui.dart'; 2 | export 'sign_up_ui.dart'; 3 | //export 'reset_password_ui.dart'; 4 | export 'update_profile_ui.dart'; 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CRYPTOcoderAS/AuthApp-Flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/ui/components/form_vertical_spacing.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FormVerticalSpace extends SizedBox { 4 | FormVerticalSpace({double height = 24.0}) : super(height: height); 5 | } 6 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/waterproject_v3/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.waterproject_v3 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /lib/models/menu_option_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Model class to hold menu option data (language and theme) 4 | class MenuOptionsModel { 5 | String key; 6 | String value; 7 | IconData icon; 8 | 9 | MenuOptionsModel({this.key, this.value, this.icon}); 10 | } 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/ui/components/components.dart: -------------------------------------------------------------------------------- 1 | export 'loading_screen.dart'; 2 | export 'avatar.dart'; 3 | export 'logo_graphic_header.dart'; 4 | export 'form_vertical_spacing.dart'; 5 | export 'sliding_segmented_control.dart'; 6 | export 'segmented_selector.dart'; 7 | export 'form_input_field_with_icon.dart'; 8 | export 'primary_button.dart'; 9 | export 'label_button.dart'; 10 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "authapp", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /lib/ui/components/label_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LabelButton extends StatelessWidget { 4 | LabelButton({this.labelText, this.onPressed}); 5 | final String labelText; 6 | final void Function() onPressed; 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return FlatButton( 11 | child: Text( 12 | labelText, 13 | ), 14 | onPressed: onPressed, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/models/user_model.dart: -------------------------------------------------------------------------------- 1 | class UserModel { 2 | String uid; 3 | String email; 4 | String name; 5 | String photoUrl; 6 | 7 | UserModel({this.uid, this.email, this.name, this.photoUrl}); 8 | 9 | factory UserModel.fromMap(Map data) { 10 | return UserModel( 11 | uid: data['uid'], 12 | email: data['email'] ?? '', 13 | name: data['name'] ?? '', 14 | photoUrl: data['photoUrl'] ?? '', 15 | ); 16 | } 17 | 18 | Map toJson() => 19 | {"uid": uid, "email": email, "name": name, "photoUrl": photoUrl}; 20 | } 21 | -------------------------------------------------------------------------------- /lib/ui/components/primary_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PrimaryButton extends StatelessWidget { 4 | PrimaryButton({this.labelText, this.onPressed}); 5 | 6 | final String labelText; 7 | final void Function() onPressed; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return RaisedButton( 12 | onPressed: onPressed, 13 | padding: EdgeInsets.all(22), 14 | child: Text( 15 | labelText.toUpperCase(), 16 | style: TextStyle(fontWeight: FontWeight.bold), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | 6 | # AUTHENTICATION APP 7 | 8 | A new Flutter project.
9 | Firebase with light and dark theme
10 | 11 | ## APP PREVIEW 12 |

13 | accessibility text 14 | accessibility text 15 | accessibility text 16 | accessibility text 17 |

18 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: authapp 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | 6 | environment: 7 | sdk: ">=2.7.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | cupertino_icons: ^1.0.2 13 | provider: ^5.0.0 14 | shared_preferences: ^2.0.5 15 | firebase_core: ^1.0.2 16 | firebase_database: ^6.1.1 17 | firebase_auth: ^1.0.1 18 | firebase_analytics: ^7.1.1 19 | cloud_firestore: ^1.0.3 20 | simple_gravatar: ^1.0.5 21 | 22 | 23 | 24 | dev_dependencies: 25 | flutter_test: 26 | sdk: flutter 27 | 28 | flutter: 29 | uses-material-design: true 30 | assets: 31 | - assets/images/default.png 32 | - assets/images/defaultDark.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waterproject_v3", 3 | "short_name": "waterproject_v3", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.5' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 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 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/constants/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:authapp/ui/ui.dart'; 3 | import 'package:authapp/ui/auth/auth.dart'; 4 | 5 | class Routes { 6 | Routes._(); 7 | static const String signin = '/signin'; 8 | static const String signup = '/signup'; 9 | static const String home = '/home'; 10 | static const String settings = '/settings'; 11 | static const String resetPassword = '/reset-password'; 12 | static const String updateProfile = '/update-profile'; 13 | 14 | static final routes = { 15 | home: (BuildContext context) => HomeUI(), 16 | signin: (BuildContext context) => SignInUI(), 17 | signup: (BuildContext context) => SignUpUI(), 18 | settings: (BuildContext context) => SettingsUI(), 19 | //resetPassword: (BuildContext context) => ResetPasswordUI(), 20 | //updateProfile: (BuildContext context) => UpdateProfileUI(), 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /lib/ui/components/avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:authapp/models/models.dart'; 3 | import 'package:authapp/ui/components/components.dart'; 4 | 5 | class Avatar extends StatelessWidget { 6 | Avatar( 7 | this.user, 8 | ); 9 | final UserModel user; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | if ((user?.photoUrl == '') || (user?.photoUrl == null)) { 14 | return LogoGraphicHeader(); 15 | } 16 | return Hero( 17 | tag: 'User Avatar Image', 18 | child: CircleAvatar( 19 | foregroundColor: Colors.blue, 20 | backgroundColor: Colors.white, 21 | radius: 70.0, 22 | child: ClipOval( 23 | child: Image.network( 24 | user?.photoUrl, 25 | fit: BoxFit.cover, 26 | width: 120.0, 27 | height: 120.0, 28 | ), 29 | )), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/images/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combined Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/ui/components/logo_graphic_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | //import 'package:provider/provider.dart'; 3 | //import 'package:flutter_starter/services/services.dart'; 4 | 5 | class LogoGraphicHeader extends StatelessWidget { 6 | LogoGraphicHeader(); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | //final themeProvider = Provider.of(context); 11 | String _imageLogo = 'assets/images/default.png'; 12 | //if (themeProvider.isDarkModeOn == true) { 13 | // _imageLogo = 'assets/images/defaultDark.png'; 14 | //} 15 | return Hero( 16 | tag: 'App Logo', 17 | child: CircleAvatar( 18 | foregroundColor: Colors.blue, 19 | backgroundColor: Colors.transparent, 20 | radius: 60.0, 21 | child: ClipOval( 22 | child: Image.asset( 23 | _imageLogo, 24 | fit: BoxFit.cover, 25 | width: 120.0, 26 | height: 120.0, 27 | ), 28 | )), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/ui/components/segmented_selector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:authapp/ui/components/sliding_segmented_control.dart'; 3 | 4 | class SegmentedSelector extends StatelessWidget { 5 | SegmentedSelector( 6 | {this.menuOptions, this.selectedOption, this.onValueChanged}); 7 | 8 | final List menuOptions; 9 | final String selectedOption; 10 | final void Function(dynamic) onValueChanged; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | //if (Platform.isIOS) {} 15 | 16 | return CupertinoSlidingSegmentedControl( 17 | groupValue: selectedOption, 18 | children: Map.fromIterable( 19 | menuOptions, 20 | key: (option) => option.key, 21 | value: (option) => Row( 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | children: [ 24 | Icon(option.icon), 25 | SizedBox(width: 6), 26 | Text(option.value), 27 | ], 28 | ), 29 | ), 30 | onValueChanged: onValueChanged); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:authapp/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "872406809407", 4 | "project_id": "flutter01-383a1", 5 | "storage_bucket": "flutter01-383a1.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:872406809407:android:f8d6d58d7f20f1551f718a", 11 | "android_client_info": { 12 | "package_name": "com.example.authapp" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "872406809407-oesu2uiqtfgcciokba05a6edgej5m9j2.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyCRYxaK3VbFJY5v6oNdR7B66tL9CsPiRi4" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "872406809407-oesu2uiqtfgcciokba05a6edgej5m9j2.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /lib/services/auth_widget_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:authapp/models/models.dart'; 5 | import 'package:authapp/services/services.dart'; 6 | 7 | class AuthWidgetBuilder extends StatelessWidget { 8 | const AuthWidgetBuilder({Key key, @required this.builder}) : super(key: key); 9 | final Widget Function(BuildContext, AsyncSnapshot) builder; 10 | @override 11 | Widget build(BuildContext context) { 12 | final authService = Provider.of(context, listen: false); 13 | return StreamBuilder( 14 | stream: authService.userStatus, 15 | builder: (BuildContext context, AsyncSnapshot snapshot) { 16 | final User user = snapshot.data; 17 | if (user != null) { 18 | MultiProvider( 19 | providers: [ 20 | Provider.value(value: user), 21 | StreamProvider.value( 22 | value: AuthService().streamFirestoreUser(user), 23 | initialData: null) 24 | ], 25 | ); 26 | } 27 | return builder(context, snapshot); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/services/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:authapp/store/store.dart'; 3 | 4 | class ThemeProvider extends ChangeNotifier { 5 | SharedPreferenceHelper _sharedPrefsHelper; 6 | String _currentTheme = "system"; 7 | 8 | ThemeProvider() { 9 | _sharedPrefsHelper = SharedPreferenceHelper(); 10 | } 11 | 12 | // gets currentTheme stored in shared preferences 13 | String get getTheme { 14 | _sharedPrefsHelper.getCurrentTheme.then((theme) { 15 | _currentTheme = theme; 16 | }); 17 | return _currentTheme; 18 | } 19 | 20 | // checks whether darkmode is set via system or previously by user 21 | bool get isDarkModeOn { 22 | if (getTheme == 'system') { 23 | if (WidgetsBinding.instance.window.platformBrightness == 24 | Brightness.dark) { 25 | return true; 26 | } 27 | } 28 | if (getTheme == 'dark') { 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | // updates selected theme into sharepreferences 35 | // and notifies ui to update via provider 36 | void updateTheme(String theme) { 37 | _sharedPrefsHelper.changeTheme(theme); 38 | _sharedPrefsHelper.getCurrentTheme.then((theme) { 39 | _currentTheme = theme; 40 | }); 41 | notifyListeners(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/ui/components/form_input_field_with_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FormInputFieldWithIcon extends StatelessWidget { 4 | FormInputFieldWithIcon( 5 | {this.controller, 6 | this.iconPrefix, 7 | this.labelText, 8 | this.validator, 9 | this.keyboardType = TextInputType.text, 10 | this.obscureText = false, 11 | this.minLines = 1, 12 | this.maxLines, 13 | this.onChanged, 14 | this.onSaved}); 15 | 16 | final TextEditingController controller; 17 | final IconData iconPrefix; 18 | final String labelText; 19 | final String Function(String) validator; 20 | final TextInputType keyboardType; 21 | final bool obscureText; 22 | final int minLines; 23 | final int maxLines; 24 | final void Function(String) onChanged; 25 | final void Function(String) onSaved; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return TextFormField( 30 | decoration: InputDecoration( 31 | filled: true, 32 | prefixIcon: Icon(iconPrefix), 33 | labelText: labelText, 34 | ), 35 | controller: controller, 36 | onSaved: onSaved, 37 | onChanged: onChanged, 38 | keyboardType: keyboardType, 39 | obscureText: obscureText, 40 | maxLines: maxLines, 41 | minLines: minLines, 42 | validator: validator, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/ui/home_ui.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:authapp/models/user_model.dart'; 6 | import 'package:authapp/ui/components/components.dart'; 7 | import 'package:authapp/services/services.dart'; 8 | 9 | class HomeUI extends StatefulWidget { 10 | @override 11 | _HomeUIState createState() => _HomeUIState(); 12 | } 13 | 14 | class _HomeUIState extends State { 15 | bool _loading = false; 16 | @override 17 | void initState() { 18 | super.initState(); 19 | } 20 | 21 | @override 22 | void dispose() { 23 | super.dispose(); 24 | } 25 | 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: Text('Todo List'), 30 | actions: [ 31 | IconButton( 32 | icon: Icon(Icons.settings), 33 | onPressed: () { 34 | Navigator.of(context).pushNamed('/settings'); 35 | }) 36 | ], 37 | ), 38 | body: LoadingScreen( 39 | child: Center( 40 | child: Column( 41 | children: [ 42 | SizedBox(height: 120), 43 | //Avatar(user), 44 | ], 45 | ), 46 | ), 47 | inAsyncCall: _loading, 48 | color: Theme.of(context).scaffoldBackgroundColor, 49 | ), 50 | ); 51 | } 52 | 53 | //_isUserAdmin() async {} 54 | } 55 | -------------------------------------------------------------------------------- /lib/store/shared_preference_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class SharedPreferenceHelper { 4 | Future _sharedPreference; 5 | static const String current_language = ""; 6 | static const String current_theme = "system"; 7 | 8 | SharedPreferenceHelper() { 9 | _sharedPreference = SharedPreferences.getInstance(); 10 | } 11 | 12 | //Theme 13 | 14 | //Sets the theme to a new value and stores in sharedpreferences 15 | Future changeTheme(String value) { 16 | return _sharedPreference.then((prefs) { 17 | return prefs.setString(current_theme, value); 18 | }); 19 | } 20 | 21 | //gets the current theme stored in sharedpreferences. 22 | //If no theme returns 'system' 23 | Future get getCurrentTheme { 24 | return _sharedPreference.then((prefs) { 25 | String currentTheme = prefs.getString(current_theme) ?? 'system'; 26 | return currentTheme; 27 | }); 28 | } 29 | 30 | //Language 31 | 32 | //Sets the language to a new value and stores in sharedpreferences 33 | Future changeLanguage(String value) { 34 | return _sharedPreference.then((prefs) { 35 | return prefs.setString(current_language, value); 36 | }); 37 | } 38 | 39 | //gets the current language stored in sharedpreferences. 40 | Future get appCurrentLanguage { 41 | return _sharedPreference.then((prefs) { 42 | return prefs.getString(current_language); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | waterproject_v3 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | waterproject_v3 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 43 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | //import 'package:firebase_analytics/firebase_analytics.dart'; 5 | //import 'package:firebase_analytics/observer.dart'; 6 | import 'package:firebase_auth/firebase_auth.dart'; 7 | import 'package:authapp/models/user_model.dart'; 8 | import 'package:authapp/ui/auth/auth.dart'; 9 | import 'package:authapp/ui/ui.dart'; 10 | import 'package:authapp/services/services.dart'; 11 | import 'package:authapp/constants/constants.dart'; 12 | 13 | void main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | await Firebase.initializeApp(); 16 | runApp(MultiProvider( 17 | providers: [ 18 | ChangeNotifierProvider( 19 | create: (context) => ThemeProvider(), 20 | ), 21 | ChangeNotifierProvider( 22 | create: (context) => AuthService(), 23 | ), 24 | ], 25 | child: MyApp(), 26 | )); 27 | } 28 | 29 | class MyApp extends StatelessWidget { 30 | const MyApp({Key key}) : super(key: key); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Consumer( 35 | builder: (_, themeProviderRef, __) { 36 | return AuthWidgetBuilder( 37 | builder: (BuildContext context, AsyncSnapshot userSnapshot) { 38 | return MaterialApp( 39 | debugShowCheckedModeBanner: false, 40 | routes: Routes.routes, 41 | theme: AppThemes.lightTheme, 42 | darkTheme: AppThemes.darkTheme, 43 | themeMode: themeProviderRef.isDarkModeOn 44 | ? ThemeMode.dark 45 | : ThemeMode.light, 46 | home: (userSnapshot?.data?.uid != null) ? HomeUI() : SignInUI(), 47 | ); 48 | }, 49 | ); 50 | }, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | apply plugin: 'com.google.gms.google-services' 28 | 29 | android { 30 | compileSdkVersion 30 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId "com.example.authapp" 39 | minSdkVersion 21 40 | targetSdkVersion 29 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | } 44 | 45 | buildTypes { 46 | release { 47 | // TODO: Add your own signing config for the release build. 48 | // Signing with the debug keys for now, so `flutter run --release` works. 49 | signingConfig signingConfigs.debug 50 | } 51 | } 52 | } 53 | 54 | flutter { 55 | source '../..' 56 | } 57 | 58 | dependencies { 59 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 60 | implementation 'com.google.firebase:firebase-analytics' 61 | } 62 | -------------------------------------------------------------------------------- /lib/ui/components/loading_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingScreen extends StatelessWidget { 4 | final bool inAsyncCall; 5 | final double opacity; 6 | final Color color; 7 | final Widget progressIndicator; 8 | final Offset offset; 9 | final bool dismissible; 10 | final Widget child; 11 | 12 | LoadingScreen({ 13 | Key key, 14 | @required this.inAsyncCall, 15 | this.opacity = 0.7, 16 | this.color = Colors.white, 17 | this.progressIndicator = const CircularProgressIndicator(), 18 | this.offset, 19 | this.dismissible = false, 20 | @required this.child, 21 | }) : assert(child != null), 22 | assert(inAsyncCall != null), 23 | super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | List widgetList = []; 28 | widgetList.add(child); 29 | if (inAsyncCall) { 30 | Widget layOutProgressIndicator; 31 | if (offset == null) { 32 | layOutProgressIndicator = Center( 33 | child: Container( 34 | height: 60, 35 | width: 60, 36 | decoration: BoxDecoration( 37 | shape: BoxShape.circle, 38 | ), 39 | child: Align( 40 | alignment: Alignment.center, 41 | child: SizedBox( 42 | child: CircularProgressIndicator(), 43 | height: 30.0, 44 | width: 30.0, 45 | ), 46 | ))); 47 | } else { 48 | layOutProgressIndicator = Positioned( 49 | child: progressIndicator, 50 | left: offset.dx, 51 | top: offset.dy, 52 | ); 53 | } 54 | final modal = [ 55 | new Opacity( 56 | child: new ModalBarrier(dismissible: dismissible, color: color), 57 | opacity: opacity, 58 | ), 59 | layOutProgressIndicator 60 | ]; 61 | widgetList += modal; 62 | } 63 | return new Stack( 64 | children: widgetList, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/services/auth_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | import 'package:authapp/models/models.dart'; 6 | import 'package:simple_gravatar/simple_gravatar.dart'; 7 | 8 | class AuthService extends ChangeNotifier { 9 | final FirebaseAuth _auth = FirebaseAuth.instance; 10 | final FirebaseFirestore _db = FirebaseFirestore.instance; 11 | 12 | //Future get getUser => _auth.authStateChanges().listen((User user) {if (user == null)}); 13 | 14 | Stream get userStatus => _auth.authStateChanges(); 15 | 16 | Stream streamFirestoreUser(User firebaseUser) { 17 | if (firebaseUser?.uid == null) {} 18 | return null; 19 | } 20 | 21 | Future createUserWithEmailAndPassword( 22 | String email, String password) async { 23 | try { 24 | await _auth 25 | .createUserWithEmailAndPassword(email: email, password: password) 26 | .then((result) async { 27 | print('uID: ' + result.user.uid); 28 | print('email: ' + result.user.email); 29 | Gravatar gravatar = Gravatar(email); 30 | String gravatarUrl = gravatar.imageUrl( 31 | size: 200, 32 | defaultImage: GravatarImage.retro, 33 | rating: GravatarRating.pg, 34 | fileExtension: true, 35 | ); 36 | UserModel _newUser = UserModel( 37 | uid: result.user.uid, 38 | email: result.user.email, 39 | name: '', 40 | photoUrl: gravatarUrl); 41 | //_updateUserFirestore(_newUser, result.user); 42 | }); 43 | return true; 44 | } catch (e) { 45 | return false; 46 | } 47 | } 48 | 49 | Future signInWithEmailAndPassword(String email, String password) async { 50 | try { 51 | await _auth.signInWithEmailAndPassword(email: email, password: password); 52 | return true; 53 | } catch (e) { 54 | return false; 55 | } 56 | } 57 | 58 | Future signOut() { 59 | _auth.signOut(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /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/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/ui/settings_ui.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:authapp/models/models.dart'; 6 | import 'package:authapp/ui/components/components.dart'; 7 | import 'package:authapp/services/services.dart'; 8 | 9 | class SettingsUI extends StatelessWidget { 10 | String systemValue = "system"; 11 | String lightValue = "light"; 12 | String darkValue = "dark"; 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: Text("App Settings"), 18 | ), 19 | body: _buildLayoutSection(context), 20 | ); 21 | } 22 | 23 | Widget _buildLayoutSection(BuildContext context) { 24 | final List themeOptions = [ 25 | MenuOptionsModel( 26 | key: "system", value: systemValue, icon: Icons.brightness_4), 27 | MenuOptionsModel( 28 | key: "light", value: lightValue, icon: Icons.brightness_low), 29 | MenuOptionsModel(key: "dark", value: darkValue, icon: Icons.brightness_3) 30 | ]; 31 | return ListView( 32 | children: [ 33 | ListTile( 34 | title: Text("Theme"), 35 | trailing: SegmentedSelector( 36 | selectedOption: Provider.of(context).getTheme, 37 | menuOptions: themeOptions, 38 | onValueChanged: (value) { 39 | Provider.of(context, listen: false) 40 | .updateTheme(value); 41 | }, 42 | ), 43 | ), 44 | ListTile( 45 | title: Text("Update Profile"), 46 | trailing: RaisedButton( 47 | onPressed: () async { 48 | Navigator.of(context).pushNamed('/update-profile'); 49 | }, 50 | child: Text( 51 | "Update", 52 | ), 53 | )), 54 | ListTile( 55 | title: Text("Sign Out"), 56 | trailing: RaisedButton( 57 | onPressed: () { 58 | AuthService _auth = AuthService(); 59 | _auth.signOut(); 60 | Navigator.of(context).pushNamed('/signin'); 61 | //Navigator.pushReplacementNamed(context, '/signin'); 62 | }, 63 | child: Text("Sign Out"), 64 | )) 65 | 66 | /* ListTile( 67 | title: Text(labels.settings.theme), 68 | trailing: DropdownPickerWithIcon( 69 | menuOptions: themeOptions, 70 | selectedOption: Provider.of(context).getTheme, 71 | onChanged: (value) { 72 | Provider.of(context, listen: false) 73 | .updateTheme(value); 74 | }, 75 | ), 76 | ),*/ 77 | /* 78 | ListTile( 79 | title: Text(labels.settings.theme), 80 | trailing: CupertinoSlidingSegmentedControl( 81 | groupValue: Provider.of(context).getTheme, 82 | children: myTabs, 83 | onValueChanged: (value) { 84 | Provider.of(context, listen: false) 85 | .updateTheme(value); 86 | }, 87 | ), 88 | ),*/ 89 | ], 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/ui/auth/update_profile_ui.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:authapp/models/models.dart'; 6 | import 'package:authapp/ui/components/components.dart'; 7 | //import 'package:authapp/services/services.dart'; 8 | 9 | class UpdateProfileUI extends StatefulWidget { 10 | _UpdateProfileUIState createState() => _UpdateProfileUIState(); 11 | } 12 | 13 | class _UpdateProfileUIState extends State { 14 | final GlobalKey _formKey = GlobalKey(); 15 | final TextEditingController _name = new TextEditingController(); 16 | final TextEditingController _email = new TextEditingController(); 17 | final _scaffoldKey = GlobalKey(); 18 | bool _loading = false; 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | _name.dispose(); 27 | _email.dispose(); 28 | super.dispose(); 29 | } 30 | 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | //_key: _scaffoldKey, 34 | appBar: AppBar(title: Text('Update Profile')), 35 | body: LoadingScreen( 36 | child: updateProfileForm(context), 37 | inAsyncCall: _loading, 38 | color: Theme.of(context).scaffoldBackgroundColor, 39 | ), 40 | ); 41 | } 42 | 43 | updateProfileForm(BuildContext context) { 44 | final UserModel user = Provider.of(context); 45 | _name.text = user?.name; 46 | _email.text = user?.email; 47 | return Form( 48 | key: _formKey, 49 | child: Padding( 50 | padding: const EdgeInsets.symmetric(horizontal: 24.0), 51 | child: Center( 52 | child: SingleChildScrollView( 53 | child: Column( 54 | mainAxisAlignment: MainAxisAlignment.center, 55 | crossAxisAlignment: CrossAxisAlignment.stretch, 56 | children: [ 57 | LogoGraphicHeader(), 58 | SizedBox(height: 48.0), 59 | FormInputFieldWithIcon( 60 | controller: _name, 61 | iconPrefix: Icons.person, 62 | labelText: "Name", 63 | //validator: Validator, 64 | onChanged: (value) => null, 65 | onSaved: (value) => _name.text = value, 66 | ), 67 | FormVerticalSpace(), 68 | FormInputFieldWithIcon( 69 | controller: _email, 70 | iconPrefix: Icons.email, 71 | labelText: "email", 72 | //validator: Validator, 73 | keyboardType: TextInputType.emailAddress, 74 | onChanged: (value) => null, 75 | onSaved: (value) => _email.text = value, 76 | ), 77 | FormVerticalSpace(), 78 | PrimaryButton( 79 | labelText: "Update", 80 | onPressed: () { 81 | if (_formKey.currentState.validate()) { 82 | SystemChannels.textInput.invokeMethod('TextInput.hide'); 83 | UserModel _updatedUser = UserModel( 84 | uid: user?.uid, 85 | name: _name.text, 86 | email: _email.text, 87 | photoUrl: user?.photoUrl); 88 | //_updateUserConfirm(context, _updatedUser, user?.email); 89 | print(_updatedUser); 90 | } 91 | }), 92 | FormVerticalSpace(), 93 | LabelButton( 94 | labelText: "Reset Password", 95 | onPressed: () => Navigator.pushNamed( 96 | context, '/reset-password', 97 | arguments: user.email)), 98 | ], 99 | ), 100 | ), 101 | ), 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/ui/auth/sign_up_ui.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:authapp/ui/components/components.dart'; 4 | import 'package:authapp/services/services.dart'; 5 | 6 | class SignUpUI extends StatefulWidget { 7 | _SignUpUIState createState() => _SignUpUIState(); 8 | } 9 | 10 | class _SignUpUIState extends State { 11 | final GlobalKey _formKey = GlobalKey(); 12 | final TextEditingController _name = new TextEditingController(); 13 | final TextEditingController _email = new TextEditingController(); 14 | final TextEditingController _password = new TextEditingController(); 15 | final _scaffoldKey = GlobalKey(); 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | _name.dispose(); 25 | _email.dispose(); 26 | _password.dispose(); 27 | super.dispose(); 28 | } 29 | 30 | Widget build(BuildContext context) { 31 | bool _loading = false; 32 | 33 | return Scaffold( 34 | key: _scaffoldKey, 35 | body: LoadingScreen( 36 | child: Form( 37 | key: _formKey, 38 | child: Padding( 39 | padding: const EdgeInsets.symmetric(horizontal: 24.0), 40 | child: Center( 41 | child: SingleChildScrollView( 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | crossAxisAlignment: CrossAxisAlignment.stretch, 45 | children: [ 46 | LogoGraphicHeader(), 47 | SizedBox(height: 48.0), 48 | FormInputFieldWithIcon( 49 | controller: _name, 50 | iconPrefix: Icons.person, 51 | labelText: 'Name', 52 | //validator: Validator(labels).name, 53 | onChanged: (value) => null, 54 | onSaved: (value) => _name.text = value, 55 | ), 56 | FormVerticalSpace(), 57 | FormInputFieldWithIcon( 58 | controller: _email, 59 | iconPrefix: Icons.email, 60 | labelText: 'Email', 61 | //validator: Validator(labels).email, 62 | keyboardType: TextInputType.emailAddress, 63 | onChanged: (value) => null, 64 | onSaved: (value) => _email.text = value, 65 | ), 66 | FormVerticalSpace(), 67 | FormInputFieldWithIcon( 68 | controller: _password, 69 | iconPrefix: Icons.lock, 70 | labelText: 'Password', 71 | //validator: Validator(labels).password, 72 | obscureText: true, 73 | maxLines: 1, 74 | onChanged: (value) => null, 75 | onSaved: (value) => _password.text = value, 76 | ), 77 | FormVerticalSpace(), 78 | PrimaryButton( 79 | labelText: 'Sign Up', 80 | onPressed: () async { 81 | if (_formKey.currentState.validate()) { 82 | AuthService _auth = AuthService(); 83 | bool _isRegisterSucccess = 84 | await _auth.createUserWithEmailAndPassword( 85 | _email.text, _password.text); 86 | 87 | if (_isRegisterSucccess == false) { 88 | _scaffoldKey.currentState.showSnackBar(SnackBar( 89 | content: Text('ERROR'), 90 | )); 91 | } 92 | if (_isRegisterSucccess == true) { 93 | Navigator.of(context).pushNamed('/home'); 94 | } 95 | } 96 | }), 97 | FormVerticalSpace(), 98 | LabelButton( 99 | labelText: 'Sign In', 100 | onPressed: () => 101 | Navigator.pushReplacementNamed(context, '/'), 102 | ), 103 | ], 104 | ), 105 | ), 106 | ), 107 | ), 108 | ), 109 | inAsyncCall: _loading, 110 | color: Theme.of(context).scaffoldBackgroundColor, 111 | ), 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/ui/auth/sign_in_ui.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:authapp/ui/components/components.dart'; 5 | import 'package:authapp/ui/components/form_input_field_with_icon.dart'; 6 | import 'package:authapp/ui/components/label_button.dart'; 7 | import 'package:authapp/ui/components/primary_button.dart'; 8 | 9 | import 'package:authapp/services/services.dart'; 10 | 11 | class SignInUI extends StatefulWidget { 12 | _SignInUIState createState() => _SignInUIState(); 13 | } 14 | 15 | class _SignInUIState extends State { 16 | final GlobalKey _formKey = GlobalKey(); 17 | final TextEditingController _email = new TextEditingController(); 18 | final TextEditingController _password = new TextEditingController(); 19 | final _scaffoldKey = GlobalKey(); 20 | bool _loading = false; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | } 26 | 27 | @override 28 | void dispose() { 29 | _email.dispose(); 30 | _password.dispose(); 31 | super.dispose(); 32 | } 33 | 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | key: _scaffoldKey, 37 | body: LoadingScreen( 38 | child: Form( 39 | key: _formKey, 40 | child: Padding( 41 | padding: const EdgeInsets.symmetric(horizontal: 24.0), 42 | child: Center( 43 | child: SingleChildScrollView( 44 | child: Column( 45 | mainAxisAlignment: MainAxisAlignment.center, 46 | crossAxisAlignment: CrossAxisAlignment.stretch, 47 | children: [ 48 | LogoGraphicHeader(), 49 | SizedBox(height: 48.0), 50 | FormInputFieldWithIcon( 51 | controller: _email, 52 | iconPrefix: Icons.email, 53 | labelText: "EMAIL", 54 | //validator: Validator, 55 | keyboardType: TextInputType.emailAddress, 56 | onChanged: (value) => null, 57 | onSaved: (value) => _email.text = value, 58 | ), 59 | FormVerticalSpace(), 60 | FormInputFieldWithIcon( 61 | controller: _password, 62 | iconPrefix: Icons.lock, 63 | labelText: "PASSWORD", 64 | //validator: Validator, 65 | obscureText: true, 66 | onChanged: (value) => null, 67 | onSaved: (value) => _password.text = value, 68 | maxLines: 1, 69 | ), 70 | FormVerticalSpace(), 71 | PrimaryButton( 72 | labelText: "Sign In", 73 | onPressed: () async { 74 | if (_formKey.currentState.validate()) { 75 | setState(() { 76 | _loading = true; 77 | }); 78 | AuthService _auth = AuthService(); 79 | bool status = await _auth 80 | .signInWithEmailAndPassword( 81 | _email.text, _password.text) 82 | .then((status) { 83 | setState(() { 84 | _loading = false; 85 | }); 86 | return status; 87 | }); 88 | //if (status == false) { 89 | // // 90 | // _scaffoldKey.currentState.showSnackBar(SnackBar( 91 | // content: Text('incorrect'), 92 | // )); 93 | // print("incorrect"); 94 | //} else { 95 | // print("suppper"); 96 | //} 97 | } 98 | }, 99 | ), 100 | FormVerticalSpace(), 101 | LabelButton( 102 | labelText: "Reset Password", 103 | onPressed: null, 104 | ), 105 | LabelButton( 106 | labelText: "Sign Up", 107 | onPressed: () => 108 | Navigator.pushReplacementNamed(context, '/signup'), 109 | ) 110 | ], 111 | ), 112 | ), 113 | ), 114 | ), 115 | ), 116 | inAsyncCall: _loading, 117 | color: Theme.of(context).scaffoldBackgroundColor, 118 | ), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/constants/app_themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppThemes { 4 | AppThemes._(); 5 | 6 | static const Color dodgerBlue = Color.fromRGBO(29, 161, 242, 1); 7 | static const Color whiteLilac = Color.fromRGBO(248, 250, 252, 1); 8 | static const Color blackPearl = Color.fromRGBO(30, 31, 43, 1); 9 | static const Color brinkPink = Color.fromRGBO(255, 97, 136, 1); 10 | static const Color juneBud = Color.fromRGBO(186, 215, 97, 1); 11 | static const Color white = Color.fromRGBO(255, 255, 255, 1); 12 | static const Color nevada = Color.fromRGBO(105, 109, 119, 1); 13 | static const Color ebonyClay = Color.fromRGBO(40, 42, 58, 1); 14 | 15 | static String font1 = "ProductSans"; 16 | static String font2 = "Roboto"; 17 | //constants color range for light theme 18 | //main color 19 | static const Color _lightPrimaryColor = dodgerBlue; 20 | 21 | //Background Colors 22 | static const Color _lightBackgroundColor = whiteLilac; 23 | static const Color _lightBackgroundAppBarColor = _lightPrimaryColor; 24 | static const Color _lightBackgroundSecondaryColor = white; 25 | static const Color _lightBackgroundAlertColor = blackPearl; 26 | static const Color _lightBackgroundErrorColor = brinkPink; 27 | static const Color _lightBackgroundSuccessColor = juneBud; 28 | 29 | //Text Colors 30 | static const Color _lightTextColor = Colors.black; 31 | static const Color _lightAlertTextColor = Colors.black; 32 | static const Color _lightTextSecondaryColor = Colors.black; 33 | 34 | //Border Color 35 | static const Color _lightBorderColor = nevada; 36 | 37 | //Icon Color 38 | static const Color _lightIconColor = nevada; 39 | 40 | //form input colors 41 | static const Color _lightInputFillColor = _lightBackgroundSecondaryColor; 42 | static const Color _lightBorderActiveColor = _lightPrimaryColor; 43 | static const Color _lightBorderErrorColor = brinkPink; 44 | 45 | //constants color range for dark theme 46 | static const Color _darkPrimaryColor = dodgerBlue; 47 | 48 | //Background Colors 49 | static const Color _darkBackgroundColor = ebonyClay; 50 | static const Color _darkBackgroundAppBarColor = _darkPrimaryColor; 51 | static const Color _darkBackgroundSecondaryColor = 52 | Color.fromRGBO(0, 0, 0, .6); 53 | static const Color _darkBackgroundAlertColor = Colors.black; 54 | static const Color _darkBackgroundErrorColor = 55 | Color.fromRGBO(255, 97, 136, 1); 56 | static const Color _darkBackgroundSuccessColor = 57 | Color.fromRGBO(186, 215, 97, 1); 58 | 59 | //Text Colors 60 | static const Color _darkTextColor = Colors.white; 61 | static const Color _darkAlertTextColor = Colors.black; 62 | static const Color _darkTextSecondaryColor = Colors.black; 63 | 64 | //Border Color 65 | static const Color _darkBorderColor = nevada; 66 | 67 | //Icon Color 68 | static const Color _darkIconColor = nevada; 69 | 70 | static const Color _darkInputFillColor = _darkBackgroundSecondaryColor; 71 | static const Color _darkBorderActiveColor = _darkPrimaryColor; 72 | static const Color _darkBorderErrorColor = brinkPink; 73 | 74 | //text theme for light theme 75 | static final TextTheme _lightTextTheme = TextTheme( 76 | headline1: TextStyle(fontSize: 20.0, color: _lightTextColor), 77 | bodyText1: TextStyle(fontSize: 16.0, color: _lightTextColor), 78 | bodyText2: TextStyle(fontSize: 14.0, color: Colors.grey), 79 | button: TextStyle( 80 | fontSize: 15.0, color: _lightTextColor, fontWeight: FontWeight.w600), 81 | headline6: TextStyle(fontSize: 16.0, color: _lightTextColor), 82 | subtitle1: TextStyle(fontSize: 16.0, color: _lightTextColor), 83 | caption: TextStyle(fontSize: 12.0, color: _lightBackgroundAppBarColor), 84 | ); 85 | 86 | //the light theme 87 | static final ThemeData lightTheme = ThemeData( 88 | brightness: Brightness.light, 89 | fontFamily: font1, 90 | scaffoldBackgroundColor: _lightBackgroundColor, 91 | floatingActionButtonTheme: FloatingActionButtonThemeData( 92 | backgroundColor: _lightPrimaryColor, 93 | ), 94 | appBarTheme: AppBarTheme( 95 | color: _lightBackgroundAppBarColor, 96 | iconTheme: IconThemeData(color: _lightTextColor), 97 | textTheme: _lightTextTheme, 98 | ), 99 | colorScheme: ColorScheme.light( 100 | primary: _lightPrimaryColor, 101 | primaryVariant: _lightBackgroundColor, 102 | // secondary: _lightSecondaryColor, 103 | ), 104 | snackBarTheme: 105 | SnackBarThemeData(backgroundColor: _lightBackgroundAlertColor), 106 | iconTheme: IconThemeData( 107 | color: _lightIconColor, 108 | ), 109 | popupMenuTheme: PopupMenuThemeData(color: _lightBackgroundAppBarColor), 110 | textTheme: _lightTextTheme, 111 | buttonTheme: ButtonThemeData( 112 | shape: RoundedRectangleBorder( 113 | borderRadius: BorderRadius.circular(8), 114 | ), 115 | buttonColor: _lightPrimaryColor, 116 | textTheme: ButtonTextTheme.primary), 117 | unselectedWidgetColor: _lightPrimaryColor, 118 | inputDecorationTheme: InputDecorationTheme( 119 | //prefixStyle: TextStyle(color: _lightIconColor), 120 | border: OutlineInputBorder( 121 | borderSide: BorderSide(width: 1.0), 122 | borderRadius: BorderRadius.all( 123 | Radius.circular(8.0), 124 | )), 125 | enabledBorder: OutlineInputBorder( 126 | borderSide: BorderSide(color: _lightBorderColor, width: 1.0), 127 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 128 | ), 129 | focusedBorder: OutlineInputBorder( 130 | borderSide: BorderSide(color: _lightBorderActiveColor), 131 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 132 | ), 133 | errorBorder: OutlineInputBorder( 134 | borderSide: BorderSide(color: _lightBorderErrorColor), 135 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 136 | ), 137 | focusedErrorBorder: OutlineInputBorder( 138 | borderSide: BorderSide(color: _lightBorderErrorColor), 139 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 140 | ), 141 | fillColor: _lightBackgroundSecondaryColor, 142 | //focusColor: _lightBorderActiveColor, 143 | ), 144 | ); 145 | 146 | //text theme for dark theme 147 | /*static final TextStyle _darkScreenHeadingTextStyle = 148 | _lightScreenHeadingTextStyle.copyWith(color: _darkTextColor); 149 | static final TextStyle _darkScreenTaskNameTextStyle = 150 | _lightScreenTaskNameTextStyle.copyWith(color: _darkTextColor); 151 | static final TextStyle _darkScreenTaskDurationTextStyle = 152 | _lightScreenTaskDurationTextStyle; 153 | static final TextStyle _darkScreenButtonTextStyle = TextStyle( 154 | fontSize: 14.0, color: _darkTextColor, fontWeight: FontWeight.w500); 155 | static final TextStyle _darkScreenCaptionTextStyle = TextStyle( 156 | fontSize: 12.0, 157 | color: _darkBackgroundAppBarColor, 158 | fontWeight: FontWeight.w100);*/ 159 | 160 | static final TextTheme _darkTextTheme = TextTheme( 161 | headline1: TextStyle(fontSize: 20.0, color: _darkTextColor), 162 | bodyText1: TextStyle(fontSize: 16.0, color: _darkTextColor), 163 | bodyText2: TextStyle(fontSize: 14.0, color: Colors.grey), 164 | button: TextStyle( 165 | fontSize: 15.0, color: _darkTextColor, fontWeight: FontWeight.w600), 166 | headline6: TextStyle(fontSize: 16.0, color: _darkTextColor), 167 | subtitle1: TextStyle(fontSize: 16.0, color: _darkTextColor), 168 | caption: TextStyle(fontSize: 12.0, color: _darkBackgroundAppBarColor), 169 | ); 170 | 171 | //the dark theme 172 | static final ThemeData darkTheme = ThemeData( 173 | brightness: Brightness.dark, 174 | //primarySwatch: _darkPrimaryColor, //cant be Color on MaterialColor so it can compute different shades. 175 | accentColor: _darkPrimaryColor, //prefix icon color form input on focus 176 | 177 | fontFamily: font1, 178 | scaffoldBackgroundColor: _darkBackgroundColor, 179 | floatingActionButtonTheme: FloatingActionButtonThemeData( 180 | backgroundColor: _darkPrimaryColor, 181 | ), 182 | appBarTheme: AppBarTheme( 183 | color: _darkBackgroundAppBarColor, 184 | iconTheme: IconThemeData(color: _darkTextColor), 185 | textTheme: _darkTextTheme, 186 | ), 187 | colorScheme: ColorScheme.dark( 188 | primary: _darkPrimaryColor, 189 | primaryVariant: _darkBackgroundColor, 190 | 191 | // secondary: _darkSecondaryColor, 192 | ), 193 | snackBarTheme: SnackBarThemeData( 194 | contentTextStyle: TextStyle(color: Colors.white), 195 | backgroundColor: _darkBackgroundAlertColor), 196 | iconTheme: IconThemeData( 197 | color: Colors.white, //_darkIconColor, 198 | ), 199 | popupMenuTheme: PopupMenuThemeData(color: _darkBackgroundAppBarColor), 200 | textTheme: _darkTextTheme, 201 | buttonTheme: ButtonThemeData( 202 | shape: RoundedRectangleBorder( 203 | borderRadius: BorderRadius.circular(8), 204 | ), 205 | buttonColor: _darkPrimaryColor, 206 | textTheme: ButtonTextTheme.primary), 207 | unselectedWidgetColor: _darkPrimaryColor, 208 | inputDecorationTheme: InputDecorationTheme( 209 | prefixStyle: TextStyle(color: _darkIconColor), 210 | //labelStyle: TextStyle(color: nevada), 211 | border: OutlineInputBorder( 212 | borderSide: BorderSide(width: 1.0), 213 | borderRadius: BorderRadius.all( 214 | Radius.circular(8.0), 215 | )), 216 | enabledBorder: OutlineInputBorder( 217 | borderSide: BorderSide(color: _darkBorderColor, width: 1.0), 218 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 219 | ), 220 | focusedBorder: OutlineInputBorder( 221 | borderSide: BorderSide(color: _darkBorderActiveColor), 222 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 223 | ), 224 | errorBorder: OutlineInputBorder( 225 | borderSide: BorderSide(color: _darkBorderErrorColor), 226 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 227 | ), 228 | focusedErrorBorder: OutlineInputBorder( 229 | borderSide: BorderSide(color: _darkBorderErrorColor), 230 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 231 | ), 232 | fillColor: _darkInputFillColor, 233 | //focusColor: _darkBorderActiveColor, 234 | ), 235 | ); 236 | } 237 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | cloud_firestore: 40 | dependency: "direct main" 41 | description: 42 | name: cloud_firestore 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.3" 46 | cloud_firestore_platform_interface: 47 | dependency: transitive 48 | description: 49 | name: cloud_firestore_platform_interface 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "4.0.0" 53 | cloud_firestore_web: 54 | dependency: transitive 55 | description: 56 | name: cloud_firestore_web 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.3" 60 | collection: 61 | dependency: transitive 62 | description: 63 | name: collection 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.15.0" 67 | convert: 68 | dependency: transitive 69 | description: 70 | name: convert 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.1" 74 | crypto: 75 | dependency: transitive 76 | description: 77 | name: crypto 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.1.5" 81 | cupertino_icons: 82 | dependency: "direct main" 83 | description: 84 | name: cupertino_icons 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.0.2" 88 | fake_async: 89 | dependency: transitive 90 | description: 91 | name: fake_async 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.2.0" 95 | ffi: 96 | dependency: transitive 97 | description: 98 | name: ffi 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.0.0" 102 | file: 103 | dependency: transitive 104 | description: 105 | name: file 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "6.1.0" 109 | firebase: 110 | dependency: transitive 111 | description: 112 | name: firebase 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "9.0.1" 116 | firebase_analytics: 117 | dependency: "direct main" 118 | description: 119 | name: firebase_analytics 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "7.1.1" 123 | firebase_analytics_platform_interface: 124 | dependency: transitive 125 | description: 126 | name: firebase_analytics_platform_interface 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.1.0" 130 | firebase_analytics_web: 131 | dependency: transitive 132 | description: 133 | name: firebase_analytics_web 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.2.0+1" 137 | firebase_auth: 138 | dependency: "direct main" 139 | description: 140 | name: firebase_auth 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.0.1" 144 | firebase_auth_platform_interface: 145 | dependency: transitive 146 | description: 147 | name: firebase_auth_platform_interface 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "4.0.0" 151 | firebase_auth_web: 152 | dependency: transitive 153 | description: 154 | name: firebase_auth_web 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.0.3" 158 | firebase_core: 159 | dependency: "direct main" 160 | description: 161 | name: firebase_core 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "1.0.2" 165 | firebase_core_platform_interface: 166 | dependency: transitive 167 | description: 168 | name: firebase_core_platform_interface 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "4.0.0" 172 | firebase_core_web: 173 | dependency: transitive 174 | description: 175 | name: firebase_core_web 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.0.2" 179 | firebase_database: 180 | dependency: "direct main" 181 | description: 182 | name: firebase_database 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "6.1.1" 186 | flutter: 187 | dependency: "direct main" 188 | description: flutter 189 | source: sdk 190 | version: "0.0.0" 191 | flutter_test: 192 | dependency: "direct dev" 193 | description: flutter 194 | source: sdk 195 | version: "0.0.0" 196 | flutter_web_plugins: 197 | dependency: transitive 198 | description: flutter 199 | source: sdk 200 | version: "0.0.0" 201 | http: 202 | dependency: transitive 203 | description: 204 | name: http 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.13.1" 208 | http_parser: 209 | dependency: transitive 210 | description: 211 | name: http_parser 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "4.0.0" 215 | intl: 216 | dependency: transitive 217 | description: 218 | name: intl 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "0.17.0" 222 | js: 223 | dependency: transitive 224 | description: 225 | name: js 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "0.6.3" 229 | matcher: 230 | dependency: transitive 231 | description: 232 | name: matcher 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "0.12.10" 236 | meta: 237 | dependency: transitive 238 | description: 239 | name: meta 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "1.3.0" 243 | nested: 244 | dependency: transitive 245 | description: 246 | name: nested 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "1.0.0" 250 | path: 251 | dependency: transitive 252 | description: 253 | name: path 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "1.8.0" 257 | path_provider_linux: 258 | dependency: transitive 259 | description: 260 | name: path_provider_linux 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "2.0.0" 264 | path_provider_platform_interface: 265 | dependency: transitive 266 | description: 267 | name: path_provider_platform_interface 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "2.0.1" 271 | path_provider_windows: 272 | dependency: transitive 273 | description: 274 | name: path_provider_windows 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "2.0.0" 278 | pedantic: 279 | dependency: transitive 280 | description: 281 | name: pedantic 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.11.0" 285 | platform: 286 | dependency: transitive 287 | description: 288 | name: platform 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "3.0.0" 292 | plugin_platform_interface: 293 | dependency: transitive 294 | description: 295 | name: plugin_platform_interface 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "2.0.0" 299 | process: 300 | dependency: transitive 301 | description: 302 | name: process 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "4.2.1" 306 | provider: 307 | dependency: "direct main" 308 | description: 309 | name: provider 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "5.0.0" 313 | shared_preferences: 314 | dependency: "direct main" 315 | description: 316 | name: shared_preferences 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "2.0.5" 320 | shared_preferences_linux: 321 | dependency: transitive 322 | description: 323 | name: shared_preferences_linux 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "2.0.0" 327 | shared_preferences_macos: 328 | dependency: transitive 329 | description: 330 | name: shared_preferences_macos 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "2.0.0" 334 | shared_preferences_platform_interface: 335 | dependency: transitive 336 | description: 337 | name: shared_preferences_platform_interface 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "2.0.0" 341 | shared_preferences_web: 342 | dependency: transitive 343 | description: 344 | name: shared_preferences_web 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "2.0.0" 348 | shared_preferences_windows: 349 | dependency: transitive 350 | description: 351 | name: shared_preferences_windows 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "2.0.0" 355 | simple_gravatar: 356 | dependency: "direct main" 357 | description: 358 | name: simple_gravatar 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "1.0.5" 362 | sky_engine: 363 | dependency: transitive 364 | description: flutter 365 | source: sdk 366 | version: "0.0.99" 367 | source_span: 368 | dependency: transitive 369 | description: 370 | name: source_span 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "1.8.0" 374 | stack_trace: 375 | dependency: transitive 376 | description: 377 | name: stack_trace 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "1.10.0" 381 | stream_channel: 382 | dependency: transitive 383 | description: 384 | name: stream_channel 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "2.1.0" 388 | string_scanner: 389 | dependency: transitive 390 | description: 391 | name: string_scanner 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "1.1.0" 395 | term_glyph: 396 | dependency: transitive 397 | description: 398 | name: term_glyph 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "1.2.0" 402 | test_api: 403 | dependency: transitive 404 | description: 405 | name: test_api 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "0.2.19" 409 | typed_data: 410 | dependency: transitive 411 | description: 412 | name: typed_data 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "1.3.0" 416 | vector_math: 417 | dependency: transitive 418 | description: 419 | name: vector_math 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "2.1.0" 423 | win32: 424 | dependency: transitive 425 | description: 426 | name: win32 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "2.0.5" 430 | xdg_directories: 431 | dependency: transitive 432 | description: 433 | name: xdg_directories 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "0.2.0" 437 | sdks: 438 | dart: ">=2.12.0 <3.0.0" 439 | flutter: ">=1.20.0" 440 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1020; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = com.example.waterprojectV3; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 297 | SWIFT_VERSION = 5.0; 298 | VERSIONING_SYSTEM = "apple-generic"; 299 | }; 300 | name = Profile; 301 | }; 302 | 97C147031CF9000F007C117D /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = dwarf; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_TESTABILITY = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_DYNAMIC_NO_PIC = NO; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_OPTIMIZATION_LEVEL = 0; 339 | GCC_PREPROCESSOR_DEFINITIONS = ( 340 | "DEBUG=1", 341 | "$(inherited)", 342 | ); 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 350 | MTL_ENABLE_DEBUG_INFO = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | 97C147041CF9000F007C117D /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INFINITE_RECURSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 381 | CLANG_WARN_STRICT_PROTOTYPES = YES; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | SUPPORTED_PLATFORMS = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 97C147061CF9000F007C117D /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CLANG_ENABLE_MODULES = YES; 414 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 415 | ENABLE_BITCODE = NO; 416 | INFOPLIST_FILE = Runner/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.example.waterprojectV3; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | SWIFT_VERSION = 5.0; 423 | VERSIONING_SYSTEM = "apple-generic"; 424 | }; 425 | name = Debug; 426 | }; 427 | 97C147071CF9000F007C117D /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.example.waterprojectV3; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 440 | SWIFT_VERSION = 5.0; 441 | VERSIONING_SYSTEM = "apple-generic"; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 97C147031CF9000F007C117D /* Debug */, 452 | 97C147041CF9000F007C117D /* Release */, 453 | 249021D3217E4FDB00AE95B9 /* Profile */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147061CF9000F007C117D /* Debug */, 462 | 97C147071CF9000F007C117D /* Release */, 463 | 249021D4217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | /* End XCConfigurationList section */ 469 | }; 470 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 471 | } 472 | -------------------------------------------------------------------------------- /lib/ui/components/sliding_segmented_control.dart: -------------------------------------------------------------------------------- 1 | //modified CupertinoSlidingSegmentedControl 2 | 3 | // Copyright 2014 The Flutter Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style license that can be 5 | // found in the LICENSE file. 6 | 7 | import 'dart:math' as math; 8 | 9 | import 'package:flutter/foundation.dart'; 10 | import 'package:flutter/gestures.dart'; 11 | import 'package:flutter/physics.dart'; 12 | import 'package:flutter/rendering.dart'; 13 | import 'package:flutter/widgets.dart'; 14 | import 'package:flutter/cupertino.dart'; //added by delay 15 | //import 'colors.dart'; //removed by delay 16 | 17 | // Extracted from https://developer.apple.com/design/resources/. 18 | 19 | // Minimum padding from edges of the segmented control to edges of 20 | // encompassing widget. 21 | const EdgeInsetsGeometry _kHorizontalItemPadding = 22 | EdgeInsets.symmetric(vertical: 2, horizontal: 3); 23 | 24 | // The corner radius of the thumb. 25 | //const Radius _kThumbRadius = Radius.circular(6.93); 26 | const Radius _kThumbRadius = Radius.circular(26.93); //changed by delay 27 | // The amount of space by which to expand the thumb from the size of the currently 28 | // selected child. 29 | const EdgeInsets _kThumbInsets = EdgeInsets.symmetric(horizontal: 1); 30 | 31 | // Minimum height of the segmented control. 32 | const double _kMinSegmentedControlHeight = 28.0; 33 | 34 | //const Color _kSeparatorColor = Color(0x4D8E8E93); 35 | const Color _kSeparatorColor = Color.fromRGBO(0, 0, 0, 0); //changed by delay 36 | 37 | const CupertinoDynamicColor _kThumbColor = CupertinoDynamicColor.withBrightness( 38 | color: Color(0xFFFFFFFF), 39 | darkColor: Color(0xFF636366), 40 | ); 41 | 42 | // The amount of space by which to inset each separator. 43 | const EdgeInsets _kSeparatorInset = EdgeInsets.symmetric(vertical: 6); 44 | const double _kSeparatorWidth = 1; 45 | const Radius _kSeparatorRadius = Radius.circular(_kSeparatorWidth / 2); 46 | 47 | // The minimum scale factor of the thumb, when being pressed on for a sufficient 48 | // amount of time. 49 | const double _kMinThumbScale = 0.95; 50 | 51 | // The minimum horizontal distance between the edges of the separator and the 52 | // closest child. 53 | const double _kSegmentMinPadding = 9.25; 54 | 55 | // The threshold value used in hasDraggedTooFar, for checking against the square 56 | // L2 distance from the location of the current drag pointer, to the closest 57 | // vertice of the CupertinoSlidingSegmentedControl's Rect. 58 | // 59 | // Both the mechanism and the value are speculated. 60 | const double _kTouchYDistanceThreshold = 50.0 * 50.0; 61 | 62 | // The corner radius of the segmented control. 63 | // 64 | // Inspected from iOS 13.2 simulator. 65 | //const double _kCornerRadius = 8; 66 | const double _kCornerRadius = 28; //changed by delay 67 | 68 | // The spring animation used when the thumb changes its rect. 69 | final SpringSimulation _kThumbSpringAnimationSimulation = SpringSimulation( 70 | const SpringDescription(mass: 1, stiffness: 503.551, damping: 44.8799), 71 | 0, 72 | 1, 73 | 0, // Everytime a new spring animation starts the previous animation stops. 74 | ); 75 | 76 | const Duration _kSpringAnimationDuration = Duration(milliseconds: 412); 77 | 78 | const Duration _kOpacityAnimationDuration = Duration(milliseconds: 470); 79 | 80 | const Duration _kHighlightAnimationDuration = Duration(milliseconds: 200); 81 | 82 | class _FontWeightTween extends Tween { 83 | _FontWeightTween({FontWeight begin, FontWeight end}) 84 | : super(begin: begin, end: end); 85 | 86 | @override 87 | FontWeight lerp(double t) => FontWeight.lerp(begin, end, t); 88 | } 89 | 90 | /// An iOS 13 style segmented control. 91 | /// 92 | /// Displays the widgets provided in the [Map] of [children] in a horizontal list. 93 | /// Used to select between a number of mutually exclusive options. When one option 94 | /// in the segmented control is selected, the other options in the segmented 95 | /// control cease to be selected. 96 | /// 97 | /// A segmented control can feature any [Widget] as one of the values in its 98 | /// [Map] of [children]. The type T is the type of the [Map] keys used to identify 99 | /// each widget and determine which widget is selected. As required by the [Map] 100 | /// class, keys must be of consistent types and must be comparable. The [children] 101 | /// argument must be an ordered [Map] such as a [LinkedHashMap], the ordering of 102 | /// the keys will determine the order of the widgets in the segmented control. 103 | /// 104 | /// When the state of the segmented control changes, the widget calls the 105 | /// [onValueChanged] callback. The map key associated with the newly selected 106 | /// widget is returned in the [onValueChanged] callback. Typically, widgets 107 | /// that use a segmented control will listen for the [onValueChanged] callback 108 | /// and rebuild the segmented control with a new [groupValue] to update which 109 | /// option is currently selected. 110 | /// 111 | /// The [children] will be displayed in the order of the keys in the [Map]. 112 | /// The height of the segmented control is determined by the height of the 113 | /// tallest widget provided as a value in the [Map] of [children]. 114 | /// The width of each child in the segmented control will be equal to the width 115 | /// of widest child, unless the combined width of the children is wider than 116 | /// the available horizontal space. In this case, the available horizontal space 117 | /// is divided by the number of provided [children] to determine the width of 118 | /// each widget. The selection area for each of the widgets in the [Map] of 119 | /// [children] will then be expanded to fill the calculated space, so each 120 | /// widget will appear to have the same dimensions. 121 | /// 122 | /// A segmented control may optionally be created with custom colors. The 123 | /// [thumbColor], [backgroundColor] arguments can be used to override the segmented 124 | /// control's colors from its defaults. 125 | /// 126 | /// See also: 127 | /// 128 | /// * [CupertinoSlidingSegmentedControl], a segmented control widget in the 129 | /// style introduced in iOS 13. 130 | /// * 131 | class CupertinoSlidingSegmentedControl extends StatefulWidget { 132 | /// Creates an iOS-style segmented control bar. 133 | /// 134 | /// The [children] and [onValueChanged] arguments must not be null. The 135 | /// [children] argument must be an ordered [Map] such as a [LinkedHashMap]. 136 | /// Further, the length of the [children] list must be greater than one. 137 | /// 138 | /// Each widget value in the map of [children] must have an associated key 139 | /// that uniquely identifies this widget. This key is what will be returned 140 | /// in the [onValueChanged] callback when a new value from the [children] map 141 | /// is selected. 142 | /// 143 | /// The [groupValue] is the currently selected value for the segmented control. 144 | /// If no [groupValue] is provided, or the [groupValue] is null, no widget will 145 | /// appear as selected. The [groupValue] must be either null or one of the keys 146 | /// in the [children] map. 147 | CupertinoSlidingSegmentedControl({ 148 | Key key, 149 | @required this.children, 150 | @required this.onValueChanged, 151 | this.groupValue, 152 | this.thumbColor = _kThumbColor, 153 | this.padding = _kHorizontalItemPadding, 154 | this.backgroundColor = CupertinoColors.tertiarySystemFill, 155 | }) : assert(children != null), 156 | assert(children.length >= 2), 157 | assert(padding != null), 158 | assert(onValueChanged != null), 159 | assert( 160 | groupValue == null || children.keys.contains(groupValue), 161 | 'The groupValue must be either null or one of the keys in the children map.', 162 | ), 163 | super(key: key); 164 | 165 | /// The identifying keys and corresponding widget values in the 166 | /// segmented control. 167 | /// 168 | /// The map must have more than one entry. 169 | /// This attribute must be an ordered [Map] such as a [LinkedHashMap]. 170 | final Map children; 171 | 172 | /// The identifier of the widget that is currently selected. 173 | /// 174 | /// This must be one of the keys in the [Map] of [children]. 175 | /// If this attribute is null, no widget will be initially selected. 176 | final T groupValue; 177 | 178 | /// The callback that is called when a new option is tapped. 179 | /// 180 | /// This attribute must not be null. 181 | /// 182 | /// The segmented control passes the newly selected widget's associated key 183 | /// to the callback but does not actually change state until the parent 184 | /// widget rebuilds the segmented control with the new [groupValue]. 185 | /// 186 | /// The callback provided to [onValueChanged] should update the state of 187 | /// the parent [StatefulWidget] using the [State.setState] method, so that 188 | /// the parent gets rebuilt; for example: 189 | /// 190 | /// {@tool snippet} 191 | /// 192 | /// ```dart 193 | /// class SegmentedControlExample extends StatefulWidget { 194 | /// @override 195 | /// State createState() => SegmentedControlExampleState(); 196 | /// } 197 | /// 198 | /// class SegmentedControlExampleState extends State { 199 | /// final Map children = const { 200 | /// 0: Text('Child 1'), 201 | /// 1: Text('Child 2'), 202 | /// }; 203 | /// 204 | /// int currentValue; 205 | /// 206 | /// @override 207 | /// Widget build(BuildContext context) { 208 | /// return Container( 209 | /// child: CupertinoSlidingSegmentedControl( 210 | /// children: children, 211 | /// onValueChanged: (int newValue) { 212 | /// setState(() { 213 | /// currentValue = newValue; 214 | /// }); 215 | /// }, 216 | /// groupValue: currentValue, 217 | /// ), 218 | /// ); 219 | /// } 220 | /// } 221 | /// ``` 222 | /// {@end-tool} 223 | final ValueChanged onValueChanged; 224 | 225 | /// The color used to paint the rounded rect behind the [children] and the separators. 226 | /// 227 | /// The default value is [CupertinoColors.tertiarySystemFill]. The background 228 | /// will not be painted if null is specified. 229 | final Color backgroundColor; 230 | 231 | /// The color used to paint the interior of the thumb that appears behind the 232 | /// currently selected item. 233 | /// 234 | /// The default value is a [CupertinoDynamicColor] that appears white in light 235 | /// mode and becomes a gray color in dark mode. 236 | final Color thumbColor; 237 | 238 | /// The amount of space by which to inset the [children]. 239 | /// 240 | /// Must not be null. Defaults to EdgeInsets.symmetric(vertical: 2, horizontal: 3). 241 | final EdgeInsetsGeometry padding; 242 | 243 | @override 244 | _SegmentedControlState createState() => _SegmentedControlState(); 245 | } 246 | 247 | class _SegmentedControlState 248 | extends State> 249 | with TickerProviderStateMixin> { 250 | final Map _highlightControllers = 251 | {}; 252 | final Tween _highlightTween = 253 | _FontWeightTween(begin: FontWeight.normal, end: FontWeight.w500); 254 | 255 | final Map _pressControllers = 256 | {}; 257 | final Tween _pressTween = Tween(begin: 1, end: 0.2); 258 | 259 | List keys; 260 | 261 | AnimationController thumbController; 262 | AnimationController separatorOpacityController; 263 | AnimationController thumbScaleController; 264 | 265 | final TapGestureRecognizer tap = TapGestureRecognizer(); 266 | final HorizontalDragGestureRecognizer drag = 267 | HorizontalDragGestureRecognizer(); 268 | final LongPressGestureRecognizer longPress = LongPressGestureRecognizer(); 269 | 270 | AnimationController _createHighlightAnimationController( 271 | {bool isCompleted = false}) { 272 | return AnimationController( 273 | duration: _kHighlightAnimationDuration, 274 | value: isCompleted ? 1 : 0, 275 | vsync: this, 276 | ); 277 | } 278 | 279 | AnimationController _createFadeoutAnimationController() { 280 | return AnimationController( 281 | duration: _kOpacityAnimationDuration, 282 | vsync: this, 283 | ); 284 | } 285 | 286 | @override 287 | void initState() { 288 | super.initState(); 289 | 290 | final GestureArenaTeam team = GestureArenaTeam(); 291 | // If the long press or horizontal drag recognizer gets accepted, we know for 292 | // sure the gesture is meant for the segmented control. Hand everything to 293 | // the drag gesture recognizer. 294 | longPress.team = team; 295 | drag.team = team; 296 | team.captain = drag; 297 | 298 | _highlighted = widget.groupValue; 299 | 300 | thumbController = AnimationController( 301 | duration: _kSpringAnimationDuration, 302 | value: 0, 303 | vsync: this, 304 | ); 305 | 306 | thumbScaleController = AnimationController( 307 | duration: _kSpringAnimationDuration, 308 | value: 1, 309 | vsync: this, 310 | ); 311 | 312 | separatorOpacityController = AnimationController( 313 | duration: _kSpringAnimationDuration, 314 | value: 0, 315 | vsync: this, 316 | ); 317 | 318 | for (final T currentKey in widget.children.keys) { 319 | _highlightControllers[currentKey] = _createHighlightAnimationController( 320 | isCompleted: 321 | currentKey == widget.groupValue, // Highlight the current selection. 322 | ); 323 | _pressControllers[currentKey] = _createFadeoutAnimationController(); 324 | } 325 | } 326 | 327 | @override 328 | void didUpdateWidget(CupertinoSlidingSegmentedControl oldWidget) { 329 | super.didUpdateWidget(oldWidget); 330 | 331 | // Update animation controllers. 332 | for (final T oldKey in oldWidget.children.keys) { 333 | if (!widget.children.containsKey(oldKey)) { 334 | _highlightControllers[oldKey].dispose(); 335 | _pressControllers[oldKey].dispose(); 336 | 337 | _highlightControllers.remove(oldKey); 338 | _pressControllers.remove(oldKey); 339 | } 340 | } 341 | 342 | for (final T newKey in widget.children.keys) { 343 | if (!_highlightControllers.keys.contains(newKey)) { 344 | _highlightControllers[newKey] = _createHighlightAnimationController(); 345 | _pressControllers[newKey] = _createFadeoutAnimationController(); 346 | } 347 | } 348 | 349 | highlighted = widget.groupValue; 350 | } 351 | 352 | @override 353 | void dispose() { 354 | for (final AnimationController animationController 355 | in _highlightControllers.values) { 356 | animationController.dispose(); 357 | } 358 | 359 | for (final AnimationController animationController 360 | in _pressControllers.values) { 361 | animationController.dispose(); 362 | } 363 | 364 | thumbScaleController.dispose(); 365 | thumbController.dispose(); 366 | separatorOpacityController.dispose(); 367 | 368 | drag.dispose(); 369 | tap.dispose(); 370 | longPress.dispose(); 371 | 372 | super.dispose(); 373 | } 374 | 375 | // Play highlight animation for the child located at _highlightControllers[at]. 376 | void _animateHighlightController({T at, bool forward}) { 377 | if (at == null) return; 378 | final AnimationController controller = _highlightControllers[at]; 379 | assert(!forward || controller != null); 380 | controller?.animateTo(forward ? 1 : 0, 381 | duration: _kHighlightAnimationDuration, curve: Curves.ease); 382 | } 383 | 384 | T _highlighted; 385 | set highlighted(T newValue) { 386 | if (_highlighted == newValue) return; 387 | _animateHighlightController(at: newValue, forward: true); 388 | _animateHighlightController(at: _highlighted, forward: false); 389 | _highlighted = newValue; 390 | } 391 | 392 | T _pressed; 393 | set pressed(T newValue) { 394 | if (_pressed == newValue) return; 395 | 396 | if (_pressed != null) { 397 | _pressControllers[_pressed]?.animateTo(0, 398 | duration: _kOpacityAnimationDuration, curve: Curves.ease); 399 | } 400 | if (newValue != _highlighted && newValue != null) { 401 | _pressControllers[newValue].animateTo(1, 402 | duration: _kOpacityAnimationDuration, curve: Curves.ease); 403 | } 404 | _pressed = newValue; 405 | } 406 | 407 | void didChangeSelectedViaGesture() { 408 | widget.onValueChanged(_highlighted); 409 | } 410 | 411 | T indexToKey(int index) => index == null ? null : keys[index]; 412 | 413 | @override 414 | Widget build(BuildContext context) { 415 | debugCheckHasDirectionality(context); 416 | 417 | switch (Directionality.of(context)) { 418 | case TextDirection.ltr: 419 | keys = widget.children.keys.toList(growable: false); 420 | break; 421 | case TextDirection.rtl: 422 | keys = widget.children.keys.toList().reversed.toList(growable: false); 423 | break; 424 | } 425 | 426 | return AnimatedBuilder( 427 | animation: Listenable.merge([ 428 | ..._highlightControllers.values, 429 | ..._pressControllers.values, 430 | ]), 431 | builder: (BuildContext context, Widget child) { 432 | final List children = []; 433 | for (final T currentKey in keys) { 434 | final TextStyle textStyle = 435 | DefaultTextStyle.of(context).style.copyWith( 436 | fontWeight: _highlightTween 437 | .evaluate(_highlightControllers[currentKey]), 438 | ); 439 | 440 | final Widget child = DefaultTextStyle( 441 | style: textStyle, 442 | child: Semantics( 443 | button: true, 444 | onTap: () { 445 | widget.onValueChanged(currentKey); 446 | }, 447 | inMutuallyExclusiveGroup: true, 448 | selected: widget.groupValue == currentKey, 449 | child: Opacity( 450 | opacity: _pressTween.evaluate(_pressControllers[currentKey]), 451 | // Expand the hitTest area to be as large as the Opacity widget. 452 | child: MetaData( 453 | behavior: HitTestBehavior.opaque, 454 | child: Center(child: widget.children[currentKey]), 455 | ), 456 | ), 457 | ), 458 | ); 459 | 460 | children.add(child); 461 | } 462 | 463 | final int selectedIndex = 464 | widget.groupValue == null ? null : keys.indexOf(widget.groupValue); 465 | 466 | final Widget box = _SegmentedControlRenderWidget( 467 | children: children, 468 | selectedIndex: selectedIndex, 469 | thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor, context), 470 | state: this, 471 | ); 472 | 473 | return UnconstrainedBox( 474 | constrainedAxis: Axis.horizontal, 475 | child: Container( 476 | padding: widget.padding.resolve(Directionality.of(context)), 477 | decoration: BoxDecoration( 478 | borderRadius: 479 | const BorderRadius.all(Radius.circular(_kCornerRadius)), 480 | color: CupertinoDynamicColor.resolve( 481 | widget.backgroundColor, context), 482 | ), 483 | child: box, 484 | ), 485 | ); 486 | }, 487 | ); 488 | } 489 | } 490 | 491 | class _SegmentedControlRenderWidget extends MultiChildRenderObjectWidget { 492 | _SegmentedControlRenderWidget({ 493 | Key key, 494 | List children = const [], 495 | @required this.selectedIndex, 496 | @required this.thumbColor, 497 | @required this.state, 498 | }) : super(key: key, children: children); 499 | 500 | final int selectedIndex; 501 | final Color thumbColor; 502 | final _SegmentedControlState state; 503 | 504 | @override 505 | RenderObject createRenderObject(BuildContext context) { 506 | return _RenderSegmentedControl( 507 | selectedIndex: selectedIndex, 508 | thumbColor: CupertinoDynamicColor.resolve(thumbColor, context), 509 | state: state, 510 | ); 511 | } 512 | 513 | @override 514 | void updateRenderObject( 515 | BuildContext context, _RenderSegmentedControl renderObject) { 516 | renderObject 517 | ..thumbColor = CupertinoDynamicColor.resolve(thumbColor, context) 518 | ..guardedSetHighlightedIndex(selectedIndex); 519 | } 520 | } 521 | 522 | class _ChildAnimationManifest { 523 | _ChildAnimationManifest({ 524 | this.opacity = 1, 525 | @required this.separatorOpacity, 526 | }) : assert(separatorOpacity != null), 527 | assert(opacity != null), 528 | separatorTween = 529 | Tween(begin: separatorOpacity, end: separatorOpacity), 530 | opacityTween = Tween(begin: opacity, end: opacity); 531 | 532 | double opacity; 533 | Tween opacityTween; 534 | double separatorOpacity; 535 | Tween separatorTween; 536 | } 537 | 538 | class _SegmentedControlContainerBoxParentData 539 | extends ContainerBoxParentData {} 540 | 541 | // The behavior of a UISegmentedControl as observed on iOS 13.1: 542 | // 543 | // 1. Tap up inside events will set the current selected index to the index of the 544 | // segment at the tap up location instantaneously (there might be animation but 545 | // the index change seems to happen before animation finishes), unless the tap 546 | // down event from the same touch event didn't happen within the segmented 547 | // control, in which case the touch event will be ignored entirely (will be 548 | // referring to these touch events as invalid touch events below). 549 | // 550 | // 2. A valid tap up event will also trigger the sliding CASpringAnimation (even 551 | // when it lands on the current segment), starting from the current `frame` 552 | // of the thumb. The previous sliding animation, if still playing, will be 553 | // removed and its velocity reset to 0. The sliding animation has a fixed 554 | // duration, regardless of the distance or transform. 555 | // 556 | // 3. When the sliding animation plays two other animations take place. In one animation 557 | // the content of the current segment gradually becomes "highlighted", turning the 558 | // font weight to semibold (CABasicAnimation, timingFunction = default, duration = 0.2). 559 | // The other is the separator fadein/fadeout animation. 560 | // 561 | // 4. A tap down event on the segment pointed to by the current selected 562 | // index will trigger a CABasicAnimation that shrinks the thumb to 95% of its 563 | // original size, even if the sliding animation is still playing. The 564 | /// corresponding tap up event inverts the process (eyeballed). 565 | // 566 | // 5. A tap down event on other segments will trigger a CABasicAnimation 567 | // (timingFunction = default, duration = 0.47.) that fades out the content, 568 | // eventually reducing the alpha of that segment to 20% unless interrupted by 569 | // a tap up event or the pointer moves out of the region (either outside of the 570 | // segmented control's vicinity or to a different segment). The reverse animation 571 | // has the same duration and timing function. 572 | class _RenderSegmentedControl extends RenderBox 573 | with 574 | ContainerRenderObjectMixin>, 576 | RenderBoxContainerDefaultsMixin> { 578 | _RenderSegmentedControl({ 579 | @required int selectedIndex, 580 | @required Color thumbColor, 581 | @required this.state, 582 | }) : _highlightedIndex = selectedIndex, 583 | _thumbColor = thumbColor, 584 | assert(state != null) { 585 | state.drag 586 | ..onDown = _onDown 587 | ..onUpdate = _onUpdate 588 | ..onEnd = _onEnd 589 | ..onCancel = _onCancel; 590 | 591 | state.tap.onTapUp = _onTapUp; 592 | // Empty callback to enable the long press recognizer. 593 | state.longPress.onLongPress = () {}; 594 | } 595 | 596 | final _SegmentedControlState state; 597 | 598 | Map _childAnimations = 599 | {}; 600 | 601 | // The current **Unscaled** Thumb Rect. 602 | Rect currentThumbRect; 603 | 604 | Tween _currentThumbTween; 605 | 606 | Tween _thumbScaleTween = 607 | Tween(begin: _kMinThumbScale, end: 1); 608 | double currentThumbScale = 1; 609 | 610 | // The current position of the active drag pointer. 611 | Offset _localDragOffset; 612 | // Whether the current drag gesture started on a selected segment. 613 | bool _startedOnSelectedSegment; 614 | 615 | @override 616 | void insert(RenderBox child, {RenderBox after}) { 617 | super.insert(child, after: after); 618 | if (_childAnimations == null) return; 619 | 620 | assert(_childAnimations[child] == null); 621 | _childAnimations[child] = _ChildAnimationManifest(separatorOpacity: 1); 622 | } 623 | 624 | @override 625 | void remove(RenderBox child) { 626 | super.remove(child); 627 | _childAnimations?.remove(child); 628 | } 629 | 630 | @override 631 | void attach(PipelineOwner owner) { 632 | super.attach(owner); 633 | state.thumbController.addListener(markNeedsPaint); 634 | state.thumbScaleController.addListener(markNeedsPaint); 635 | state.separatorOpacityController.addListener(markNeedsPaint); 636 | } 637 | 638 | @override 639 | void detach() { 640 | state.thumbController.removeListener(markNeedsPaint); 641 | state.thumbScaleController.removeListener(markNeedsPaint); 642 | state.separatorOpacityController.removeListener(markNeedsPaint); 643 | super.detach(); 644 | } 645 | 646 | // Indicates whether selectedIndex has changed and animations need to be updated. 647 | // when true some animation tweens will be updated in paint phase. 648 | bool _needsThumbAnimationUpdate = false; 649 | 650 | int get highlightedIndex => _highlightedIndex; 651 | int _highlightedIndex; 652 | set highlightedIndex(int value) { 653 | if (_highlightedIndex == value) { 654 | return; 655 | } 656 | 657 | _needsThumbAnimationUpdate = true; 658 | _highlightedIndex = value; 659 | 660 | state.thumbController.animateWith(_kThumbSpringAnimationSimulation); 661 | 662 | state.separatorOpacityController.reset(); 663 | state.separatorOpacityController.animateTo( 664 | 1, 665 | duration: _kSpringAnimationDuration, 666 | curve: Curves.ease, 667 | ); 668 | 669 | state.highlighted = state.indexToKey(value); 670 | markNeedsPaint(); 671 | markNeedsSemanticsUpdate(); 672 | } 673 | 674 | void guardedSetHighlightedIndex(int value) { 675 | // Ignore set highlightedIndex when the user is dragging the thumb around. 676 | if (_startedOnSelectedSegment == true) return; 677 | highlightedIndex = value; 678 | } 679 | 680 | int get pressedIndex => _pressedIndex; 681 | int _pressedIndex; 682 | set pressedIndex(int value) { 683 | if (_pressedIndex == value) { 684 | return; 685 | } 686 | 687 | assert(value == null || (value >= 0 && value < childCount)); 688 | 689 | _pressedIndex = value; 690 | state.pressed = state.indexToKey(value); 691 | } 692 | 693 | Color get thumbColor => _thumbColor; 694 | Color _thumbColor; 695 | set thumbColor(Color value) { 696 | if (_thumbColor == value) { 697 | return; 698 | } 699 | _thumbColor = value; 700 | markNeedsPaint(); 701 | } 702 | 703 | double get totalSeparatorWidth => 704 | (_kSeparatorInset.horizontal + _kSeparatorWidth) * (childCount - 1); 705 | 706 | @override 707 | void handleEvent(PointerEvent event, BoxHitTestEntry entry) { 708 | assert(debugHandleEvent(event, entry)); 709 | if (event is PointerDownEvent) { 710 | state.tap.addPointer(event); 711 | state.longPress.addPointer(event); 712 | state.drag.addPointer(event); 713 | } 714 | } 715 | 716 | int indexFromLocation(Offset location) { 717 | return childCount == 0 718 | ? null 719 | // This assumes all children have the same width. 720 | : ((location.dx / (size.width / childCount)) 721 | .floor() 722 | .clamp(0, childCount - 1) as int); 723 | } 724 | 725 | void _onTapUp(TapUpDetails details) { 726 | highlightedIndex = indexFromLocation(details.localPosition); 727 | state.didChangeSelectedViaGesture(); 728 | } 729 | 730 | void _onDown(DragDownDetails details) { 731 | assert(size.contains(details.localPosition)); 732 | _localDragOffset = details.localPosition; 733 | final int index = indexFromLocation(_localDragOffset); 734 | _startedOnSelectedSegment = index == highlightedIndex; 735 | pressedIndex = index; 736 | 737 | if (_startedOnSelectedSegment) { 738 | _playThumbScaleAnimation(isExpanding: false); 739 | } 740 | } 741 | 742 | void _onUpdate(DragUpdateDetails details) { 743 | _localDragOffset = details.localPosition; 744 | final int newIndex = indexFromLocation(_localDragOffset); 745 | 746 | if (_startedOnSelectedSegment) { 747 | highlightedIndex = newIndex; 748 | pressedIndex = newIndex; 749 | } else { 750 | pressedIndex = _hasDraggedTooFar(details) ? null : newIndex; 751 | } 752 | } 753 | 754 | void _onEnd(DragEndDetails details) { 755 | if (_startedOnSelectedSegment) { 756 | _playThumbScaleAnimation(isExpanding: true); 757 | state.didChangeSelectedViaGesture(); 758 | } 759 | 760 | if (pressedIndex != null) { 761 | highlightedIndex = pressedIndex; 762 | state.didChangeSelectedViaGesture(); 763 | } 764 | pressedIndex = null; 765 | _localDragOffset = null; 766 | _startedOnSelectedSegment = null; 767 | } 768 | 769 | void _onCancel() { 770 | if (_startedOnSelectedSegment) { 771 | _playThumbScaleAnimation(isExpanding: true); 772 | } 773 | 774 | _localDragOffset = null; 775 | pressedIndex = null; 776 | _startedOnSelectedSegment = null; 777 | } 778 | 779 | void _playThumbScaleAnimation({@required bool isExpanding}) { 780 | assert(isExpanding != null); 781 | _thumbScaleTween = Tween( 782 | begin: currentThumbScale, end: isExpanding ? 1 : _kMinThumbScale); 783 | state.thumbScaleController.animateWith(_kThumbSpringAnimationSimulation); 784 | } 785 | 786 | bool _hasDraggedTooFar(DragUpdateDetails details) { 787 | final Offset offCenter = 788 | details.localPosition - Offset(size.width / 2, size.height / 2); 789 | return math.pow(math.max(0, offCenter.dx.abs() - size.width / 2), 2) + 790 | math.pow(math.max(0, offCenter.dy.abs() - size.height / 2), 2) > 791 | _kTouchYDistanceThreshold; 792 | } 793 | 794 | @override 795 | double computeMinIntrinsicWidth(double height) { 796 | RenderBox child = firstChild; 797 | double maxMinChildWidth = 0; 798 | while (child != null) { 799 | final _SegmentedControlContainerBoxParentData childParentData = 800 | child.parentData as _SegmentedControlContainerBoxParentData; 801 | final double childWidth = child.getMinIntrinsicWidth(height); 802 | maxMinChildWidth = math.max(maxMinChildWidth, childWidth); 803 | child = childParentData.nextSibling; 804 | } 805 | return (maxMinChildWidth + 2 * _kSegmentMinPadding) * childCount + 806 | totalSeparatorWidth; 807 | } 808 | 809 | @override 810 | double computeMaxIntrinsicWidth(double height) { 811 | RenderBox child = firstChild; 812 | double maxMaxChildWidth = 0; 813 | while (child != null) { 814 | final _SegmentedControlContainerBoxParentData childParentData = 815 | child.parentData as _SegmentedControlContainerBoxParentData; 816 | final double childWidth = child.getMaxIntrinsicWidth(height); 817 | maxMaxChildWidth = math.max(maxMaxChildWidth, childWidth); 818 | child = childParentData.nextSibling; 819 | } 820 | return (maxMaxChildWidth + 2 * _kSegmentMinPadding) * childCount + 821 | totalSeparatorWidth; 822 | } 823 | 824 | @override 825 | double computeMinIntrinsicHeight(double width) { 826 | RenderBox child = firstChild; 827 | double maxMinChildHeight = 0; 828 | while (child != null) { 829 | final _SegmentedControlContainerBoxParentData childParentData = 830 | child.parentData as _SegmentedControlContainerBoxParentData; 831 | final double childHeight = child.getMinIntrinsicHeight(width); 832 | maxMinChildHeight = math.max(maxMinChildHeight, childHeight); 833 | child = childParentData.nextSibling; 834 | } 835 | return maxMinChildHeight; 836 | } 837 | 838 | @override 839 | double computeMaxIntrinsicHeight(double width) { 840 | RenderBox child = firstChild; 841 | double maxMaxChildHeight = 0; 842 | while (child != null) { 843 | final _SegmentedControlContainerBoxParentData childParentData = 844 | child.parentData as _SegmentedControlContainerBoxParentData; 845 | final double childHeight = child.getMaxIntrinsicHeight(width); 846 | maxMaxChildHeight = math.max(maxMaxChildHeight, childHeight); 847 | child = childParentData.nextSibling; 848 | } 849 | return maxMaxChildHeight; 850 | } 851 | 852 | @override 853 | double computeDistanceToActualBaseline(TextBaseline baseline) { 854 | return defaultComputeDistanceToHighestActualBaseline(baseline); 855 | } 856 | 857 | @override 858 | void setupParentData(RenderBox child) { 859 | if (child.parentData is! _SegmentedControlContainerBoxParentData) { 860 | child.parentData = _SegmentedControlContainerBoxParentData(); 861 | } 862 | } 863 | 864 | @override 865 | void performLayout() { 866 | final BoxConstraints constraints = this.constraints; 867 | double childWidth = 868 | (constraints.minWidth - totalSeparatorWidth) / childCount; 869 | double maxHeight = _kMinSegmentedControlHeight; 870 | 871 | for (final RenderBox child in getChildrenAsList()) { 872 | childWidth = math.max( 873 | childWidth, 874 | child.getMaxIntrinsicWidth(double.infinity) + 875 | 2 * _kSegmentMinPadding); 876 | } 877 | 878 | childWidth = math.min( 879 | childWidth, 880 | (constraints.maxWidth - totalSeparatorWidth) / childCount, 881 | ); 882 | 883 | RenderBox child = firstChild; 884 | while (child != null) { 885 | final double boxHeight = child.getMaxIntrinsicHeight(childWidth); 886 | maxHeight = math.max(maxHeight, boxHeight); 887 | child = childAfter(child); 888 | } 889 | 890 | constraints.constrainHeight(maxHeight); 891 | 892 | final BoxConstraints childConstraints = BoxConstraints.tightFor( 893 | width: childWidth, 894 | height: maxHeight, 895 | ); 896 | 897 | // Layout children. 898 | child = firstChild; 899 | while (child != null) { 900 | child.layout(childConstraints, parentUsesSize: true); 901 | child = childAfter(child); 902 | } 903 | 904 | double start = 0; 905 | child = firstChild; 906 | 907 | while (child != null) { 908 | final _SegmentedControlContainerBoxParentData childParentData = 909 | child.parentData as _SegmentedControlContainerBoxParentData; 910 | final Offset childOffset = Offset(start, 0); 911 | childParentData.offset = childOffset; 912 | start += 913 | child.size.width + _kSeparatorWidth + _kSeparatorInset.horizontal; 914 | child = childAfter(child); 915 | } 916 | 917 | size = constraints.constrain( 918 | Size(childWidth * childCount + totalSeparatorWidth, maxHeight)); 919 | } 920 | 921 | @override 922 | void paint(PaintingContext context, Offset offset) { 923 | final List children = getChildrenAsList(); 924 | 925 | // Paint thumb if highlightedIndex is not null. 926 | if (highlightedIndex != null) { 927 | if (_childAnimations == null) { 928 | _childAnimations = {}; 929 | for (int i = 0; i < childCount - 1; i += 1) { 930 | // The separator associated with the last child will not be painted (unless 931 | // a new trailing segment is added), and its opacity will always be 1. 932 | final bool shouldFadeOut = 933 | i == highlightedIndex || i == highlightedIndex - 1; 934 | final RenderBox child = children[i]; 935 | _childAnimations[child] = 936 | _ChildAnimationManifest(separatorOpacity: shouldFadeOut ? 0 : 1); 937 | } 938 | } 939 | 940 | final RenderBox selectedChild = children[highlightedIndex]; 941 | 942 | final _SegmentedControlContainerBoxParentData childParentData = 943 | selectedChild.parentData as _SegmentedControlContainerBoxParentData; 944 | final Rect unscaledThumbTargetRect = _kThumbInsets 945 | .inflateRect(childParentData.offset & selectedChild.size); 946 | 947 | // Update related Tweens before animation update phase. 948 | if (_needsThumbAnimationUpdate) { 949 | // Needs to ensure _currentThumbRect is valid. 950 | _currentThumbTween = RectTween( 951 | begin: currentThumbRect ?? unscaledThumbTargetRect, 952 | end: unscaledThumbTargetRect); 953 | 954 | for (int i = 0; i < childCount - 1; i += 1) { 955 | // The separator associated with the last child will not be painted (unless 956 | // a new segment is appended to the child list), and its opacity will always be 1. 957 | final bool shouldFadeOut = 958 | i == highlightedIndex || i == highlightedIndex - 1; 959 | final RenderBox child = children[i]; 960 | final _ChildAnimationManifest manifest = _childAnimations[child]; 961 | assert(manifest != null); 962 | manifest.separatorTween = Tween( 963 | begin: manifest.separatorOpacity, 964 | end: shouldFadeOut ? 0 : 1, 965 | ); 966 | } 967 | 968 | _needsThumbAnimationUpdate = false; 969 | } else if (_currentThumbTween != null && 970 | unscaledThumbTargetRect != _currentThumbTween.begin) { 971 | _currentThumbTween = RectTween( 972 | begin: _currentThumbTween.begin, end: unscaledThumbTargetRect); 973 | } 974 | 975 | for (int index = 0; index < childCount - 1; index += 1) { 976 | _paintSeparator(context, offset, children[index]); 977 | } 978 | 979 | currentThumbRect = _currentThumbTween?.evaluate(state.thumbController) ?? 980 | unscaledThumbTargetRect; 981 | 982 | currentThumbScale = _thumbScaleTween.evaluate(state.thumbScaleController); 983 | 984 | final Rect thumbRect = Rect.fromCenter( 985 | center: currentThumbRect.center, 986 | width: currentThumbRect.width * currentThumbScale, 987 | height: currentThumbRect.height * currentThumbScale, 988 | ); 989 | 990 | _paintThumb(context, offset, thumbRect); 991 | } else { 992 | // Reset all animations when there's no thumb. 993 | currentThumbRect = null; 994 | _childAnimations = null; 995 | 996 | for (int index = 0; index < childCount - 1; index += 1) { 997 | _paintSeparator(context, offset, children[index]); 998 | } 999 | } 1000 | 1001 | for (int index = 0; index < children.length; index++) { 1002 | _paintChild(context, offset, children[index], index); 1003 | } 1004 | } 1005 | 1006 | // Paint the separator to the right of the given child. 1007 | void _paintSeparator( 1008 | PaintingContext context, Offset offset, RenderBox child) { 1009 | assert(child != null); 1010 | final _SegmentedControlContainerBoxParentData childParentData = 1011 | child.parentData as _SegmentedControlContainerBoxParentData; 1012 | 1013 | final Paint paint = Paint(); 1014 | 1015 | final _ChildAnimationManifest manifest = 1016 | _childAnimations == null ? null : _childAnimations[child]; 1017 | final double opacity = 1018 | manifest?.separatorTween?.evaluate(state.separatorOpacityController) ?? 1019 | 1; 1020 | manifest?.separatorOpacity = opacity; 1021 | paint.color = 1022 | _kSeparatorColor.withOpacity(_kSeparatorColor.opacity * opacity); 1023 | 1024 | final Rect childRect = (childParentData.offset + offset) & child.size; 1025 | final Rect separatorRect = _kSeparatorInset.deflateRect( 1026 | childRect.topRight & 1027 | Size(_kSeparatorInset.horizontal + _kSeparatorWidth, 1028 | child.size.height), 1029 | ); 1030 | 1031 | context.canvas.drawRRect( 1032 | RRect.fromRectAndRadius(separatorRect, _kSeparatorRadius), 1033 | paint, 1034 | ); 1035 | } 1036 | 1037 | void _paintChild( 1038 | PaintingContext context, Offset offset, RenderBox child, int childIndex) { 1039 | assert(child != null); 1040 | final _SegmentedControlContainerBoxParentData childParentData = 1041 | child.parentData as _SegmentedControlContainerBoxParentData; 1042 | context.paintChild(child, childParentData.offset + offset); 1043 | } 1044 | 1045 | void _paintThumb(PaintingContext context, Offset offset, Rect thumbRect) { 1046 | // Colors extracted from https://developer.apple.com/design/resources/. 1047 | const List thumbShadow = [ 1048 | BoxShadow( 1049 | color: Color(0x1F000000), 1050 | offset: Offset(0, 3), 1051 | blurRadius: 8, 1052 | ), 1053 | BoxShadow( 1054 | color: Color(0x0A000000), 1055 | offset: Offset(0, 3), 1056 | blurRadius: 1, 1057 | ), 1058 | ]; 1059 | 1060 | final RRect thumbRRect = 1061 | RRect.fromRectAndRadius(thumbRect.shift(offset), _kThumbRadius); 1062 | 1063 | for (final BoxShadow shadow in thumbShadow) { 1064 | context.canvas 1065 | .drawRRect(thumbRRect.shift(shadow.offset), shadow.toPaint()); 1066 | } 1067 | 1068 | context.canvas.drawRRect( 1069 | thumbRRect.inflate(0.5), 1070 | Paint()..color = const Color(0x0A000000), 1071 | ); 1072 | 1073 | context.canvas.drawRRect( 1074 | thumbRRect, 1075 | Paint()..color = thumbColor, 1076 | ); 1077 | } 1078 | 1079 | @override 1080 | bool hitTestChildren(BoxHitTestResult result, {@required Offset position}) { 1081 | assert(position != null); 1082 | RenderBox child = lastChild; 1083 | while (child != null) { 1084 | final _SegmentedControlContainerBoxParentData childParentData = 1085 | child.parentData as _SegmentedControlContainerBoxParentData; 1086 | if ((childParentData.offset & child.size).contains(position)) { 1087 | final Offset center = (Offset.zero & child.size).center; 1088 | return result.addWithRawTransform( 1089 | transform: MatrixUtils.forceToPoint(center), 1090 | position: center, 1091 | hitTest: (BoxHitTestResult result, Offset position) { 1092 | assert(position == center); 1093 | return child.hitTest(result, position: center); 1094 | }, 1095 | ); 1096 | } 1097 | child = childParentData.previousSibling; 1098 | } 1099 | return false; 1100 | } 1101 | } 1102 | --------------------------------------------------------------------------------