diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d009f5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Firebase – this is necessary if you use Firebase since it could otherwise +# lead to leaking of private certificates to your repository, which is no bueno. +.firebase/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..140b929 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb + channel: stable + +project_type: app diff --git a/README - Copy.md b/README - Copy.md new file mode 100644 index 0000000..9e1f386 --- /dev/null +++ b/README - Copy.md @@ -0,0 +1,7 @@ +# BHARAT TRACKER + +A new Flutter project. + +## Getting Started + +FlutterFlow projects are built to run on the Flutter _stable_ release. diff --git a/README.md b/README.md index 39b043f..9e1f386 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ -# Pro-Planet -mobile apllication nudging user behaviour for betterment of planet earth +# BHARAT TRACKER + +A new Flutter project. + +## Getting Started + +FlutterFlow projects are built to run on the Flutter _stable_ release. diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..50cacb5 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,82 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +apply plugin: 'com.google.gms.google-services' // Google Services plugin + + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +android { + compileSdkVersion 33 + + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + checkReleaseBuilds false + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.mycompany.bharattracker" + minSdkVersion 21 + targetSdkVersion 33 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..3994956 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "649830357383", + "project_id": "bharat-tracker", + "storage_bucket": "bharat-tracker.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:649830357383:android:9ccc05766e448f128fdb22", + "android_client_info": { + "package_name": "com.mycompany.bharattracker" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCXxrwNpme1GRhCmK5-6tq1wQP9SBONEnw" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..55394bb --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e92e514 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt b/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt new file mode 100644 index 0000000..7ccf847 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/my_project/MainActivity.kt @@ -0,0 +1,6 @@ +package com.mycompany.bharattracker + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..f93aa34 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..d089bd9 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + BHARAT TRACKER + + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d74aa35 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..55394bb --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..dacf67c --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,33 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.3.8' // Google Services plugin + + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..498c33b --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx4608m +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6b66533 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..f6e4cd3 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,25 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} \ No newline at end of file diff --git a/assets/audios/favicon.png b/assets/audios/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/audios/favicon.png differ diff --git a/assets/fonts/favicon.png b/assets/fonts/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/fonts/favicon.png differ diff --git a/assets/images/@4xff_badgeDesign_dark_small.png b/assets/images/@4xff_badgeDesign_dark_small.png new file mode 100644 index 0000000..c95dd2a Binary files /dev/null and b/assets/images/@4xff_badgeDesign_dark_small.png differ diff --git a/assets/images/@4xff_badgeDesign_light_small.png b/assets/images/@4xff_badgeDesign_light_small.png new file mode 100644 index 0000000..28c469e Binary files /dev/null and b/assets/images/@4xff_badgeDesign_light_small.png differ diff --git a/assets/images/app_launcher_icon.png b/assets/images/app_launcher_icon.png new file mode 100644 index 0000000..b8a3fb7 Binary files /dev/null and b/assets/images/app_launcher_icon.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/splash.gif b/assets/images/splash.gif new file mode 100644 index 0000000..cb9d083 Binary files /dev/null and b/assets/images/splash.gif differ diff --git a/assets/lottie_animations/favicon.png b/assets/lottie_animations/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/lottie_animations/favicon.png differ diff --git a/assets/pdfs/favicon.png b/assets/pdfs/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/pdfs/favicon.png differ diff --git a/assets/rive_animations/favicon.png b/assets/rive_animations/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/rive_animations/favicon.png differ diff --git a/assets/videos/favicon.png b/assets/videos/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assets/videos/favicon.png differ diff --git a/flutter_native_splash.yaml b/flutter_native_splash.yaml new file mode 100644 index 0000000..2e37c00 --- /dev/null +++ b/flutter_native_splash.yaml @@ -0,0 +1,9 @@ +flutter_native_splash: + color: "#000000" + color_dark: "#000000" + android_12: + color: "#000000" + color_dark: "#000000" + android: true + ios: true + web: false diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..eb86478 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +build/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..d57061d --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/ImageNotification/Info.plist b/ios/ImageNotification/Info.plist new file mode 100644 index 0000000..9f28060 --- /dev/null +++ b/ios/ImageNotification/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ImageNotification + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/ios/ImageNotification/NotificationService.swift b/ios/ImageNotification/NotificationService.swift new file mode 100644 index 0000000..d16342e --- /dev/null +++ b/ios/ImageNotification/NotificationService.swift @@ -0,0 +1,27 @@ +import FirebaseMessaging +import UserNotifications + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = request.content + .mutableCopy() as? UNMutableNotificationContent + guard let bestAttemptContent = bestAttemptContent else { return } + FIRMessagingExtensionHelper().populateNotificationContent( + bestAttemptContent, + withContentHandler: contentHandler) + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..ad0c74b --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,45 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + + end + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5fd6c19 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 60CEEDB492F577A653BC6E45 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8285832C7E7A3262A5A897C /* Pods_Runner.framework */; }; + 6426BE1225AC0EBD0080CC2A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6426BE1125AC0EBD0080CC2A /* GoogleService-Info.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 6436409A27A31CD800820AF7 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6436409C27A31CD800820AF7 /* InfoPlist.strings */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 64E3CA8B26FC10FF0070C5E0 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 3DCE399542E9B8C63BEA38E6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 6426BE1125AC0EBD0080CC2A /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A9C17DEFA21738E5FBD7F54A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B8285832C7E7A3262A5A897C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FA9015B13AC8FF8CBD8DD673 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 60CEEDB492F577A653BC6E45 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33098FC5D2106DCB44CAEA79 /* Pods */ = { + isa = PBXGroup; + children = ( + FA9015B13AC8FF8CBD8DD673 /* Pods-Runner.debug.xcconfig */, + A9C17DEFA21738E5FBD7F54A /* Pods-Runner.release.xcconfig */, + 3DCE399542E9B8C63BEA38E6 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 33098FC5D2106DCB44CAEA79 /* Pods */, + CC79BB5F4B7E86C62E930C2C /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 64E3CA8B26FC10FF0070C5E0 /* Runner.entitlements */, + 6426BE1125AC0EBD0080CC2A /* GoogleService-Info.plist */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 6436409C27A31CD800820AF7 /* InfoPlist.strings */, + ); + path = Runner; + sourceTree = ""; + }; + CC79BB5F4B7E86C62E930C2C /* Frameworks */ = { + isa = PBXGroup; + children = ( + B8285832C7E7A3262A5A897C /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 3EA46EF11B42FC3BFF6A4BBC /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 2838A0ADC44730F0A2303575 /* [CP] Embed Pods Frameworks */, + 55067970987309DE09E9DE72 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 6436409A27A31CD800820AF7 /* InfoPlist.strings in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 6426BE1225AC0EBD0080CC2A /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2838A0ADC44730F0A2303575 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 3EA46EF11B42FC3BFF6A4BBC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 55067970987309DE09E9DE72 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 6436409C27A31CD800820AF7 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mycompany.bharattracker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mycompany.bharattracker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.mycompany.bharattracker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..f52eb22 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,14 @@ +import UIKit + +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..50376a9 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyDwtEmHVePZhTe_KEQdnKU49a3QfUCQIMA + GCM_SENDER_ID + 649830357383 + PLIST_VERSION + 1 + BUNDLE_ID + com.mycompany.bharattracker + PROJECT_ID + bharat-tracker + STORAGE_BUCKET + bharat-tracker.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:649830357383:ios:83696ad9384bf7f08fdb22 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..2d1c2ec --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,81 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + BHARAT TRACKER + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + BHARAT TRACKER + CFBundlePackageType + APPL + + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + + + + + + CFBundleTypeRole + Editor + CFBundleURLName + bharattracker.com + CFBundleURLSchemes + + bharattracker + + + + FlutterDeepLinkingEnabled + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..6631ffa --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/app_state.dart b/lib/app_state.dart new file mode 100644 index 0000000..87a20a9 --- /dev/null +++ b/lib/app_state.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import '/backend/backend.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'flutter_flow/flutter_flow_util.dart'; + +class FFAppState extends ChangeNotifier { + static FFAppState _instance = FFAppState._internal(); + + factory FFAppState() { + return _instance; + } + + FFAppState._internal(); + + static void reset() { + _instance = FFAppState._internal(); + } + + Future initializePersistedState() async {} + + void update(VoidCallback callback) { + callback(); + notifyListeners(); + } + + List _imagelist = [ + 'https://img.freepik.com/premium-photo/supermarket-paper-bag-full-healthy-food_79782-1632.jpg?size=626&ext=jpg', + 'https://images.app.goo.gl/JupX4KMTWKbvbnFw8', + 'https://images.app.goo.gl/yjMRYzdpXjLURbw78' + ]; + List get imagelist => _imagelist; + set imagelist(List _value) { + _imagelist = _value; + } + + void addToImagelist(String _value) { + _imagelist.add(_value); + } + + void removeFromImagelist(String _value) { + _imagelist.remove(_value); + } + + void removeAtIndexFromImagelist(int _index) { + _imagelist.removeAt(_index); + } + + void updateImagelistAtIndex( + int _index, + String Function(String) updateFn, + ) { + _imagelist[_index] = updateFn(_imagelist[_index]); + } + + void insertAtIndexInImagelist(int _index, String _value) { + _imagelist.insert(_index, _value); + } +} + +LatLng? _latLngFromString(String? val) { + if (val == null) { + return null; + } + final split = val.split(','); + final lat = double.parse(split.first); + final lng = double.parse(split.last); + return LatLng(lat, lng); +} + +void _safeInit(Function() initializeField) { + try { + initializeField(); + } catch (_) {} +} + +Future _safeInitAsync(Function() initializeField) async { + try { + await initializeField(); + } catch (_) {} +} diff --git a/lib/auth/auth_manager.dart b/lib/auth/auth_manager.dart new file mode 100644 index 0000000..b39c357 --- /dev/null +++ b/lib/auth/auth_manager.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import 'base_auth_user_provider.dart'; + +abstract class AuthManager { + Future signOut(); + Future deleteUser(BuildContext context); + Future updateEmail({required String email, required BuildContext context}); + Future resetPassword({required String email, required BuildContext context}); + Future sendEmailVerification() async => currentUser?.sendEmailVerification(); + Future refreshUser() async => currentUser?.refreshUser(); +} + +mixin EmailSignInManager on AuthManager { + Future signInWithEmail( + BuildContext context, + String email, + String password, + ); + + Future createAccountWithEmail( + BuildContext context, + String email, + String password, + ); +} + +mixin AnonymousSignInManager on AuthManager { + Future signInAnonymously(BuildContext context); +} + +mixin AppleSignInManager on AuthManager { + Future signInWithApple(BuildContext context); +} + +mixin GoogleSignInManager on AuthManager { + Future signInWithGoogle(BuildContext context); +} + +mixin JwtSignInManager on AuthManager { + Future signInWithJwtToken( + BuildContext context, + String jwtToken, + ); +} + +mixin PhoneSignInManager on AuthManager { + Future beginPhoneAuth({ + required BuildContext context, + required String phoneNumber, + required void Function(BuildContext) onCodeSent, + }); + + Future verifySmsCode({ + required BuildContext context, + required String smsCode, + }); +} + +mixin FacebookSignInManager on AuthManager { + Future signInWithFacebook(BuildContext context); +} + +mixin MicrosoftSignInManager on AuthManager { + Future signInWithMicrosoft( + BuildContext context, + List scopes, + String tenantId, + ); +} + +mixin GithubSignInManager on AuthManager { + Future signInWithGithub(BuildContext context); +} diff --git a/lib/auth/base_auth_user_provider.dart b/lib/auth/base_auth_user_provider.dart new file mode 100644 index 0000000..1578da1 --- /dev/null +++ b/lib/auth/base_auth_user_provider.dart @@ -0,0 +1,36 @@ +class AuthUserInfo { + const AuthUserInfo({ + this.uid, + this.email, + this.displayName, + this.photoUrl, + this.phoneNumber, + }); + + final String? uid; + final String? email; + final String? displayName; + final String? photoUrl; + final String? phoneNumber; +} + +abstract class BaseAuthUser { + bool get loggedIn; + bool get emailVerified; + + AuthUserInfo get authUserInfo; + + Future? delete(); + Future? updateEmail(String email); + Future? sendEmailVerification(); + Future refreshUser() async {} + + String? get uid => authUserInfo.uid; + String? get email => authUserInfo.email; + String? get displayName => authUserInfo.displayName; + String? get photoUrl => authUserInfo.photoUrl; + String? get phoneNumber => authUserInfo.phoneNumber; +} + +BaseAuthUser? currentUser; +bool get loggedIn => currentUser?.loggedIn ?? false; diff --git a/lib/auth/firebase_auth/anonymous_auth.dart b/lib/auth/firebase_auth/anonymous_auth.dart new file mode 100644 index 0000000..bf6502c --- /dev/null +++ b/lib/auth/firebase_auth/anonymous_auth.dart @@ -0,0 +1,4 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +Future anonymousSignInFunc() => + FirebaseAuth.instance.signInAnonymously(); diff --git a/lib/auth/firebase_auth/apple_auth.dart b/lib/auth/firebase_auth/apple_auth.dart new file mode 100644 index 0000000..39fc206 --- /dev/null +++ b/lib/auth/firebase_auth/apple_auth.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:crypto/crypto.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +/// Generates a cryptographically secure random nonce, to be included in a +/// credential request. +String generateNonce([int length = 32]) { + final charset = + '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; + final random = Random.secure(); + return List.generate(length, (_) => charset[random.nextInt(charset.length)]) + .join(); +} + +/// Returns the sha256 hash of [input] in hex notation. +String sha256ofString(String input) { + final bytes = utf8.encode(input); + final digest = sha256.convert(bytes); + return digest.toString(); +} + +Future appleSignIn() async { + if (kIsWeb) { + final provider = OAuthProvider("apple.com") + ..addScope('email') + ..addScope('name'); + + // Sign in the user with Firebase. + return await FirebaseAuth.instance.signInWithPopup(provider); + } + // To prevent replay attacks with the credential returned from Apple, we + // include a nonce in the credential request. When signing in in with + // Firebase, the nonce in the id token returned by Apple, is expected to + // match the sha256 hash of `rawNonce`. + final rawNonce = generateNonce(); + final nonce = sha256ofString(rawNonce); + + // Request credential for the currently signed in Apple account. + final appleCredential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + nonce: nonce, + ); + + // Create an `OAuthCredential` from the credential returned by Apple. + final oauthCredential = OAuthProvider("apple.com").credential( + idToken: appleCredential.identityToken, + rawNonce: rawNonce, + ); + + // Sign in the user with Firebase. If the nonce we generated earlier does + // not match the nonce in `appleCredential.identityToken`, sign in will fail. + final user = + await FirebaseAuth.instance.signInWithCredential(oauthCredential); + + final displayName = [appleCredential.givenName, appleCredential.familyName] + .where((name) => name != null) + .join(' '); + + // The display name does not automatically come with the user. + if (displayName.isNotEmpty) { + await user.user?.updateDisplayName(displayName); + } + + return user; +} diff --git a/lib/auth/firebase_auth/auth_util.dart b/lib/auth/firebase_auth/auth_util.dart new file mode 100644 index 0000000..d93ab9c --- /dev/null +++ b/lib/auth/firebase_auth/auth_util.dart @@ -0,0 +1,73 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import '../auth_manager.dart'; +import '../base_auth_user_provider.dart'; +import '../../flutter_flow/flutter_flow_util.dart'; + +import '/backend/backend.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:stream_transform/stream_transform.dart'; +import 'firebase_auth_manager.dart'; + +export 'firebase_auth_manager.dart'; + +final _authManager = FirebaseAuthManager(); +FirebaseAuthManager get authManager => _authManager; + +String get currentUserEmail => + currentUserDocument?.email ?? currentUser?.email ?? ''; + +String get currentUserUid => currentUser?.uid ?? ''; + +String get currentUserDisplayName => + currentUserDocument?.displayName ?? currentUser?.displayName ?? ''; + +String get currentUserPhoto => + currentUserDocument?.photoUrl ?? currentUser?.photoUrl ?? ''; + +String get currentPhoneNumber => + currentUserDocument?.phoneNumber ?? currentUser?.phoneNumber ?? ''; + +String get currentJwtToken => _currentJwtToken ?? ''; + +bool get currentUserEmailVerified => currentUser?.emailVerified ?? false; + +/// Create a Stream that listens to the current user's JWT Token, since Firebase +/// generates a new token every hour. +String? _currentJwtToken; +final jwtTokenStream = FirebaseAuth.instance + .idTokenChanges() + .map((user) async => _currentJwtToken = await user?.getIdToken()) + .asBroadcastStream(); + +DocumentReference? get currentUserReference => + loggedIn ? UsersRecord.collection.doc(currentUser!.uid) : null; + +UsersRecord? currentUserDocument; +final authenticatedUserStream = FirebaseAuth.instance + .authStateChanges() + .map((user) => user?.uid ?? '') + .switchMap( + (uid) => uid.isEmpty + ? Stream.value(null) + : UsersRecord.getDocument(UsersRecord.collection.doc(uid)) + .handleError((_) {}), + ) + .map((user) => currentUserDocument = user) + .asBroadcastStream(); + +class AuthUserStreamWidget extends StatelessWidget { + const AuthUserStreamWidget({Key? key, required this.builder}) + : super(key: key); + + final WidgetBuilder builder; + + @override + Widget build(BuildContext context) => StreamBuilder( + stream: authenticatedUserStream, + builder: (context, _) => builder(context), + ); +} diff --git a/lib/auth/firebase_auth/email_auth.dart b/lib/auth/firebase_auth/email_auth.dart new file mode 100644 index 0000000..f917ce8 --- /dev/null +++ b/lib/auth/firebase_auth/email_auth.dart @@ -0,0 +1,17 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +Future emailSignInFunc( + String email, + String password, +) => + FirebaseAuth.instance + .signInWithEmailAndPassword(email: email.trim(), password: password); + +Future emailCreateAccountFunc( + String email, + String password, +) => + FirebaseAuth.instance.createUserWithEmailAndPassword( + email: email.trim(), + password: password, + ); diff --git a/lib/auth/firebase_auth/firebase_auth_manager.dart b/lib/auth/firebase_auth/firebase_auth_manager.dart new file mode 100644 index 0000000..0fe8ac7 --- /dev/null +++ b/lib/auth/firebase_auth/firebase_auth_manager.dart @@ -0,0 +1,303 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import '../auth_manager.dart'; +import '../base_auth_user_provider.dart'; +import '../../flutter_flow/flutter_flow_util.dart'; + +import '/backend/backend.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:stream_transform/stream_transform.dart'; +import 'anonymous_auth.dart'; +import 'apple_auth.dart'; +import 'email_auth.dart'; +import 'firebase_user_provider.dart'; +import 'google_auth.dart'; +import 'jwt_token_auth.dart'; +import 'github_auth.dart'; + +export '../base_auth_user_provider.dart'; + +class FirebasePhoneAuthManager extends ChangeNotifier { + bool? _triggerOnCodeSent; + FirebaseAuthException? phoneAuthError; + // Set when using phone verification (after phone number is provided). + String? phoneAuthVerificationCode; + // Set when using phone sign in in web mode (ignored otherwise). + ConfirmationResult? webPhoneAuthConfirmationResult; + // Used for handling verification codes for phone sign in. + void Function(BuildContext)? _onCodeSent; + + bool get triggerOnCodeSent => _triggerOnCodeSent ?? false; + set triggerOnCodeSent(bool val) => _triggerOnCodeSent = val; + + void Function(BuildContext) get onCodeSent => + _onCodeSent == null ? (_) {} : _onCodeSent!; + set onCodeSent(void Function(BuildContext) func) => _onCodeSent = func; + + void update(VoidCallback callback) { + callback(); + notifyListeners(); + } +} + +class FirebaseAuthManager extends AuthManager + with + EmailSignInManager, + AnonymousSignInManager, + AppleSignInManager, + GoogleSignInManager, + GithubSignInManager, + JwtSignInManager, + PhoneSignInManager { + // Set when using phone verification (after phone number is provided). + String? _phoneAuthVerificationCode; + // Set when using phone sign in in web mode (ignored otherwise). + ConfirmationResult? _webPhoneAuthConfirmationResult; + FirebasePhoneAuthManager phoneAuthManager = FirebasePhoneAuthManager(); + + @override + Future signOut() { + return FirebaseAuth.instance.signOut(); + } + + @override + Future deleteUser(BuildContext context) async { + try { + if (!loggedIn) { + print('Error: delete user attempted with no logged in user!'); + return; + } + await currentUser?.delete(); + } on FirebaseAuthException catch (e) { + if (e.code == 'requires-recent-login') { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Too long since most recent sign in. Sign in again before deleting your account.')), + ); + } + } + } + + @override + Future updateEmail({ + required String email, + required BuildContext context, + }) async { + try { + if (!loggedIn) { + print('Error: update email attempted with no logged in user!'); + return; + } + await currentUser?.updateEmail(email); + await updateUserDocument(email: email); + } on FirebaseAuthException catch (e) { + if (e.code == 'requires-recent-login') { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Too long since most recent sign in. Sign in again before updating your email.')), + ); + } + } + } + + @override + Future resetPassword({ + required String email, + required BuildContext context, + }) async { + try { + await FirebaseAuth.instance.sendPasswordResetEmail(email: email); + } on FirebaseAuthException catch (e) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: ${e.message!}')), + ); + return null; + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Password reset email sent')), + ); + } + + @override + Future signInWithEmail( + BuildContext context, + String email, + String password, + ) => + _signInOrCreateAccount( + context, + () => emailSignInFunc(email, password), + 'EMAIL', + ); + + @override + Future createAccountWithEmail( + BuildContext context, + String email, + String password, + ) => + _signInOrCreateAccount( + context, + () => emailCreateAccountFunc(email, password), + 'EMAIL', + ); + + @override + Future signInAnonymously( + BuildContext context, + ) => + _signInOrCreateAccount(context, anonymousSignInFunc, 'ANONYMOUS'); + + @override + Future signInWithApple(BuildContext context) => + _signInOrCreateAccount(context, appleSignIn, 'APPLE'); + + @override + Future signInWithGoogle(BuildContext context) => + _signInOrCreateAccount(context, googleSignInFunc, 'GOOGLE'); + + @override + Future signInWithGithub(BuildContext context) => + _signInOrCreateAccount(context, githubSignInFunc, 'GITHUB'); + + @override + Future signInWithJwtToken( + BuildContext context, + String jwtToken, + ) => + _signInOrCreateAccount(context, () => jwtTokenSignIn(jwtToken), 'JWT'); + + void handlePhoneAuthStateChanges(BuildContext context) { + phoneAuthManager.addListener(() { + if (!context.mounted) { + return; + } + + if (phoneAuthManager.triggerOnCodeSent) { + phoneAuthManager.onCodeSent(context); + phoneAuthManager + .update(() => phoneAuthManager.triggerOnCodeSent = false); + } else if (phoneAuthManager.phoneAuthError != null) { + final e = phoneAuthManager.phoneAuthError!; + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Error: ${e.message!}'), + )); + phoneAuthManager.update(() => phoneAuthManager.phoneAuthError = null); + } + }); + } + + @override + Future beginPhoneAuth({ + required BuildContext context, + required String phoneNumber, + required void Function(BuildContext) onCodeSent, + }) async { + phoneAuthManager.update(() => phoneAuthManager.onCodeSent = onCodeSent); + if (kIsWeb) { + phoneAuthManager.webPhoneAuthConfirmationResult = + await FirebaseAuth.instance.signInWithPhoneNumber(phoneNumber); + phoneAuthManager.update(() => phoneAuthManager.triggerOnCodeSent = true); + return; + } + final completer = Completer(); + // If you'd like auto-verification, without the user having to enter the SMS + // code manually. Follow these instructions: + // * For Android: https://firebase.google.com/docs/auth/android/phone-auth?authuser=0#enable-app-verification (SafetyNet set up) + // * For iOS: https://firebase.google.com/docs/auth/ios/phone-auth?authuser=0#start-receiving-silent-notifications + // * Finally modify verificationCompleted below as instructed. + await FirebaseAuth.instance.verifyPhoneNumber( + phoneNumber: phoneNumber, + timeout: + Duration(seconds: 0), // Skips Android's default auto-verification + verificationCompleted: (phoneAuthCredential) async { + await FirebaseAuth.instance.signInWithCredential(phoneAuthCredential); + phoneAuthManager.update(() { + phoneAuthManager.triggerOnCodeSent = false; + phoneAuthManager.phoneAuthError = null; + }); + // If you've implemented auto-verification, navigate to home page or + // onboarding page here manually. Uncomment the lines below and replace + // DestinationPage() with the desired widget. + // await Navigator.push( + // context, + // MaterialPageRoute(builder: (_) => DestinationPage()), + // ); + }, + verificationFailed: (e) { + phoneAuthManager.update(() { + phoneAuthManager.triggerOnCodeSent = false; + phoneAuthManager.phoneAuthError = e; + }); + completer.complete(false); + }, + codeSent: (verificationId, _) { + phoneAuthManager.update(() { + phoneAuthManager.phoneAuthVerificationCode = verificationId; + phoneAuthManager.triggerOnCodeSent = true; + phoneAuthManager.phoneAuthError = null; + }); + completer.complete(true); + }, + codeAutoRetrievalTimeout: (_) {}, + ); + + return completer.future; + } + + @override + Future verifySmsCode({ + required BuildContext context, + required String smsCode, + }) { + if (kIsWeb) { + return _signInOrCreateAccount( + context, + () => phoneAuthManager.webPhoneAuthConfirmationResult!.confirm(smsCode), + 'PHONE', + ); + } else { + final authCredential = PhoneAuthProvider.credential( + verificationId: phoneAuthManager.phoneAuthVerificationCode!, + smsCode: smsCode, + ); + return _signInOrCreateAccount( + context, + () => FirebaseAuth.instance.signInWithCredential(authCredential), + 'PHONE', + ); + } + } + + /// Tries to sign in or create an account using Firebase Auth. + /// Returns the User object if sign in was successful. + Future _signInOrCreateAccount( + BuildContext context, + Future Function() signInFunc, + String authProvider, + ) async { + try { + final userCredential = await signInFunc(); + if (userCredential?.user != null) { + await maybeCreateUser(userCredential!.user!); + } + return userCredential == null + ? null + : BharatTrackerFirebaseUser.fromUserCredential(userCredential); + } on FirebaseAuthException catch (e) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: ${e.message!}')), + ); + return null; + } + } +} diff --git a/lib/auth/firebase_auth/firebase_user_provider.dart b/lib/auth/firebase_auth/firebase_user_provider.dart new file mode 100644 index 0000000..4f0dfb9 --- /dev/null +++ b/lib/auth/firebase_auth/firebase_user_provider.dart @@ -0,0 +1,64 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:rxdart/rxdart.dart'; + +import '../base_auth_user_provider.dart'; + +export '../base_auth_user_provider.dart'; + +class BharatTrackerFirebaseUser extends BaseAuthUser { + BharatTrackerFirebaseUser(this.user); + User? user; + bool get loggedIn => user != null; + + @override + AuthUserInfo get authUserInfo => AuthUserInfo( + uid: user?.uid, + email: user?.email, + displayName: user?.displayName, + photoUrl: user?.photoURL, + phoneNumber: user?.phoneNumber, + ); + + @override + Future? delete() => user?.delete(); + + @override + Future? updateEmail(String email) async => await user?.updateEmail(email); + + @override + Future? sendEmailVerification() => user?.sendEmailVerification(); + + @override + bool get emailVerified { + // Reloads the user when checking in order to get the most up to date + // email verified status. + if (loggedIn && !user!.emailVerified) { + refreshUser(); + } + return user?.emailVerified ?? false; + } + + @override + Future refreshUser() async { + await FirebaseAuth.instance.currentUser + ?.reload() + .then((_) => user = FirebaseAuth.instance.currentUser); + } + + static BaseAuthUser fromUserCredential(UserCredential userCredential) => + fromFirebaseUser(userCredential.user); + static BaseAuthUser fromFirebaseUser(User? user) => + BharatTrackerFirebaseUser(user); +} + +Stream bharatTrackerFirebaseUserStream() => FirebaseAuth.instance + .authStateChanges() + .debounce((user) => user == null && !loggedIn + ? TimerStream(true, const Duration(seconds: 1)) + : Stream.value(user)) + .map( + (user) { + currentUser = BharatTrackerFirebaseUser(user); + return currentUser!; + }, + ); diff --git a/lib/auth/firebase_auth/github_auth.dart b/lib/auth/firebase_auth/github_auth.dart new file mode 100644 index 0000000..ff72a73 --- /dev/null +++ b/lib/auth/firebase_auth/github_auth.dart @@ -0,0 +1,11 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; + +// https://firebase.flutter.dev/docs/auth/social/#github +Future githubSignInFunc() async { + // Create a new provider + GithubAuthProvider githubProvider = GithubAuthProvider(); + + // Once signed in, return the UserCredential + return await FirebaseAuth.instance.signInWithPopup(githubProvider); +} diff --git a/lib/auth/firebase_auth/google_auth.dart b/lib/auth/firebase_auth/google_auth.dart new file mode 100644 index 0000000..ff860b2 --- /dev/null +++ b/lib/auth/firebase_auth/google_auth.dart @@ -0,0 +1,23 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +final _googleSignIn = GoogleSignIn(); + +Future googleSignInFunc() async { + if (kIsWeb) { + // Once signed in, return the UserCredential + return await FirebaseAuth.instance.signInWithPopup(GoogleAuthProvider()); + } + + await signOutWithGoogle().catchError((_) => null); + final auth = await (await _googleSignIn.signIn())?.authentication; + if (auth == null) { + return null; + } + final credential = GoogleAuthProvider.credential( + idToken: auth.idToken, accessToken: auth.accessToken); + return FirebaseAuth.instance.signInWithCredential(credential); +} + +Future signOutWithGoogle() => _googleSignIn.signOut(); diff --git a/lib/auth/firebase_auth/jwt_token_auth.dart b/lib/auth/firebase_auth/jwt_token_auth.dart new file mode 100644 index 0000000..35cd56c --- /dev/null +++ b/lib/auth/firebase_auth/jwt_token_auth.dart @@ -0,0 +1,4 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +Future jwtTokenSignIn(String jwtToken) => + FirebaseAuth.instance.signInWithCustomToken(jwtToken); diff --git a/lib/backend/backend.dart b/lib/backend/backend.dart new file mode 100644 index 0000000..070d246 --- /dev/null +++ b/lib/backend/backend.dart @@ -0,0 +1,207 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import '../auth/firebase_auth/auth_util.dart'; + +import '../flutter_flow/flutter_flow_util.dart'; +import 'schema/util/firestore_util.dart'; + +import 'schema/users_record.dart'; + +export 'dart:async' show StreamSubscription; +export 'package:cloud_firestore/cloud_firestore.dart'; +export 'schema/index.dart'; +export 'schema/util/firestore_util.dart'; +export 'schema/util/schema_util.dart'; + +export 'schema/users_record.dart'; + +/// Functions to query UsersRecords (as a Stream and as a Future). +Future queryUsersRecordCount({ + Query Function(Query)? queryBuilder, + int limit = -1, +}) => + queryCollectionCount( + UsersRecord.collection, + queryBuilder: queryBuilder, + limit: limit, + ); + +Stream> queryUsersRecord({ + Query Function(Query)? queryBuilder, + int limit = -1, + bool singleRecord = false, +}) => + queryCollection( + UsersRecord.collection, + UsersRecord.fromSnapshot, + queryBuilder: queryBuilder, + limit: limit, + singleRecord: singleRecord, + ); + +Future> queryUsersRecordOnce({ + Query Function(Query)? queryBuilder, + int limit = -1, + bool singleRecord = false, +}) => + queryCollectionOnce( + UsersRecord.collection, + UsersRecord.fromSnapshot, + queryBuilder: queryBuilder, + limit: limit, + singleRecord: singleRecord, + ); + +Future queryCollectionCount( + Query collection, { + Query Function(Query)? queryBuilder, + int limit = -1, +}) { + final builder = queryBuilder ?? (q) => q; + var query = builder(collection); + if (limit > 0) { + query = query.limit(limit); + } + + return query.count().get().catchError((err) { + print('Error querying $collection: $err'); + }).then((value) => value.count); +} + +Stream> queryCollection( + Query collection, + RecordBuilder recordBuilder, { + Query Function(Query)? queryBuilder, + int limit = -1, + bool singleRecord = false, +}) { + final builder = queryBuilder ?? (q) => q; + var query = builder(collection); + if (limit > 0 || singleRecord) { + query = query.limit(singleRecord ? 1 : limit); + } + return query.snapshots().handleError((err) { + print('Error querying $collection: $err'); + }).map((s) => s.docs + .map( + (d) => safeGet( + () => recordBuilder(d), + (e) => print('Error serializing doc ${d.reference.path}:\n$e'), + ), + ) + .where((d) => d != null) + .map((d) => d!) + .toList()); +} + +Future> queryCollectionOnce( + Query collection, + RecordBuilder recordBuilder, { + Query Function(Query)? queryBuilder, + int limit = -1, + bool singleRecord = false, +}) { + final builder = queryBuilder ?? (q) => q; + var query = builder(collection); + if (limit > 0 || singleRecord) { + query = query.limit(singleRecord ? 1 : limit); + } + return query.get().then((s) => s.docs + .map( + (d) => safeGet( + () => recordBuilder(d), + (e) => print('Error serializing doc ${d.reference.path}:\n$e'), + ), + ) + .where((d) => d != null) + .map((d) => d!) + .toList()); +} + +extension QueryExtension on Query { + Query whereIn(String field, List? list) => (list?.isEmpty ?? true) + ? where(field, whereIn: null) + : where(field, whereIn: list); + + Query whereNotIn(String field, List? list) => (list?.isEmpty ?? true) + ? where(field, whereNotIn: null) + : where(field, whereNotIn: list); + + Query whereArrayContainsAny(String field, List? list) => + (list?.isEmpty ?? true) + ? where(field, arrayContainsAny: null) + : where(field, arrayContainsAny: list); +} + +class FFFirestorePage { + final List data; + final Stream>? dataStream; + final QueryDocumentSnapshot? nextPageMarker; + + FFFirestorePage(this.data, this.dataStream, this.nextPageMarker); +} + +Future> queryCollectionPage( + Query collection, + RecordBuilder recordBuilder, { + Query Function(Query)? queryBuilder, + DocumentSnapshot? nextPageMarker, + required int pageSize, + required bool isStream, +}) async { + final builder = queryBuilder ?? (q) => q; + var query = builder(collection).limit(pageSize); + if (nextPageMarker != null) { + query = query.startAfterDocument(nextPageMarker); + } + Stream? docSnapshotStream; + QuerySnapshot docSnapshot; + if (isStream) { + docSnapshotStream = query.snapshots(); + docSnapshot = await docSnapshotStream.first; + } else { + docSnapshot = await query.get(); + } + final getDocs = (QuerySnapshot s) => s.docs + .map( + (d) => safeGet( + () => recordBuilder(d), + (e) => print('Error serializing doc ${d.reference.path}:\n$e'), + ), + ) + .where((d) => d != null) + .map((d) => d!) + .toList(); + final data = getDocs(docSnapshot); + final dataStream = docSnapshotStream?.map(getDocs); + final nextPageToken = docSnapshot.docs.isEmpty ? null : docSnapshot.docs.last; + return FFFirestorePage(data, dataStream, nextPageToken); +} + +// Creates a Firestore document representing the logged in user if it doesn't yet exist +Future maybeCreateUser(User user) async { + final userRecord = UsersRecord.collection.doc(user.uid); + final userExists = await userRecord.get().then((u) => u.exists); + if (userExists) { + currentUserDocument = await UsersRecord.getDocumentOnce(userRecord); + return; + } + + final userData = createUsersRecordData( + email: user.email, + displayName: + user.displayName ?? FirebaseAuth.instance.currentUser?.displayName, + photoUrl: user.photoURL, + uid: user.uid, + phoneNumber: user.phoneNumber, + createdTime: getCurrentTimestamp, + ); + + await userRecord.set(userData); + currentUserDocument = UsersRecord.getDocumentFromData(userData, userRecord); +} + +Future updateUserDocument({String? email}) async { + await currentUserDocument?.reference + .update(createUsersRecordData(email: email)); +} diff --git a/lib/backend/firebase/firebase_config.dart b/lib/backend/firebase/firebase_config.dart new file mode 100644 index 0000000..18734f7 --- /dev/null +++ b/lib/backend/firebase/firebase_config.dart @@ -0,0 +1,17 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; + +Future initFirebase() async { + if (kIsWeb) { + await Firebase.initializeApp( + options: FirebaseOptions( + apiKey: "AIzaSyAtkj0pdVUPWGiluRUBuArnJAh0U9OOV5o", + authDomain: "bharat-tracker.firebaseapp.com", + projectId: "bharat-tracker", + storageBucket: "bharat-tracker.appspot.com", + messagingSenderId: "649830357383", + appId: "1:649830357383:web:fee69daac61ac0798fdb22")); + } else { + await Firebase.initializeApp(); + } +} diff --git a/lib/backend/schema/firestore.indexes.json b/lib/backend/schema/firestore.indexes.json new file mode 100644 index 0000000..b19e92b --- /dev/null +++ b/lib/backend/schema/firestore.indexes.json @@ -0,0 +1,3 @@ +{ + "indexes": [] +} \ No newline at end of file diff --git a/lib/backend/schema/firestore.rules b/lib/backend/schema/firestore.rules new file mode 100644 index 0000000..edcc12b --- /dev/null +++ b/lib/backend/schema/firestore.rules @@ -0,0 +1,11 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /users/{document} { + allow create: if request.auth.uid == document; + allow read: if request.auth.uid == document; + allow write: if request.auth.uid == document; + allow delete: if false; + } + } +} diff --git a/lib/backend/schema/index.dart b/lib/backend/schema/index.dart new file mode 100644 index 0000000..7a8049a --- /dev/null +++ b/lib/backend/schema/index.dart @@ -0,0 +1,3 @@ +export 'package:cloud_firestore/cloud_firestore.dart'; +export 'package:flutter/material.dart' show Color, Colors; +export '/flutter_flow/lat_lng.dart'; diff --git a/lib/backend/schema/users_record.dart b/lib/backend/schema/users_record.dart new file mode 100644 index 0000000..d17e6cd --- /dev/null +++ b/lib/backend/schema/users_record.dart @@ -0,0 +1,138 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; + +import '/backend/schema/util/firestore_util.dart'; +import '/backend/schema/util/schema_util.dart'; + +import 'index.dart'; +import '/flutter_flow/flutter_flow_util.dart'; + +class UsersRecord extends FirestoreRecord { + UsersRecord._( + DocumentReference reference, + Map data, + ) : super(reference, data) { + _initializeFields(); + } + + // "email" field. + String? _email; + String get email => _email ?? ''; + bool hasEmail() => _email != null; + + // "display_name" field. + String? _displayName; + String get displayName => _displayName ?? ''; + bool hasDisplayName() => _displayName != null; + + // "photo_url" field. + String? _photoUrl; + String get photoUrl => _photoUrl ?? ''; + bool hasPhotoUrl() => _photoUrl != null; + + // "uid" field. + String? _uid; + String get uid => _uid ?? ''; + bool hasUid() => _uid != null; + + // "created_time" field. + DateTime? _createdTime; + DateTime? get createdTime => _createdTime; + bool hasCreatedTime() => _createdTime != null; + + // "phone_number" field. + String? _phoneNumber; + String get phoneNumber => _phoneNumber ?? ''; + bool hasPhoneNumber() => _phoneNumber != null; + + void _initializeFields() { + _email = snapshotData['email'] as String?; + _displayName = snapshotData['display_name'] as String?; + _photoUrl = snapshotData['photo_url'] as String?; + _uid = snapshotData['uid'] as String?; + _createdTime = snapshotData['created_time'] as DateTime?; + _phoneNumber = snapshotData['phone_number'] as String?; + } + + static CollectionReference get collection => + FirebaseFirestore.instance.collection('users'); + + static Stream getDocument(DocumentReference ref) => + ref.snapshots().map((s) => UsersRecord.fromSnapshot(s)); + + static Future getDocumentOnce(DocumentReference ref) => + ref.get().then((s) => UsersRecord.fromSnapshot(s)); + + static UsersRecord fromSnapshot(DocumentSnapshot snapshot) => UsersRecord._( + snapshot.reference, + mapFromFirestore(snapshot.data() as Map), + ); + + static UsersRecord getDocumentFromData( + Map data, + DocumentReference reference, + ) => + UsersRecord._(reference, mapFromFirestore(data)); + + @override + String toString() => + 'UsersRecord(reference: ${reference.path}, data: $snapshotData)'; + + @override + int get hashCode => reference.path.hashCode; + + @override + bool operator ==(other) => + other is UsersRecord && + reference.path.hashCode == other.reference.path.hashCode; +} + +Map createUsersRecordData({ + String? email, + String? displayName, + String? photoUrl, + String? uid, + DateTime? createdTime, + String? phoneNumber, +}) { + final firestoreData = mapToFirestore( + { + 'email': email, + 'display_name': displayName, + 'photo_url': photoUrl, + 'uid': uid, + 'created_time': createdTime, + 'phone_number': phoneNumber, + }.withoutNulls, + ); + + return firestoreData; +} + +class UsersRecordDocumentEquality implements Equality { + const UsersRecordDocumentEquality(); + + @override + bool equals(UsersRecord? e1, UsersRecord? e2) { + return e1?.email == e2?.email && + e1?.displayName == e2?.displayName && + e1?.photoUrl == e2?.photoUrl && + e1?.uid == e2?.uid && + e1?.createdTime == e2?.createdTime && + e1?.phoneNumber == e2?.phoneNumber; + } + + @override + int hash(UsersRecord? e) => const ListEquality().hash([ + e?.email, + e?.displayName, + e?.photoUrl, + e?.uid, + e?.createdTime, + e?.phoneNumber + ]); + + @override + bool isValidKey(Object? o) => o is UsersRecord; +} diff --git a/lib/backend/schema/util/firestore_util.dart b/lib/backend/schema/util/firestore_util.dart new file mode 100644 index 0000000..7418847 --- /dev/null +++ b/lib/backend/schema/util/firestore_util.dart @@ -0,0 +1,152 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/material.dart'; +import 'package:from_css_color/from_css_color.dart'; + +import '/backend/schema/util/schema_util.dart'; +import '/flutter_flow/flutter_flow_util.dart'; + +typedef RecordBuilder = T Function(DocumentSnapshot snapshot); + +abstract class FirestoreRecord { + FirestoreRecord(this.reference, this.snapshotData); + Map snapshotData; + DocumentReference reference; +} + +abstract class FFFirebaseStruct extends BaseStruct { + FFFirebaseStruct(this.firestoreUtilData); + + /// Utility class for Firestore updates + FirestoreUtilData firestoreUtilData = FirestoreUtilData(); +} + +class FirestoreUtilData { + const FirestoreUtilData({ + this.fieldValues = const {}, + this.clearUnsetFields = true, + this.create = false, + this.delete = false, + }); + final Map fieldValues; + final bool clearUnsetFields; + final bool create; + final bool delete; + static String get name => 'firestoreUtilData'; +} + +Map mapFromFirestore(Map data) => + mergeNestedFields(data) + .where((k, _) => k != FirestoreUtilData.name) + .map((key, value) { + // Handle Timestamp + if (value is Timestamp) { + value = value.toDate(); + } + // Handle list of Timestamp + if (value is Iterable && value.isNotEmpty && value.first is Timestamp) { + value = value.map((v) => (v as Timestamp).toDate()).toList(); + } + // Handle GeoPoint + if (value is GeoPoint) { + value = value.toLatLng(); + } + // Handle list of GeoPoint + if (value is Iterable && value.isNotEmpty && value.first is GeoPoint) { + value = value.map((v) => (v as GeoPoint).toLatLng()).toList(); + } + // Handle nested data. + if (value is Map) { + value = mapFromFirestore(value as Map); + } + // Handle list of nested data. + if (value is Iterable && value.isNotEmpty && value.first is Map) { + value = value + .map((v) => mapFromFirestore(v as Map)) + .toList(); + } + return MapEntry(key, value); + }); + +Map mapToFirestore(Map data) => + data.where((k, v) => k != FirestoreUtilData.name).map((key, value) { + // Handle GeoPoint + if (value is LatLng) { + value = value.toGeoPoint(); + } + // Handle list of GeoPoint + if (value is Iterable && value.isNotEmpty && value.first is LatLng) { + value = value.map((v) => (v as LatLng).toGeoPoint()).toList(); + } + // Handle Color + if (value is Color) { + value = value.toCssString(); + } + // Handle list of Color + if (value is Iterable && value.isNotEmpty && value.first is Color) { + value = value.map((v) => (v as Color).toCssString()).toList(); + } + // Handle nested data. + if (value is Map) { + value = mapFromFirestore(value as Map); + } + // Handle list of nested data. + if (value is Iterable && value.isNotEmpty && value.first is Map) { + value = value + .map((v) => mapFromFirestore(v as Map)) + .toList(); + } + return MapEntry(key, value); + }); + +List? convertToGeoPointList(List? list) => + list?.map((e) => e.toGeoPoint()).toList(); + +extension GeoPointExtension on LatLng { + GeoPoint toGeoPoint() => GeoPoint(latitude, longitude); +} + +extension LatLngExtension on GeoPoint { + LatLng toLatLng() => LatLng(latitude, longitude); +} + +DocumentReference toRef(String ref) => FirebaseFirestore.instance.doc(ref); + +T? safeGet(T Function() func, [Function(dynamic)? reportError]) { + try { + return func(); + } catch (e) { + reportError?.call(e); + } + return null; +} + +Map mergeNestedFields(Map data) { + final nestedData = data.where((k, _) => k.contains('.')); + final fieldNames = nestedData.keys.map((k) => k.split('.').first).toSet(); + // Remove nested values (e.g. 'foo.bar') and merge them into a map. + data.removeWhere((k, _) => k.contains('.')); + fieldNames.forEach((name) { + final mergedValues = mergeNestedFields( + nestedData + .where((k, _) => k.split('.').first == name) + .map((k, v) => MapEntry(k.split('.').skip(1).join('.'), v)), + ); + final existingValue = data[name]; + data[name] = { + if (existingValue != null && existingValue is Map) + ...existingValue as Map, + ...mergedValues, + }; + }); + // Merge any nested maps inside any of the fields as well. + data.where((_, v) => v is Map).forEach((k, v) { + data[k] = mergeNestedFields(v as Map); + }); + + return data; +} + +extension _WhereMapExtension on Map { + Map where(bool Function(K, V) test) => + Map.fromEntries(entries.where((e) => test(e.key, e.value))); +} diff --git a/lib/backend/schema/util/schema_util.dart b/lib/backend/schema/util/schema_util.dart new file mode 100644 index 0000000..7566ded --- /dev/null +++ b/lib/backend/schema/util/schema_util.dart @@ -0,0 +1,68 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:from_css_color/from_css_color.dart'; +import '/flutter_flow/flutter_flow_util.dart'; + +export 'package:collection/collection.dart' show ListEquality; +export 'package:flutter/material.dart' show Color, Colors; +export 'package:from_css_color/from_css_color.dart'; + +typedef StructBuilder = T Function(Map data); + +abstract class BaseStruct { + Map toSerializableMap(); + String serialize() => json.encode(toSerializableMap()); +} + +List? getStructList( + dynamic value, + StructBuilder structBuilder, +) => + value is! List + ? null + : value + .where((e) => e is Map) + .map((e) => structBuilder(e as Map)) + .toList(); + +Color? getSchemaColor(dynamic value) => value is String + ? fromCssColor(value) + : value is Color + ? value + : null; + +List? getColorsList(dynamic value) => + value is! List ? null : value.map(getSchemaColor).withoutNulls; + +List? getDataList(dynamic value) => + value is! List ? null : value.map((e) => castToType(e)!).toList(); + +T? castToType(dynamic value) { + if (value == null) { + return null; + } + switch (T) { + case double: + // Doubles may be stored as ints in some cases. + return value.toDouble() as T; + case int: + // Likewise, ints may be stored as doubles. If this is the case + // (i.e. no decimal value), return the value as an int. + if (value is num && value.toInt() == value) { + return value.toInt() as T; + } + break; + default: + break; + } + return value as T; +} + +extension MapDataExtensions on Map { + Map get withoutNulls => Map.fromEntries( + entries + .where((e) => e.value != null) + .map((e) => MapEntry(e.key, e.value!)), + ); +} diff --git a/lib/custom_code/widgets/carousel_menu.dart b/lib/custom_code/widgets/carousel_menu.dart new file mode 100644 index 0000000..533b27f --- /dev/null +++ b/lib/custom_code/widgets/carousel_menu.dart @@ -0,0 +1,31 @@ +// Automatic FlutterFlow imports +import '/backend/backend.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import 'index.dart'; // Imports other custom widgets +import 'package:flutter/material.dart'; +// Begin custom widget code +// DO NOT REMOVE OR MODIFY THE CODE ABOVE! + +class CarouselMenu extends StatefulWidget { + const CarouselMenu({ + Key? key, + this.width, + this.height, + this.jsonMenuItems, + }) : super(key: key); + + final double? width; + final double? height; + final dynamic jsonMenuItems; + + @override + _CarouselMenuState createState() => _CarouselMenuState(); +} + +class _CarouselMenuState extends State { + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/custom_code/widgets/index.dart b/lib/custom_code/widgets/index.dart new file mode 100644 index 0000000..2c7b20a --- /dev/null +++ b/lib/custom_code/widgets/index.dart @@ -0,0 +1 @@ +export 'carousel_menu.dart' show CarouselMenu; diff --git a/lib/flutter_flow/flutter_flow_animations.dart b/lib/flutter_flow/flutter_flow_animations.dart new file mode 100644 index 0000000..710f007 --- /dev/null +++ b/lib/flutter_flow/flutter_flow_animations.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; + +enum AnimationTrigger { + onPageLoad, + onActionTrigger, +} + +class AnimationInfo { + AnimationInfo({ + required this.trigger, + required this.effects, + this.loop = false, + this.reverse = false, + this.applyInitialState = true, + }); + final AnimationTrigger trigger; + final List> effects; + final bool applyInitialState; + final bool loop; + final bool reverse; + late AnimationController controller; +} + +void createAnimation(AnimationInfo animation, TickerProvider vsync) { + final newController = AnimationController(vsync: vsync); + animation.controller = newController; +} + +void setupAnimations(Iterable animations, TickerProvider vsync) { + animations.forEach((animation) => createAnimation(animation, vsync)); +} + +extension AnimatedWidgetExtension on Widget { + Widget animateOnPageLoad(AnimationInfo animationInfo) => Animate( + effects: animationInfo.effects, + child: this, + onPlay: (controller) => animationInfo.loop + ? controller.repeat(reverse: animationInfo.reverse) + : null, + onComplete: (controller) => !animationInfo.loop && animationInfo.reverse + ? controller.reverse() + : null); + + Widget animateOnActionTrigger( + AnimationInfo animationInfo, { + bool hasBeenTriggered = false, + }) => + hasBeenTriggered || animationInfo.applyInitialState + ? Animate( + controller: animationInfo.controller, + autoPlay: false, + effects: animationInfo.effects, + child: this) + : this; +} + +class TiltEffect extends Effect { + const TiltEffect({ + Duration? delay, + Duration? duration, + Curve? curve, + Offset? begin, + Offset? end, + }) : super( + delay: delay, + duration: duration, + curve: curve, + begin: begin ?? const Offset(0.0, 0.0), + end: end ?? const Offset(0.0, 0.0), + ); + + @override + Widget build( + BuildContext context, + Widget child, + AnimationController controller, + EffectEntry entry, + ) { + Animation animation = buildAnimation(controller, entry); + return getOptimizedBuilder( + animation: animation, + builder: (_, __) => Transform( + transform: Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateX(animation.value.dx) + ..rotateY(animation.value.dy), + alignment: Alignment.center, + child: child, + ), + ); + } +} diff --git a/lib/flutter_flow/flutter_flow_button_tabbar.dart b/lib/flutter_flow/flutter_flow_button_tabbar.dart new file mode 100644 index 0000000..c0151fe --- /dev/null +++ b/lib/flutter_flow/flutter_flow_button_tabbar.dart @@ -0,0 +1,865 @@ +import 'dart:math' as math; +import 'dart:ui' show lerpDouble; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +const double _kTabHeight = 46.0; + +typedef _LayoutCallback = void Function( + List xOffsets, TextDirection textDirection, double width); + +class _TabLabelBarRenderer extends RenderFlex { + _TabLabelBarRenderer({ + required Axis direction, + required MainAxisSize mainAxisSize, + required MainAxisAlignment mainAxisAlignment, + required CrossAxisAlignment crossAxisAlignment, + required TextDirection textDirection, + required VerticalDirection verticalDirection, + required this.onPerformLayout, + }) : super( + direction: direction, + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + textDirection: textDirection, + verticalDirection: verticalDirection, + ); + + _LayoutCallback onPerformLayout; + + @override + void performLayout() { + super.performLayout(); + // xOffsets will contain childCount+1 values, giving the offsets of the + // leading edge of the first tab as the first value, of the leading edge of + // the each subsequent tab as each subsequent value, and of the trailing + // edge of the last tab as the last value. + RenderBox? child = firstChild; + final List xOffsets = []; + while (child != null) { + final FlexParentData childParentData = + child.parentData! as FlexParentData; + xOffsets.add(childParentData.offset.dx); + assert(child.parentData == childParentData); + child = childParentData.nextSibling; + } + assert(textDirection != null); + switch (textDirection!) { + case TextDirection.rtl: + xOffsets.insert(0, size.width); + break; + case TextDirection.ltr: + xOffsets.add(size.width); + break; + } + onPerformLayout(xOffsets, textDirection!, size.width); + } +} + +// This class and its renderer class only exist to report the widths of the tabs +// upon layout. The tab widths are only used at paint time (see _IndicatorPainter) +// or in response to input. +class _TabLabelBar extends Flex { + _TabLabelBar({ + required List children, + required this.onPerformLayout, + }) : super( + children: children, + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + verticalDirection: VerticalDirection.down, + ); + + final _LayoutCallback onPerformLayout; + + @override + RenderFlex createRenderObject(BuildContext context) { + return _TabLabelBarRenderer( + direction: direction, + mainAxisAlignment: mainAxisAlignment, + mainAxisSize: mainAxisSize, + crossAxisAlignment: crossAxisAlignment, + textDirection: getEffectiveTextDirection(context)!, + verticalDirection: verticalDirection, + onPerformLayout: onPerformLayout, + ); + } + + @override + void updateRenderObject( + BuildContext context, _TabLabelBarRenderer renderObject) { + super.updateRenderObject(context, renderObject); + renderObject.onPerformLayout = onPerformLayout; + } +} + +class _IndicatorPainter extends CustomPainter { + _IndicatorPainter({ + required this.controller, + required this.tabKeys, + required _IndicatorPainter? old, + }) : super(repaint: controller.animation) { + if (old != null) { + saveTabOffsets(old._currentTabOffsets, old._currentTextDirection); + } + } + + final TabController controller; + + final List tabKeys; + + // _currentTabOffsets and _currentTextDirection are set each time TabBar + // layout is completed. These values can be null when TabBar contains no + // tabs, since there are nothing to lay out. + List? _currentTabOffsets; + TextDirection? _currentTextDirection; + + BoxPainter? _painter; + bool _needsPaint = false; + void markNeedsPaint() { + _needsPaint = true; + } + + void dispose() { + _painter?.dispose(); + } + + void saveTabOffsets(List? tabOffsets, TextDirection? textDirection) { + _currentTabOffsets = tabOffsets; + _currentTextDirection = textDirection; + } + + // _currentTabOffsets[index] is the offset of the start edge of the tab at index, and + // _currentTabOffsets[_currentTabOffsets.length] is the end edge of the last tab. + int get maxTabIndex => _currentTabOffsets!.length - 2; + + double centerOf(int tabIndex) { + assert(_currentTabOffsets != null); + assert(_currentTabOffsets!.isNotEmpty); + assert(tabIndex >= 0); + assert(tabIndex <= maxTabIndex); + return (_currentTabOffsets![tabIndex] + _currentTabOffsets![tabIndex + 1]) / + 2.0; + } + + @override + void paint(Canvas canvas, Size size) { + _needsPaint = false; + } + + @override + bool shouldRepaint(_IndicatorPainter old) { + return _needsPaint || + controller != old.controller || + tabKeys.length != old.tabKeys.length || + (!listEquals(_currentTabOffsets, old._currentTabOffsets)) || + _currentTextDirection != old._currentTextDirection; + } +} + +// This class, and TabBarScrollController, only exist to handle the case +// where a scrollable TabBar has a non-zero initialIndex. In that case we can +// only compute the scroll position's initial scroll offset (the "correct" +// pixels value) after the TabBar viewport width and scroll limits are known. + +class _TabBarScrollPosition extends ScrollPositionWithSingleContext { + _TabBarScrollPosition({ + required ScrollPhysics physics, + required ScrollContext context, + required ScrollPosition? oldPosition, + required this.tabBar, + }) : super( + initialPixels: null, + physics: physics, + context: context, + oldPosition: oldPosition, + ); + + final _FlutterFlowButtonTabBarState tabBar; + + bool _viewportDimensionWasNonZero = false; + + // Position should be adjusted at least once. + bool _needsPixelsCorrection = true; + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + bool result = true; + if (!_viewportDimensionWasNonZero) { + _viewportDimensionWasNonZero = viewportDimension != 0.0; + } + // If the viewport never had a non-zero dimension, we just want to jump + // to the initial scroll position to avoid strange scrolling effects in + // release mode: In release mode, the viewport temporarily may have a + // dimension of zero before the actual dimension is calculated. In that + // scenario, setting the actual dimension would cause a strange scroll + // effect without this guard because the super call below would starts a + // ballistic scroll activity. + if (!_viewportDimensionWasNonZero || _needsPixelsCorrection) { + _needsPixelsCorrection = false; + correctPixels(tabBar._initialScrollOffset( + viewportDimension, minScrollExtent, maxScrollExtent)); + result = false; + } + return super.applyContentDimensions(minScrollExtent, maxScrollExtent) && + result; + } + + void markNeedsPixelsCorrection() { + _needsPixelsCorrection = true; + } +} + +// This class, and TabBarScrollPosition, only exist to handle the case +// where a scrollable TabBar has a non-zero initialIndex. +class _TabBarScrollController extends ScrollController { + _TabBarScrollController(this.tabBar); + + final _FlutterFlowButtonTabBarState tabBar; + + @override + ScrollPosition createScrollPosition(ScrollPhysics physics, + ScrollContext context, ScrollPosition? oldPosition) { + return _TabBarScrollPosition( + physics: physics, + context: context, + oldPosition: oldPosition, + tabBar: tabBar, + ); + } +} + +/// A Flutterflow Design widget that displays a horizontal row of tabs. +class FlutterFlowButtonTabBar extends StatefulWidget + implements PreferredSizeWidget { + /// The [tabs] argument must not be null and its length must match the [controller]'s + /// [TabController.length]. + /// + /// If a [TabController] is not provided, then there must be a + /// [DefaultTabController] ancestor. + /// + const FlutterFlowButtonTabBar({ + Key? key, + required this.tabs, + this.controller, + this.isScrollable = false, + this.useToggleButtonStyle = false, + this.dragStartBehavior = DragStartBehavior.start, + this.onTap, + this.backgroundColor, + this.unselectedBackgroundColor, + this.decoration, + this.unselectedDecoration, + this.labelStyle, + this.unselectedLabelStyle, + this.labelColor, + this.unselectedLabelColor, + this.borderWidth = 0, + this.borderColor = Colors.transparent, + this.unselectedBorderColor = Colors.transparent, + this.physics = const BouncingScrollPhysics(), + this.labelPadding = const EdgeInsets.symmetric(horizontal: 4), + this.buttonMargin = const EdgeInsets.all(4), + this.padding = EdgeInsets.zero, + this.borderRadius = 8.0, + this.elevation = 0, + }) : super(key: key); + + /// Typically a list of two or more [Tab] widgets. + /// + /// The length of this list must match the [controller]'s [TabController.length] + /// and the length of the [TabBarView.children] list. + final List tabs; + + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of [DefaultTabController.of] + /// will be used. + final TabController? controller; + + /// Whether this tab bar can be scrolled horizontally. + /// + /// If [isScrollable] is true, then each tab is as wide as needed for its label + /// and the entire [FlutterFlowButtonTabBar] is scrollable. Otherwise each tab gets an equal + /// share of the available space. + final bool isScrollable; + + /// Whether the tab buttons should be styled as toggle buttons. + final bool useToggleButtonStyle; + + /// The background [Color] of the button on its selected state. + final Color? backgroundColor; + + /// The background [Color] of the button on its unselected state. + final Color? unselectedBackgroundColor; + + /// The [BoxDecoration] of the button on its selected state. + /// + /// If [BoxDecoration] is not provided, [backgroundColor] is used. + final BoxDecoration? decoration; + + /// The [BoxDecoration] of the button on its unselected state. + /// + /// If [BoxDecoration] is not provided, [unselectedBackgroundColor] is used. + final BoxDecoration? unselectedDecoration; + + /// The [TextStyle] of the button's [Text] on its selected state. The color provided + /// on the TextStyle will be used for the [Icon]'s color. + final TextStyle? labelStyle; + + /// The color of selected tab labels. + final Color? labelColor; + + /// The color of unselected tab labels. + final Color? unselectedLabelColor; + + /// The [TextStyle] of the button's [Text] on its unselected state. The color provided + /// on the TextStyle will be used for the [Icon]'s color. + final TextStyle? unselectedLabelStyle; + + /// The with of solid [Border] for each button. If no value is provided, the border + /// is not drawn. + final double borderWidth; + + /// The [Color] of solid [Border] for each button. + final Color? borderColor; + + /// The [Color] of solid [Border] for each button. If no value is provided, the value of + /// [this.borderColor] is used. + final Color? unselectedBorderColor; + + /// The [EdgeInsets] used for the [Padding] of the buttons' content. + /// + /// The default value is [EdgeInsets.symmetric(horizontal: 4)]. + final EdgeInsetsGeometry labelPadding; + + /// The [EdgeInsets] used for the [Margin] of the buttons. + /// + /// The default value is [EdgeInsets.all(4)]. + final EdgeInsetsGeometry buttonMargin; + + /// The amount of space by which to inset the tab bar. + final EdgeInsetsGeometry? padding; + + /// The value of the [BorderRadius.circular] applied to each button. + final double borderRadius; + + /// The value of the [elevation] applied to each button. + final double elevation; + + final DragStartBehavior dragStartBehavior; + + final ValueChanged? onTap; + + final ScrollPhysics? physics; + + /// A size whose height depends on if the tabs have both icons and text. + /// + /// [AppBar] uses this size to compute its own preferred size. + @override + Size get preferredSize { + double maxHeight = _kTabHeight; + for (final Widget item in tabs) { + if (item is PreferredSizeWidget) { + final double itemHeight = item.preferredSize.height; + maxHeight = math.max(itemHeight, maxHeight); + } + } + return Size.fromHeight( + maxHeight + labelPadding.vertical + buttonMargin.vertical); + } + + @override + State createState() => + _FlutterFlowButtonTabBarState(); +} + +class _FlutterFlowButtonTabBarState extends State + with TickerProviderStateMixin { + ScrollController? _scrollController; + TabController? _controller; + _IndicatorPainter? _indicatorPainter; + late AnimationController _animationController; + int _currentIndex = 0; + int _prevIndex = -1; + + late double _tabStripWidth; + late List _tabKeys; + + final GlobalKey _tabsParentKey = GlobalKey(); + + bool _debugHasScheduledValidTabsCountCheck = false; + + @override + void initState() { + super.initState(); + // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find + // the width of tab widget i. See _IndicatorPainter.indicatorRect(). + _tabKeys = widget.tabs.map((tab) => GlobalKey()).toList(); + + /// The animation duration is 2/3 of the tab scroll animation duration in + /// Material design (kTabScrollDuration). + _animationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 200)); + + // so the buttons start in their "final" state (color) + _animationController + ..value = 1.0 + ..addListener(() { + if (mounted) { + setState(() {}); + } + }); + } + + // If the TabBar is rebuilt with a new tab controller, the caller should + // dispose the old one. In that case the old controller's animation will be + // null and should not be accessed. + bool get _controllerIsValid => _controller?.animation != null; + + void _updateTabController() { + final TabController? newController = + widget.controller ?? DefaultTabController.maybeOf(context); + assert(() { + if (newController == null) { + throw FlutterError( + 'No TabController for ${widget.runtimeType}.\n' + 'When creating a ${widget.runtimeType}, you must either provide an explicit ' + 'TabController using the "controller" property, or you must ensure that there ' + 'is a DefaultTabController above the ${widget.runtimeType}.\n' + 'In this case, there was neither an explicit controller nor a default controller.', + ); + } + return true; + }()); + + if (newController == _controller) { + return; + } + + if (_controllerIsValid) { + _controller!.animation!.removeListener(_handleTabControllerAnimationTick); + _controller!.removeListener(_handleTabControllerTick); + } + _controller = newController; + if (_controller != null) { + _controller!.animation!.addListener(_handleTabControllerAnimationTick); + _controller!.addListener(_handleTabControllerTick); + _currentIndex = _controller!.index; + } + } + + void _initIndicatorPainter() { + _indicatorPainter = !_controllerIsValid + ? null + : _IndicatorPainter( + controller: _controller!, + tabKeys: _tabKeys, + old: _indicatorPainter, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + assert(debugCheckHasMaterial(context)); + _updateTabController(); + _initIndicatorPainter(); + } + + @override + void didUpdateWidget(FlutterFlowButtonTabBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _updateTabController(); + _initIndicatorPainter(); + // Adjust scroll position. + if (_scrollController != null) { + final ScrollPosition position = _scrollController!.position; + if (position is _TabBarScrollPosition) { + position.markNeedsPixelsCorrection(); + } + } + } + + if (widget.tabs.length > _tabKeys.length) { + final int delta = widget.tabs.length - _tabKeys.length; + _tabKeys.addAll(List.generate(delta, (int n) => GlobalKey())); + } else if (widget.tabs.length < _tabKeys.length) { + _tabKeys.removeRange(widget.tabs.length, _tabKeys.length); + } + } + + @override + void dispose() { + _indicatorPainter!.dispose(); + if (_controllerIsValid) { + _controller!.animation!.removeListener(_handleTabControllerAnimationTick); + _controller!.removeListener(_handleTabControllerTick); + } + _controller = null; + // We don't own the _controller Animation, so it's not disposed here. + super.dispose(); + } + + int get maxTabIndex => _indicatorPainter!.maxTabIndex; + + double _tabScrollOffset( + int index, double viewportWidth, double minExtent, double maxExtent) { + if (!widget.isScrollable) { + return 0.0; + } + double tabCenter = _indicatorPainter!.centerOf(index); + double paddingStart; + switch (Directionality.of(context)) { + case TextDirection.rtl: + paddingStart = widget.padding?.resolve(TextDirection.rtl).right ?? 0; + tabCenter = _tabStripWidth - tabCenter; + break; + case TextDirection.ltr: + paddingStart = widget.padding?.resolve(TextDirection.ltr).left ?? 0; + break; + } + + return clampDouble( + tabCenter + paddingStart - viewportWidth / 2.0, minExtent, maxExtent); + } + + double _tabCenteredScrollOffset(int index) { + final ScrollPosition position = _scrollController!.position; + return _tabScrollOffset(index, position.viewportDimension, + position.minScrollExtent, position.maxScrollExtent); + } + + double _initialScrollOffset( + double viewportWidth, double minExtent, double maxExtent) { + return _tabScrollOffset(_currentIndex, viewportWidth, minExtent, maxExtent); + } + + void _scrollToCurrentIndex() { + final double offset = _tabCenteredScrollOffset(_currentIndex); + _scrollController! + .animateTo(offset, duration: kTabScrollDuration, curve: Curves.ease); + } + + void _scrollToControllerValue() { + final double? leadingPosition = + _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null; + final double middlePosition = _tabCenteredScrollOffset(_currentIndex); + final double? trailingPosition = _currentIndex < maxTabIndex + ? _tabCenteredScrollOffset(_currentIndex + 1) + : null; + + final double index = _controller!.index.toDouble(); + final double value = _controller!.animation!.value; + final double offset; + if (value == index - 1.0) { + offset = leadingPosition ?? middlePosition; + } else if (value == index + 1.0) { + offset = trailingPosition ?? middlePosition; + } else if (value == index) { + offset = middlePosition; + } else if (value < index) { + offset = leadingPosition == null + ? middlePosition + : lerpDouble(middlePosition, leadingPosition, index - value)!; + } else { + offset = trailingPosition == null + ? middlePosition + : lerpDouble(middlePosition, trailingPosition, value - index)!; + } + + _scrollController!.jumpTo(offset); + } + + void _handleTabControllerAnimationTick() { + assert(mounted); + if (!_controller!.indexIsChanging && widget.isScrollable) { + // Sync the TabBar's scroll position with the TabBarView's PageView. + _currentIndex = _controller!.index; + _scrollToControllerValue(); + } + } + + void _handleTabControllerTick() { + if (_controller!.index != _currentIndex) { + _prevIndex = _currentIndex; + _currentIndex = _controller!.index; + _triggerAnimation(); + if (widget.isScrollable) { + _scrollToCurrentIndex(); + } + } + setState(() { + // Rebuild the tabs after a (potentially animated) index change + // has completed. + }); + } + + void _triggerAnimation() { + // reset the animation so it's ready to go + _animationController + ..reset() + ..forward(); + } + + // Called each time layout completes. + void _saveTabOffsets( + List tabOffsets, TextDirection textDirection, double width) { + _tabStripWidth = width; + _indicatorPainter?.saveTabOffsets(tabOffsets, textDirection); + } + + void _handleTap(int index) { + assert(index >= 0 && index < widget.tabs.length); + _controller?.animateTo(index); + widget.onTap?.call(index); + } + + Widget _buildStyledTab(Widget child, int index) { + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + + final double animationValue; + if (index == _currentIndex) { + animationValue = _animationController.value; + } else if (index == _prevIndex) { + animationValue = 1 - _animationController.value; + } else { + animationValue = 0; + } + + final TextStyle? textStyle = TextStyle.lerp( + (widget.unselectedLabelStyle ?? + tabBarTheme.labelStyle ?? + DefaultTextStyle.of(context).style) + .copyWith( + color: widget.unselectedLabelColor, + ), + (widget.labelStyle ?? + tabBarTheme.labelStyle ?? + DefaultTextStyle.of(context).style) + .copyWith( + color: widget.labelColor, + ), + animationValue); + + final Color? textColor = Color.lerp( + widget.unselectedLabelColor, widget.labelColor, animationValue); + + final Color? borderColor = Color.lerp( + widget.unselectedBorderColor, widget.borderColor, animationValue); + + BoxDecoration? boxDecoration = BoxDecoration.lerp( + BoxDecoration( + color: widget.unselectedDecoration?.color ?? + widget.unselectedBackgroundColor ?? + Colors.transparent, + boxShadow: widget.unselectedDecoration?.boxShadow, + gradient: widget.unselectedDecoration?.gradient, + borderRadius: widget.useToggleButtonStyle + ? null + : BorderRadius.circular(widget.borderRadius), + ), + BoxDecoration( + color: widget.decoration?.color ?? + widget.backgroundColor ?? + Colors.transparent, + boxShadow: widget.decoration?.boxShadow, + gradient: widget.decoration?.gradient, + borderRadius: widget.useToggleButtonStyle + ? null + : BorderRadius.circular(widget.borderRadius), + ), + animationValue); + + if (widget.useToggleButtonStyle && + widget.borderWidth > 0 && + boxDecoration != null) { + if (index == 0) { + boxDecoration = boxDecoration.copyWith( + border: Border( + right: BorderSide( + color: widget.unselectedBorderColor ?? Colors.transparent, + width: widget.borderWidth / 2, + ), + ), + ); + } else if (index == widget.tabs.length - 1) { + boxDecoration = boxDecoration.copyWith( + border: Border( + left: BorderSide( + color: widget.unselectedBorderColor ?? Colors.transparent, + width: widget.borderWidth / 2, + ), + ), + ); + } else { + boxDecoration = boxDecoration.copyWith( + border: Border.symmetric( + vertical: BorderSide( + color: widget.unselectedBorderColor ?? Colors.transparent, + width: widget.borderWidth / 2, + ), + ), + ); + } + } + + return Padding( + key: _tabKeys[index], + // padding for the buttons + padding: + widget.useToggleButtonStyle ? EdgeInsets.zero : widget.buttonMargin, + child: TextButton( + onPressed: () => _handleTap(index), + style: ButtonStyle( + elevation: MaterialStateProperty.all( + widget.useToggleButtonStyle ? 0 : widget.elevation), + + /// give a pretty small minimum size + minimumSize: MaterialStateProperty.all(const Size(10, 10)), + padding: MaterialStateProperty.all(EdgeInsets.zero), + textStyle: MaterialStateProperty.all(textStyle), + foregroundColor: MaterialStateProperty.all(textColor), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: MaterialStateProperty.all( + widget.useToggleButtonStyle + ? const RoundedRectangleBorder( + side: BorderSide.none, + borderRadius: BorderRadius.zero, + ) + : RoundedRectangleBorder( + side: (widget.borderWidth == 0) + ? BorderSide.none + : BorderSide( + color: borderColor ?? Colors.transparent, + width: widget.borderWidth, + ), + borderRadius: BorderRadius.circular(widget.borderRadius), + ), + ), + ), + child: Ink( + decoration: boxDecoration, + child: Container( + padding: widget.labelPadding, + alignment: Alignment.center, + child: child, + ), + ), + ), + ); + } + + bool _debugScheduleCheckHasValidTabsCount() { + if (_debugHasScheduledValidTabsCountCheck) { + return true; + } + WidgetsBinding.instance.addPostFrameCallback((Duration duration) { + _debugHasScheduledValidTabsCountCheck = false; + if (!mounted) { + return; + } + assert(() { + if (_controller!.length != widget.tabs.length) { + throw FlutterError( + "Controller's length property (${_controller!.length}) does not match the " + "number of tabs (${widget.tabs.length}) present in TabBar's tabs property.", + ); + } + return true; + }()); + }); + _debugHasScheduledValidTabsCountCheck = true; + return true; + } + + @override + Widget build(BuildContext context) { + assert(_debugScheduleCheckHasValidTabsCount()); + + if (_controller!.length == 0) { + return Container( + height: _kTabHeight + + widget.labelPadding.vertical + + widget.buttonMargin.vertical, + ); + } + + final List wrappedTabs = + List.generate(widget.tabs.length, (int index) { + return _buildStyledTab(widget.tabs[index], index); + }); + + final int tabCount = widget.tabs.length; + // Add the tap handler to each tab. If the tab bar is not scrollable, + // then give all of the tabs equal flexibility so that they each occupy + // the same share of the tab bar's overall width. + + for (int index = 0; index < tabCount; index += 1) { + if (!widget.isScrollable) { + wrappedTabs[index] = Expanded(child: wrappedTabs[index]); + } + } + + Widget tabBar = AnimatedBuilder( + animation: _animationController, + key: _tabsParentKey, + builder: (context, child) { + Widget tabBarTemp = _TabLabelBar( + onPerformLayout: _saveTabOffsets, + children: wrappedTabs, + ); + + if (widget.useToggleButtonStyle) { + tabBarTemp = Material( + shape: widget.useToggleButtonStyle + ? RoundedRectangleBorder( + side: (widget.borderWidth == 0) + ? BorderSide.none + : BorderSide( + color: widget.borderColor ?? Colors.transparent, + width: widget.borderWidth, + style: BorderStyle.solid, + ), + borderRadius: BorderRadius.circular(widget.borderRadius), + ) + : null, + elevation: widget.useToggleButtonStyle ? widget.elevation : 0, + clipBehavior: Clip.antiAliasWithSaveLayer, + child: tabBarTemp, + ); + } + return CustomPaint( + painter: _indicatorPainter, + child: tabBarTemp, + ); + }, + ); + + if (widget.isScrollable) { + _scrollController ??= _TabBarScrollController(this); + tabBar = SingleChildScrollView( + dragStartBehavior: widget.dragStartBehavior, + scrollDirection: Axis.horizontal, + controller: _scrollController, + padding: widget.padding, + physics: widget.physics, + child: tabBar, + ); + } else if (widget.padding != null) { + tabBar = Padding( + padding: widget.padding!, + child: tabBar, + ); + } + + return tabBar; + } +} diff --git a/lib/flutter_flow/flutter_flow_icon_button.dart b/lib/flutter_flow/flutter_flow_icon_button.dart new file mode 100644 index 0000000..ae964c2 --- /dev/null +++ b/lib/flutter_flow/flutter_flow_icon_button.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +class FlutterFlowIconButton extends StatefulWidget { + const FlutterFlowIconButton({ + Key? key, + required this.icon, + this.borderColor, + this.borderRadius, + this.borderWidth, + this.buttonSize, + this.fillColor, + this.disabledColor, + this.disabledIconColor, + this.hoverColor, + this.hoverIconColor, + this.onPressed, + this.showLoadingIndicator = false, + }) : super(key: key); + + final Widget icon; + final double? borderRadius; + final double? buttonSize; + final Color? fillColor; + final Color? disabledColor; + final Color? disabledIconColor; + final Color? hoverColor; + final Color? hoverIconColor; + final Color? borderColor; + final double? borderWidth; + final bool showLoadingIndicator; + final Function()? onPressed; + + @override + State createState() => _FlutterFlowIconButtonState(); +} + +class _FlutterFlowIconButtonState extends State { + bool loading = false; + late double? iconSize; + late Color? iconColor; + late Widget effectiveIcon; + + @override + void initState() { + super.initState(); + _updateIcon(); + } + + @override + void didUpdateWidget(FlutterFlowIconButton oldWidget) { + super.didUpdateWidget(oldWidget); + _updateIcon(); + } + + void _updateIcon() { + final isFontAwesome = widget.icon is FaIcon; + if (isFontAwesome) { + FaIcon icon = widget.icon as FaIcon; + effectiveIcon = FaIcon( + icon.icon, + size: icon.size, + ); + iconSize = icon.size; + iconColor = icon.color; + } else { + Icon icon = widget.icon as Icon; + effectiveIcon = Icon( + icon.icon, + size: icon.size, + ); + iconSize = icon.size; + iconColor = icon.color; + } + } + + @override + Widget build(BuildContext context) { + ButtonStyle style = ButtonStyle( + shape: MaterialStateProperty.resolveWith( + (states) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular(widget.borderRadius ?? 0), + side: BorderSide( + color: widget.borderColor ?? Colors.transparent, + width: widget.borderWidth ?? 0, + ), + ); + }, + ), + iconColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled) && + widget.disabledIconColor != null) { + return widget.disabledIconColor; + } + if (states.contains(MaterialState.hovered) && + widget.hoverIconColor != null) { + return widget.hoverIconColor; + } + return iconColor; + }, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled) && + widget.disabledColor != null) { + return widget.disabledColor; + } + if (states.contains(MaterialState.hovered) && + widget.hoverColor != null) { + return widget.hoverColor; + } + + return widget.fillColor; + }, + ), + ); + + return SizedBox( + width: widget.buttonSize, + height: widget.buttonSize, + child: Theme( + data: Theme.of(context).copyWith(useMaterial3: true), + child: IgnorePointer( + ignoring: (widget.showLoadingIndicator && loading), + child: IconButton( + icon: (widget.showLoadingIndicator && loading) + ? Container( + width: iconSize, + height: iconSize, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + iconColor ?? Colors.white, + ), + ), + ) + : effectiveIcon, + onPressed: widget.onPressed == null + ? null + : () async { + if (loading) { + return; + } + setState(() => loading = true); + try { + await widget.onPressed!(); + } finally { + if (mounted) { + setState(() => loading = false); + } + } + }, + splashRadius: widget.buttonSize, + style: style, + ), + ), + ), + ); + } +} diff --git a/lib/flutter_flow/flutter_flow_model.dart b/lib/flutter_flow/flutter_flow_model.dart new file mode 100644 index 0000000..7188e02 --- /dev/null +++ b/lib/flutter_flow/flutter_flow_model.dart @@ -0,0 +1,159 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; + +Widget wrapWithModel({ + required T model, + required Widget child, + required VoidCallback updateCallback, + bool updateOnChange = false, +}) { + // Set the component to optionally update the page on updates. + model.setOnUpdate( + onUpdate: updateCallback, + updateOnChange: updateOnChange, + ); + // Models for components within a page will be disposed by the page's model, + // so we don't want the component widget to dispose them until the page is + // itself disposed. + model.disposeOnWidgetDisposal = false; + // Wrap in a Provider so that the model can be accessed by the component. + return Provider.value( + value: model, + child: child, + ); +} + +T createModel( + BuildContext context, + T Function() defaultBuilder, +) { + final model = context.read() ?? defaultBuilder(); + model._init(context); + return model; +} + +abstract class FlutterFlowModel { + // Initialization methods + bool _isInitialized = false; + void initState(BuildContext context); + void _init(BuildContext context) { + if (!_isInitialized) { + initState(context); + _isInitialized = true; + } + } + + // Dispose methods + // Whether to dispose this model when the corresponding widget is + // disposed. By default this is true for pages and false for components, + // as page/component models handle the disposal of their children. + bool disposeOnWidgetDisposal = true; + void dispose(); + void maybeDispose() { + if (disposeOnWidgetDisposal) { + dispose(); + } + } + + // Whether to update the containing page / component on updates. + bool updateOnChange = false; + // Function to call when the model receives an update. + VoidCallback _updateCallback = () {}; + void onUpdate() => updateOnChange ? _updateCallback() : () {}; + FlutterFlowModel setOnUpdate({ + bool updateOnChange = false, + required VoidCallback onUpdate, + }) => + this + .._updateCallback = onUpdate + ..updateOnChange = updateOnChange; + // Update the containing page when this model received an update. + void updatePage(VoidCallback callback) { + callback(); + _updateCallback(); + } +} + +class FlutterFlowDynamicModels { + FlutterFlowDynamicModels(this.defaultBuilder); + + final T Function() defaultBuilder; + final Map _childrenModels = {}; + final Map _childrenIndexes = {}; + Set? _activeKeys; + + T getModel(String uniqueKey, int index) { + _updateActiveKeys(uniqueKey); + _childrenIndexes[uniqueKey] = index; + return _childrenModels[uniqueKey] ??= defaultBuilder(); + } + + List getValues(S? Function(T) getValue) { + return _childrenIndexes.entries + // Sort keys by index. + .sorted((a, b) => a.value.compareTo(b.value)) + .where((e) => _childrenModels[e.key] != null) + // Map each model to the desired value and return as list. In order + // to preserve index order, rather than removing null values we provide + // default values (for types with reasonable defaults). + .map((e) => getValue(_childrenModels[e.key]!) ?? _getDefaultValue()!) + .toList(); + } + + S? getValueAtIndex(int index, S? Function(T) getValue) { + final uniqueKey = + _childrenIndexes.entries.firstWhereOrNull((e) => e.value == index)?.key; + return getValueForKey(uniqueKey, getValue); + } + + S? getValueForKey(String? uniqueKey, S? Function(T) getValue) { + final model = _childrenModels[uniqueKey]; + return model != null ? getValue(model) : null; + } + + void dispose() => _childrenModels.values.forEach((model) => model.dispose()); + + void _updateActiveKeys(String uniqueKey) { + final shouldResetActiveKeys = _activeKeys == null; + _activeKeys ??= {}; + _activeKeys!.add(uniqueKey); + + if (shouldResetActiveKeys) { + // Add a post-frame callback to remove and dispose of unused models after + // we're done building, then reset `_activeKeys` to null so we know to do + // this again next build. + SchedulerBinding.instance.addPostFrameCallback((_) { + _childrenIndexes.removeWhere((k, _) => !_activeKeys!.contains(k)); + _childrenModels.keys + .toSet() + .difference(_activeKeys!) + // Remove and dispose of unused models since they are not being used + // elsewhere and would not otherwise be disposed. + .forEach((k) => _childrenModels.remove(k)?.dispose()); + _activeKeys = null; + }); + } + } +} + +T? _getDefaultValue() { + switch (T) { + case int: + return 0 as T; + case double: + return 0.0 as T; + case String: + return '' as T; + case bool: + return false as T; + default: + return null as T; + } +} + +extension TextValidationExtensions on String? Function(BuildContext, String?)? { + String? Function(String?)? asValidator(BuildContext context) => + this != null ? (val) => this!(context, val) : null; +} diff --git a/lib/flutter_flow/flutter_flow_theme.dart b/lib/flutter_flow/flutter_flow_theme.dart new file mode 100644 index 0000000..48e4f70 --- /dev/null +++ b/lib/flutter_flow/flutter_flow_theme.dart @@ -0,0 +1,350 @@ +// ignore_for_file: overridden_fields, annotate_overrides + +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import 'package:shared_preferences/shared_preferences.dart'; + +const kThemeModeKey = '__theme_mode__'; +SharedPreferences? _prefs; + +abstract class FlutterFlowTheme { + static Future initialize() async => + _prefs = await SharedPreferences.getInstance(); + static ThemeMode get themeMode { + final darkMode = _prefs?.getBool(kThemeModeKey); + return darkMode == null + ? ThemeMode.system + : darkMode + ? ThemeMode.dark + : ThemeMode.light; + } + + static void saveThemeMode(ThemeMode mode) => mode == ThemeMode.system + ? _prefs?.remove(kThemeModeKey) + : _prefs?.setBool(kThemeModeKey, mode == ThemeMode.dark); + + static FlutterFlowTheme of(BuildContext context) { + return Theme.of(context).brightness == Brightness.dark + ? DarkModeTheme() + : LightModeTheme(); + } + + @Deprecated('Use primary instead') + Color get primaryColor => primary; + @Deprecated('Use secondary instead') + Color get secondaryColor => secondary; + @Deprecated('Use tertiary instead') + Color get tertiaryColor => tertiary; + + late Color primary; + late Color secondary; + late Color tertiary; + late Color alternate; + late Color primaryText; + late Color secondaryText; + late Color primaryBackground; + late Color secondaryBackground; + late Color accent1; + late Color accent2; + late Color accent3; + late Color accent4; + late Color success; + late Color warning; + late Color error; + late Color info; + + @Deprecated('Use displaySmallFamily instead') + String get title1Family => displaySmallFamily; + @Deprecated('Use displaySmall instead') + TextStyle get title1 => typography.displaySmall; + @Deprecated('Use headlineMediumFamily instead') + String get title2Family => typography.headlineMediumFamily; + @Deprecated('Use headlineMedium instead') + TextStyle get title2 => typography.headlineMedium; + @Deprecated('Use headlineSmallFamily instead') + String get title3Family => typography.headlineSmallFamily; + @Deprecated('Use headlineSmall instead') + TextStyle get title3 => typography.headlineSmall; + @Deprecated('Use titleMediumFamily instead') + String get subtitle1Family => typography.titleMediumFamily; + @Deprecated('Use titleMedium instead') + TextStyle get subtitle1 => typography.titleMedium; + @Deprecated('Use titleSmallFamily instead') + String get subtitle2Family => typography.titleSmallFamily; + @Deprecated('Use titleSmall instead') + TextStyle get subtitle2 => typography.titleSmall; + @Deprecated('Use bodyMediumFamily instead') + String get bodyText1Family => typography.bodyMediumFamily; + @Deprecated('Use bodyMedium instead') + TextStyle get bodyText1 => typography.bodyMedium; + @Deprecated('Use bodySmallFamily instead') + String get bodyText2Family => typography.bodySmallFamily; + @Deprecated('Use bodySmall instead') + TextStyle get bodyText2 => typography.bodySmall; + + String get displayLargeFamily => typography.displayLargeFamily; + TextStyle get displayLarge => typography.displayLarge; + String get displayMediumFamily => typography.displayMediumFamily; + TextStyle get displayMedium => typography.displayMedium; + String get displaySmallFamily => typography.displaySmallFamily; + TextStyle get displaySmall => typography.displaySmall; + String get headlineLargeFamily => typography.headlineLargeFamily; + TextStyle get headlineLarge => typography.headlineLarge; + String get headlineMediumFamily => typography.headlineMediumFamily; + TextStyle get headlineMedium => typography.headlineMedium; + String get headlineSmallFamily => typography.headlineSmallFamily; + TextStyle get headlineSmall => typography.headlineSmall; + String get titleLargeFamily => typography.titleLargeFamily; + TextStyle get titleLarge => typography.titleLarge; + String get titleMediumFamily => typography.titleMediumFamily; + TextStyle get titleMedium => typography.titleMedium; + String get titleSmallFamily => typography.titleSmallFamily; + TextStyle get titleSmall => typography.titleSmall; + String get labelLargeFamily => typography.labelLargeFamily; + TextStyle get labelLarge => typography.labelLarge; + String get labelMediumFamily => typography.labelMediumFamily; + TextStyle get labelMedium => typography.labelMedium; + String get labelSmallFamily => typography.labelSmallFamily; + TextStyle get labelSmall => typography.labelSmall; + String get bodyLargeFamily => typography.bodyLargeFamily; + TextStyle get bodyLarge => typography.bodyLarge; + String get bodyMediumFamily => typography.bodyMediumFamily; + TextStyle get bodyMedium => typography.bodyMedium; + String get bodySmallFamily => typography.bodySmallFamily; + TextStyle get bodySmall => typography.bodySmall; + + Typography get typography => ThemeTypography(this); +} + +class LightModeTheme extends FlutterFlowTheme { + @Deprecated('Use primary instead') + Color get primaryColor => primary; + @Deprecated('Use secondary instead') + Color get secondaryColor => secondary; + @Deprecated('Use tertiary instead') + Color get tertiaryColor => tertiary; + + late Color primary = const Color(0xFF4B39EF); + late Color secondary = const Color(0xFF39D2C0); + late Color tertiary = const Color(0xFFEE8B60); + late Color alternate = const Color(0xFFE0E3E7); + late Color primaryText = const Color(0xFF14181B); + late Color secondaryText = const Color(0xFF57636C); + late Color primaryBackground = const Color(0xFFF1F4F8); + late Color secondaryBackground = const Color(0xFFFFFFFF); + late Color accent1 = const Color(0x4C4B39EF); + late Color accent2 = const Color(0x4D39D2C0); + late Color accent3 = const Color(0x4DEE8B60); + late Color accent4 = const Color(0xCCFFFFFF); + late Color success = const Color(0xFF249689); + late Color warning = const Color(0xFFF9CF58); + late Color error = const Color(0xFFFF5963); + late Color info = const Color(0xFFFFFFFF); +} + +abstract class Typography { + String get displayLargeFamily; + TextStyle get displayLarge; + String get displayMediumFamily; + TextStyle get displayMedium; + String get displaySmallFamily; + TextStyle get displaySmall; + String get headlineLargeFamily; + TextStyle get headlineLarge; + String get headlineMediumFamily; + TextStyle get headlineMedium; + String get headlineSmallFamily; + TextStyle get headlineSmall; + String get titleLargeFamily; + TextStyle get titleLarge; + String get titleMediumFamily; + TextStyle get titleMedium; + String get titleSmallFamily; + TextStyle get titleSmall; + String get labelLargeFamily; + TextStyle get labelLarge; + String get labelMediumFamily; + TextStyle get labelMedium; + String get labelSmallFamily; + TextStyle get labelSmall; + String get bodyLargeFamily; + TextStyle get bodyLarge; + String get bodyMediumFamily; + TextStyle get bodyMedium; + String get bodySmallFamily; + TextStyle get bodySmall; +} + +class ThemeTypography extends Typography { + ThemeTypography(this.theme); + + final FlutterFlowTheme theme; + + String get displayLargeFamily => 'Outfit'; + TextStyle get displayLarge => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.normal, + fontSize: 64.0, + ); + String get displayMediumFamily => 'Outfit'; + TextStyle get displayMedium => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.normal, + fontSize: 44.0, + ); + String get displaySmallFamily => 'Outfit'; + TextStyle get displaySmall => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.w600, + fontSize: 36.0, + ); + String get headlineLargeFamily => 'Outfit'; + TextStyle get headlineLarge => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.w600, + fontSize: 32.0, + ); + String get headlineMediumFamily => 'Outfit'; + TextStyle get headlineMedium => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.normal, + fontSize: 24.0, + ); + String get headlineSmallFamily => 'Outfit'; + TextStyle get headlineSmall => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.w500, + fontSize: 24.0, + ); + String get titleLargeFamily => 'Outfit'; + TextStyle get titleLarge => GoogleFonts.getFont( + 'Outfit', + color: theme.primaryText, + fontWeight: FontWeight.w500, + fontSize: 22.0, + ); + String get titleMediumFamily => 'Readex Pro'; + TextStyle get titleMedium => GoogleFonts.getFont( + 'Readex Pro', + color: theme.info, + fontWeight: FontWeight.normal, + fontSize: 18.0, + ); + String get titleSmallFamily => 'Readex Pro'; + TextStyle get titleSmall => GoogleFonts.getFont( + 'Readex Pro', + color: theme.info, + fontWeight: FontWeight.w500, + fontSize: 16.0, + ); + String get labelLargeFamily => 'Readex Pro'; + TextStyle get labelLarge => GoogleFonts.getFont( + 'Readex Pro', + color: theme.secondaryText, + fontWeight: FontWeight.normal, + fontSize: 16.0, + ); + String get labelMediumFamily => 'Readex Pro'; + TextStyle get labelMedium => GoogleFonts.getFont( + 'Readex Pro', + color: theme.secondaryText, + fontWeight: FontWeight.normal, + fontSize: 14.0, + ); + String get labelSmallFamily => 'Readex Pro'; + TextStyle get labelSmall => GoogleFonts.getFont( + 'Readex Pro', + color: theme.secondaryText, + fontWeight: FontWeight.normal, + fontSize: 12.0, + ); + String get bodyLargeFamily => 'Readex Pro'; + TextStyle get bodyLarge => GoogleFonts.getFont( + 'Readex Pro', + color: theme.primaryText, + fontWeight: FontWeight.normal, + fontSize: 16.0, + ); + String get bodyMediumFamily => 'Readex Pro'; + TextStyle get bodyMedium => GoogleFonts.getFont( + 'Readex Pro', + color: theme.primaryText, + fontWeight: FontWeight.normal, + fontSize: 14.0, + ); + String get bodySmallFamily => 'Readex Pro'; + TextStyle get bodySmall => GoogleFonts.getFont( + 'Readex Pro', + color: theme.primaryText, + fontWeight: FontWeight.normal, + fontSize: 12.0, + ); +} + +class DarkModeTheme extends FlutterFlowTheme { + @Deprecated('Use primary instead') + Color get primaryColor => primary; + @Deprecated('Use secondary instead') + Color get secondaryColor => secondary; + @Deprecated('Use tertiary instead') + Color get tertiaryColor => tertiary; + + late Color primary = const Color(0xFF4B39EF); + late Color secondary = const Color(0xFF39D2C0); + late Color tertiary = const Color(0xFFEE8B60); + late Color alternate = const Color(0xFF262D34); + late Color primaryText = const Color(0xFFFFFFFF); + late Color secondaryText = const Color(0xFF95A1AC); + late Color primaryBackground = const Color(0xFF1D2428); + late Color secondaryBackground = const Color(0xFF14181B); + late Color accent1 = const Color(0x4C4B39EF); + late Color accent2 = const Color(0x4D39D2C0); + late Color accent3 = const Color(0x4DEE8B60); + late Color accent4 = const Color(0xB2262D34); + late Color success = const Color(0xFF249689); + late Color warning = const Color(0xFFF9CF58); + late Color error = const Color(0xFFFF5963); + late Color info = const Color(0xFFFFFFFF); +} + +extension TextStyleHelper on TextStyle { + TextStyle override({ + String? fontFamily, + Color? color, + double? fontSize, + FontWeight? fontWeight, + double? letterSpacing, + FontStyle? fontStyle, + bool useGoogleFonts = true, + TextDecoration? decoration, + double? lineHeight, + }) => + useGoogleFonts + ? GoogleFonts.getFont( + fontFamily!, + color: color ?? this.color, + fontSize: fontSize ?? this.fontSize, + letterSpacing: letterSpacing ?? this.letterSpacing, + fontWeight: fontWeight ?? this.fontWeight, + fontStyle: fontStyle ?? this.fontStyle, + decoration: decoration, + height: lineHeight, + ) + : copyWith( + fontFamily: fontFamily, + color: color, + fontSize: fontSize, + letterSpacing: letterSpacing, + fontWeight: fontWeight, + fontStyle: fontStyle, + decoration: decoration, + height: lineHeight, + ); +} diff --git a/lib/flutter_flow/flutter_flow_util.dart b/lib/flutter_flow/flutter_flow_util.dart new file mode 100644 index 0000000..a39e81d --- /dev/null +++ b/lib/flutter_flow/flutter_flow_util.dart @@ -0,0 +1,305 @@ +import 'dart:io'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; +import 'package:from_css_color/from_css_color.dart'; +import 'package:intl/intl.dart'; +import 'package:json_path/json_path.dart'; +import 'package:timeago/timeago.dart' as timeago; +import 'package:url_launcher/url_launcher.dart'; + +import '../main.dart'; + +import 'lat_lng.dart'; + +export 'keep_alive_wrapper.dart'; +export 'lat_lng.dart'; +export 'place.dart'; +export 'uploaded_file.dart'; +export '../app_state.dart'; +export 'flutter_flow_model.dart'; +export 'dart:math' show min, max; +export 'dart:typed_data' show Uint8List; +export 'dart:convert' show jsonEncode, jsonDecode; +export 'package:intl/intl.dart'; +export 'package:cloud_firestore/cloud_firestore.dart' + show DocumentReference, FirebaseFirestore; +export 'package:page_transition/page_transition.dart'; +export 'nav/nav.dart'; + +T valueOrDefault(T? value, T defaultValue) => + (value is String && value.isEmpty) || value == null ? defaultValue : value; + +String dateTimeFormat(String format, DateTime? dateTime, {String? locale}) { + if (dateTime == null) { + return ''; + } + if (format == 'relative') { + return timeago.format(dateTime, locale: locale, allowFromNow: true); + } + return DateFormat(format, locale).format(dateTime); +} + +Future launchURL(String url) async { + var uri = Uri.parse(url).toString(); + try { + await launch(uri); + } catch (e) { + throw 'Could not launch $uri: $e'; + } +} + +Color colorFromCssString(String color, {Color? defaultColor}) { + try { + return fromCssColor(color); + } catch (_) {} + return defaultColor ?? Colors.black; +} + +enum FormatType { + decimal, + percent, + scientific, + compact, + compactLong, + custom, +} + +enum DecimalType { + automatic, + periodDecimal, + commaDecimal, +} + +String formatNumber( + num? value, { + required FormatType formatType, + DecimalType? decimalType, + String? currency, + bool toLowerCase = false, + String? format, + String? locale, +}) { + if (value == null) { + return ''; + } + var formattedValue = ''; + switch (formatType) { + case FormatType.decimal: + switch (decimalType!) { + case DecimalType.automatic: + formattedValue = NumberFormat.decimalPattern().format(value); + break; + case DecimalType.periodDecimal: + formattedValue = NumberFormat.decimalPattern('en_US').format(value); + break; + case DecimalType.commaDecimal: + formattedValue = NumberFormat.decimalPattern('es_PA').format(value); + break; + } + break; + case FormatType.percent: + formattedValue = NumberFormat.percentPattern().format(value); + break; + case FormatType.scientific: + formattedValue = NumberFormat.scientificPattern().format(value); + if (toLowerCase) { + formattedValue = formattedValue.toLowerCase(); + } + break; + case FormatType.compact: + formattedValue = NumberFormat.compact().format(value); + break; + case FormatType.compactLong: + formattedValue = NumberFormat.compactLong().format(value); + break; + case FormatType.custom: + final hasLocale = locale != null && locale.isNotEmpty; + formattedValue = + NumberFormat(format, hasLocale ? locale : null).format(value); + } + + if (formattedValue.isEmpty) { + return value.toString(); + } + + if (currency != null) { + final currencySymbol = currency.isNotEmpty + ? currency + : NumberFormat.simpleCurrency().format(0.0).substring(0, 1); + formattedValue = '$currencySymbol$formattedValue'; + } + + return formattedValue; +} + +DateTime get getCurrentTimestamp => DateTime.now(); +DateTime dateTimeFromSecondsSinceEpoch(int seconds) { + return DateTime.fromMillisecondsSinceEpoch(seconds * 1000); +} + +extension DateTimeConversionExtension on DateTime { + int get secondsSinceEpoch => (millisecondsSinceEpoch / 1000).round(); +} + +extension DateTimeComparisonOperators on DateTime { + bool operator <(DateTime other) => isBefore(other); + bool operator >(DateTime other) => isAfter(other); + bool operator <=(DateTime other) => this < other || isAtSameMomentAs(other); + bool operator >=(DateTime other) => this > other || isAtSameMomentAs(other); +} + +dynamic getJsonField( + dynamic response, + String jsonPath, [ + bool isForList = false, +]) { + final field = JsonPath(jsonPath).read(response); + if (field.isEmpty) { + return null; + } + if (field.length > 1) { + return field.map((f) => f.value).toList(); + } + final value = field.first.value; + return isForList && value is! Iterable ? [value] : value; +} + +Rect? getWidgetBoundingBox(BuildContext context) { + try { + final renderBox = context.findRenderObject() as RenderBox?; + return renderBox!.localToGlobal(Offset.zero) & renderBox.size; + } catch (_) { + return null; + } +} + +bool get isAndroid => !kIsWeb && Platform.isAndroid; +bool get isiOS => !kIsWeb && Platform.isIOS; +bool get isWeb => kIsWeb; + +const kBreakpointSmall = 479.0; +const kBreakpointMedium = 767.0; +const kBreakpointLarge = 991.0; +bool isMobileWidth(BuildContext context) => + MediaQuery.sizeOf(context).width < kBreakpointSmall; +bool responsiveVisibility({ + required BuildContext context, + bool phone = true, + bool tablet = true, + bool tabletLandscape = true, + bool desktop = true, +}) { + final width = MediaQuery.sizeOf(context).width; + if (width < kBreakpointSmall) { + return phone; + } else if (width < kBreakpointMedium) { + return tablet; + } else if (width < kBreakpointLarge) { + return tabletLandscape; + } else { + return desktop; + } +} + +const kTextValidatorUsernameRegex = r'^[a-zA-Z][a-zA-Z0-9_-]{2,16}$'; +const kTextValidatorEmailRegex = + r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"; +const kTextValidatorWebsiteRegex = + r'(https?:\/\/)?(www\.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,10}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)|(https?:\/\/)?(www\.)?(?!ww)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,10}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)'; + +extension FFTextEditingControllerExt on TextEditingController? { + String get text => this == null ? '' : this!.text; + set text(String newText) => this?.text = newText; +} + +extension IterableExt on Iterable { + List mapIndexed(S Function(int, T) func) => toList() + .asMap() + .map((index, value) => MapEntry(index, func(index, value))) + .values + .toList(); +} + +extension StringDocRef on String { + DocumentReference get ref => FirebaseFirestore.instance.doc(this); +} + +void setAppLanguage(BuildContext context, String language) => + MyApp.of(context).setLocale(language); + +void setDarkModeSetting(BuildContext context, ThemeMode themeMode) => + MyApp.of(context).setThemeMode(themeMode); + +void showSnackbar( + BuildContext context, + String message, { + bool loading = false, + int duration = 4, +}) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + if (loading) + Padding( + padding: EdgeInsetsDirectional.only(end: 10.0), + child: Container( + height: 20, + width: 20, + child: const CircularProgressIndicator( + color: Colors.white, + ), + ), + ), + Text(message), + ], + ), + duration: Duration(seconds: duration), + ), + ); +} + +extension FFStringExt on String { + String maybeHandleOverflow({int? maxChars, String replacement = ''}) => + maxChars != null && length > maxChars + ? replaceRange(maxChars, null, replacement) + : this; +} + +extension ListFilterExt on Iterable { + List get withoutNulls => where((s) => s != null).map((e) => e!).toList(); +} + +extension ListDivideExt on Iterable { + Iterable> get enumerate => toList().asMap().entries; + + List divide(Widget t) => isEmpty + ? [] + : (enumerate.map((e) => [e.value, t]).expand((i) => i).toList() + ..removeLast()); + + List around(Widget t) => addToStart(t).addToEnd(t); + + List addToStart(Widget t) => + enumerate.map((e) => e.value).toList()..insert(0, t); + + List addToEnd(Widget t) => + enumerate.map((e) => e.value).toList()..add(t); + + List paddingTopEach(double val) => + map((w) => Padding(padding: EdgeInsets.only(top: val), child: w)) + .toList(); +} + +extension StatefulWidgetExtensions on State { + /// Check if the widget exist before safely setting state. + void safeSetState(VoidCallback fn) { + if (mounted) { + // ignore: invalid_use_of_protected_member + setState(fn); + } + } +} diff --git a/lib/flutter_flow/flutter_flow_widgets.dart b/lib/flutter_flow/flutter_flow_widgets.dart new file mode 100644 index 0000000..1d7fd67 --- /dev/null +++ b/lib/flutter_flow/flutter_flow_widgets.dart @@ -0,0 +1,242 @@ +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:auto_size_text/auto_size_text.dart'; + +class FFButtonOptions { + const FFButtonOptions({ + this.textStyle, + this.elevation, + this.height, + this.width, + this.padding, + this.color, + this.disabledColor, + this.disabledTextColor, + this.splashColor, + this.iconSize, + this.iconColor, + this.iconPadding, + this.borderRadius, + this.borderSide, + this.hoverColor, + this.hoverBorderSide, + this.hoverTextColor, + this.hoverElevation, + this.maxLines, + }); + + final TextStyle? textStyle; + final double? elevation; + final double? height; + final double? width; + final EdgeInsetsGeometry? padding; + final Color? color; + final Color? disabledColor; + final Color? disabledTextColor; + final int? maxLines; + final Color? splashColor; + final double? iconSize; + final Color? iconColor; + final EdgeInsetsGeometry? iconPadding; + final BorderRadius? borderRadius; + final BorderSide? borderSide; + final Color? hoverColor; + final BorderSide? hoverBorderSide; + final Color? hoverTextColor; + final double? hoverElevation; +} + +class FFButtonWidget extends StatefulWidget { + const FFButtonWidget({ + Key? key, + required this.text, + required this.onPressed, + this.icon, + this.iconData, + required this.options, + this.showLoadingIndicator = true, + }) : super(key: key); + + final String text; + final Widget? icon; + final IconData? iconData; + final Function()? onPressed; + final FFButtonOptions options; + final bool showLoadingIndicator; + + @override + State createState() => _FFButtonWidgetState(); +} + +class _FFButtonWidgetState extends State { + bool loading = false; + + int get maxLines => widget.options.maxLines ?? 1; + + @override + Widget build(BuildContext context) { + Widget textWidget = loading + ? Center( + child: Container( + width: 23, + height: 23, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + widget.options.textStyle!.color ?? Colors.white, + ), + ), + ), + ) + : AutoSizeText( + widget.text, + style: widget.options.textStyle?.withoutColor(), + maxLines: maxLines, + overflow: TextOverflow.ellipsis, + ); + + final onPressed = widget.onPressed != null + ? (widget.showLoadingIndicator + ? () async { + if (loading) { + return; + } + setState(() => loading = true); + try { + await widget.onPressed!(); + } finally { + if (mounted) { + setState(() => loading = false); + } + } + } + : () => widget.onPressed!()) + : null; + + ButtonStyle style = ButtonStyle( + shape: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.hovered) && + widget.options.hoverBorderSide != null) { + return RoundedRectangleBorder( + borderRadius: + widget.options.borderRadius ?? BorderRadius.circular(8), + side: widget.options.hoverBorderSide!, + ); + } + return RoundedRectangleBorder( + borderRadius: + widget.options.borderRadius ?? BorderRadius.circular(8), + side: widget.options.borderSide ?? BorderSide.none, + ); + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled) && + widget.options.disabledTextColor != null) { + return widget.options.disabledTextColor; + } + if (states.contains(MaterialState.hovered) && + widget.options.hoverTextColor != null) { + return widget.options.hoverTextColor; + } + return widget.options.textStyle?.color; + }, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled) && + widget.options.disabledColor != null) { + return widget.options.disabledColor; + } + if (states.contains(MaterialState.hovered) && + widget.options.hoverColor != null) { + return widget.options.hoverColor; + } + return widget.options.color; + }, + ), + overlayColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.pressed)) { + return widget.options.splashColor; + } + return null; + }), + padding: MaterialStateProperty.all(widget.options.padding ?? + const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0)), + elevation: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.hovered) && + widget.options.hoverElevation != null) { + return widget.options.hoverElevation!; + } + return widget.options.elevation; + }, + ), + ); + + if ((widget.icon != null || widget.iconData != null) && !loading) { + return Container( + height: widget.options.height, + width: widget.options.width, + child: ElevatedButton.icon( + icon: Padding( + padding: widget.options.iconPadding ?? EdgeInsets.zero, + child: widget.icon ?? + FaIcon( + widget.iconData, + size: widget.options.iconSize, + color: widget.options.iconColor ?? + widget.options.textStyle!.color, + ), + ), + label: textWidget, + onPressed: onPressed, + style: style, + ), + ); + } + + return Container( + height: widget.options.height, + width: widget.options.width, + child: ElevatedButton( + onPressed: onPressed, + style: style, + child: textWidget, + ), + ); + } +} + +extension _WithoutColorExtension on TextStyle { + TextStyle withoutColor() => TextStyle( + inherit: inherit, + color: null, + backgroundColor: backgroundColor, + fontSize: fontSize, + fontWeight: fontWeight, + fontStyle: fontStyle, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + textBaseline: textBaseline, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + foreground: foreground, + background: background, + shadows: shadows, + fontFeatures: fontFeatures, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + debugLabel: debugLabel, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + // The _package field is private so unfortunately we can't set it here, + // but it's almost always unset anyway. + // package: _package, + overflow: overflow, + ); +} diff --git a/lib/flutter_flow/form_field_controller.dart b/lib/flutter_flow/form_field_controller.dart new file mode 100644 index 0000000..99b4ecb --- /dev/null +++ b/lib/flutter_flow/form_field_controller.dart @@ -0,0 +1,9 @@ +import 'package:flutter/foundation.dart'; + +class FormFieldController extends ValueNotifier { + FormFieldController(this.initialValue) : super(initialValue); + + final T? initialValue; + + void reset() => value = initialValue; +} diff --git a/lib/flutter_flow/internationalization.dart b/lib/flutter_flow/internationalization.dart new file mode 100644 index 0000000..f9ea258 --- /dev/null +++ b/lib/flutter_flow/internationalization.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +const _kLocaleStorageKey = '__locale_key__'; + +class FFLocalizations { + FFLocalizations(this.locale); + + final Locale locale; + + static FFLocalizations of(BuildContext context) => + Localizations.of(context, FFLocalizations)!; + + static List languages() => ['en']; + + static late SharedPreferences _prefs; + static Future initialize() async => + _prefs = await SharedPreferences.getInstance(); + static Future storeLocale(String locale) => + _prefs.setString(_kLocaleStorageKey, locale); + static Locale? getStoredLocale() { + final locale = _prefs.getString(_kLocaleStorageKey); + return locale != null && locale.isNotEmpty ? createLocale(locale) : null; + } + + String get languageCode => locale.toString(); + String? get languageShortCode => + _languagesWithShortCode.contains(locale.toString()) + ? '${locale.toString()}_short' + : null; + int get languageIndex => languages().contains(languageCode) + ? languages().indexOf(languageCode) + : 0; + + String getText(String key) => + (kTranslationsMap[key] ?? {})[locale.toString()] ?? ''; + + String getVariableText({ + String? enText = '', + }) => + [enText][languageIndex] ?? ''; + + static const Set _languagesWithShortCode = { + 'ar', + 'az', + 'ca', + 'cs', + 'da', + 'de', + 'dv', + 'en', + 'es', + 'et', + 'fi', + 'fr', + 'gr', + 'he', + 'hi', + 'hu', + 'it', + 'km', + 'ku', + 'mn', + 'ms', + 'no', + 'pt', + 'ro', + 'ru', + 'rw', + 'sv', + 'th', + 'uk', + 'vi', + }; +} + +class FFLocalizationsDelegate extends LocalizationsDelegate { + const FFLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) { + final language = locale.toString(); + return FFLocalizations.languages().contains( + language.endsWith('_') + ? language.substring(0, language.length - 1) + : language, + ); + } + + @override + Future load(Locale locale) => + SynchronousFuture(FFLocalizations(locale)); + + @override + bool shouldReload(FFLocalizationsDelegate old) => false; +} + +Locale createLocale(String language) => language.contains('_') + ? Locale.fromSubtags( + languageCode: language.split('_').first, + scriptCode: language.split('_').last, + ) + : Locale(language); + +final kTranslationsMap = + >>[].reduce((a, b) => a..addAll(b)); diff --git a/lib/flutter_flow/keep_alive_wrapper.dart b/lib/flutter_flow/keep_alive_wrapper.dart new file mode 100644 index 0000000..9b747f8 --- /dev/null +++ b/lib/flutter_flow/keep_alive_wrapper.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class KeepAliveWidgetWrapper extends StatefulWidget { + const KeepAliveWidgetWrapper({ + Key? key, + required this.builder, + }) : super(key: key); + + final WidgetBuilder builder; + + @override + State createState() => _KeepAliveWidgetWrapperState(); +} + +class _KeepAliveWidgetWrapperState extends State + with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + + @override + Widget build(BuildContext context) { + super.build(context); + return widget.builder(context); + } +} diff --git a/lib/flutter_flow/lat_lng.dart b/lib/flutter_flow/lat_lng.dart new file mode 100644 index 0000000..9e7b8ea --- /dev/null +++ b/lib/flutter_flow/lat_lng.dart @@ -0,0 +1,19 @@ +class LatLng { + const LatLng(this.latitude, this.longitude); + final double latitude; + final double longitude; + + @override + String toString() => 'LatLng(lat: $latitude, lng: $longitude)'; + + String serialize() => '$latitude,$longitude'; + + @override + int get hashCode => latitude.hashCode + longitude.hashCode; + + @override + bool operator ==(other) => + other is LatLng && + latitude == other.latitude && + longitude == other.longitude; +} diff --git a/lib/flutter_flow/nav/nav.dart b/lib/flutter_flow/nav/nav.dart new file mode 100644 index 0000000..02af468 --- /dev/null +++ b/lib/flutter_flow/nav/nav.dart @@ -0,0 +1,354 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:page_transition/page_transition.dart'; +import '/backend/backend.dart'; + +import '../../auth/base_auth_user_provider.dart'; + +import '/index.dart'; +import '/main.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/lat_lng.dart'; +import '/flutter_flow/place.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import 'serialization_util.dart'; + +export 'package:go_router/go_router.dart'; +export 'serialization_util.dart'; + +const kTransitionInfoKey = '__transition_info__'; + +class AppStateNotifier extends ChangeNotifier { + AppStateNotifier._(); + + static AppStateNotifier? _instance; + static AppStateNotifier get instance => _instance ??= AppStateNotifier._(); + + BaseAuthUser? initialUser; + BaseAuthUser? user; + bool showSplashImage = true; + String? _redirectLocation; + + /// Determines whether the app will refresh and build again when a sign + /// in or sign out happens. This is useful when the app is launched or + /// on an unexpected logout. However, this must be turned off when we + /// intend to sign in/out and then navigate or perform any actions after. + /// Otherwise, this will trigger a refresh and interrupt the action(s). + bool notifyOnAuthChange = true; + + bool get loading => user == null || showSplashImage; + bool get loggedIn => user?.loggedIn ?? false; + bool get initiallyLoggedIn => initialUser?.loggedIn ?? false; + bool get shouldRedirect => loggedIn && _redirectLocation != null; + + String getRedirectLocation() => _redirectLocation!; + bool hasRedirect() => _redirectLocation != null; + void setRedirectLocationIfUnset(String loc) => _redirectLocation ??= loc; + void clearRedirectLocation() => _redirectLocation = null; + + /// Mark as not needing to notify on a sign in / out when we intend + /// to perform subsequent actions (such as navigation) afterwards. + void updateNotifyOnAuthChange(bool notify) => notifyOnAuthChange = notify; + + void update(BaseAuthUser newUser) { + final shouldUpdate = + user?.uid == null || newUser.uid == null || user?.uid != newUser.uid; + initialUser ??= newUser; + user = newUser; + // Refresh the app on auth change unless explicitly marked otherwise. + // No need to update unless the user has changed. + if (notifyOnAuthChange && shouldUpdate) { + notifyListeners(); + } + // Once again mark the notifier as needing to update on auth change + // (in order to catch sign in / out events). + updateNotifyOnAuthChange(true); + } + + void stopShowingSplashImage() { + showSplashImage = false; + notifyListeners(); + } +} + +GoRouter createRouter(AppStateNotifier appStateNotifier) => GoRouter( + initialLocation: '/', + debugLogDiagnostics: true, + refreshListenable: appStateNotifier, + errorBuilder: (context, state) => + appStateNotifier.loggedIn ? NavBarPage() : LoginWidget(), + routes: [ + FFRoute( + name: '_initialize', + path: '/', + builder: (context, _) => + appStateNotifier.loggedIn ? NavBarPage() : LoginWidget(), + ), + FFRoute( + name: 'login', + path: '/login', + builder: (context, params) => LoginWidget(), + ), + FFRoute( + name: 'signup', + path: '/signup', + builder: (context, params) => SignupWidget(), + ), + FFRoute( + name: 'categories', + path: '/categories', + builder: (context, params) => params.isEmpty + ? NavBarPage(initialPage: 'categories') + : CategoriesWidget(), + ), + FFRoute( + name: 'task', + path: '/task', + builder: (context, params) => + params.isEmpty ? NavBarPage(initialPage: 'task') : TaskWidget(), + ), + FFRoute( + name: 'profile', + path: '/profile', + builder: (context, params) => params.isEmpty + ? NavBarPage(initialPage: 'profile') + : ProfileWidget(), + ), + FFRoute( + name: 'leaderboard', + path: '/leaderboard', + builder: (context, params) => params.isEmpty + ? NavBarPage(initialPage: 'leaderboard') + : LeaderboardWidget(), + ), + FFRoute( + name: 'homepage', + path: '/homepage', + builder: (context, params) => params.isEmpty + ? NavBarPage(initialPage: 'homepage') + : HomepageWidget(), + ) + ].map((r) => r.toRoute(appStateNotifier)).toList(), + ); + +extension NavParamExtensions on Map { + Map get withoutNulls => Map.fromEntries( + entries + .where((e) => e.value != null) + .map((e) => MapEntry(e.key, e.value!)), + ); +} + +extension NavigationExtensions on BuildContext { + void goNamedAuth( + String name, + bool mounted, { + Map pathParameters = const {}, + Map queryParameters = const {}, + Object? extra, + bool ignoreRedirect = false, + }) => + !mounted || GoRouter.of(this).shouldRedirect(ignoreRedirect) + ? null + : goNamed( + name, + pathParameters: pathParameters, + queryParameters: queryParameters, + extra: extra, + ); + + void pushNamedAuth( + String name, + bool mounted, { + Map pathParameters = const {}, + Map queryParameters = const {}, + Object? extra, + bool ignoreRedirect = false, + }) => + !mounted || GoRouter.of(this).shouldRedirect(ignoreRedirect) + ? null + : pushNamed( + name, + pathParameters: pathParameters, + queryParameters: queryParameters, + extra: extra, + ); + + void safePop() { + // If there is only one route on the stack, navigate to the initial + // page instead of popping. + if (canPop()) { + pop(); + } else { + go('/'); + } + } +} + +extension GoRouterExtensions on GoRouter { + AppStateNotifier get appState => AppStateNotifier.instance; + void prepareAuthEvent([bool ignoreRedirect = false]) => + appState.hasRedirect() && !ignoreRedirect + ? null + : appState.updateNotifyOnAuthChange(false); + bool shouldRedirect(bool ignoreRedirect) => + !ignoreRedirect && appState.hasRedirect(); + void clearRedirectLocation() => appState.clearRedirectLocation(); + void setRedirectLocationIfUnset(String location) => + appState.updateNotifyOnAuthChange(false); +} + +extension _GoRouterStateExtensions on GoRouterState { + Map get extraMap => + extra != null ? extra as Map : {}; + Map get allParams => {} + ..addAll(pathParameters) + ..addAll(queryParameters) + ..addAll(extraMap); + TransitionInfo get transitionInfo => extraMap.containsKey(kTransitionInfoKey) + ? extraMap[kTransitionInfoKey] as TransitionInfo + : TransitionInfo.appDefault(); +} + +class FFParameters { + FFParameters(this.state, [this.asyncParams = const {}]); + + final GoRouterState state; + final Map Function(String)> asyncParams; + + Map futureParamValues = {}; + + // Parameters are empty if the params map is empty or if the only parameter + // present is the special extra parameter reserved for the transition info. + bool get isEmpty => + state.allParams.isEmpty || + (state.extraMap.length == 1 && + state.extraMap.containsKey(kTransitionInfoKey)); + bool isAsyncParam(MapEntry param) => + asyncParams.containsKey(param.key) && param.value is String; + bool get hasFutures => state.allParams.entries.any(isAsyncParam); + Future completeFutures() => Future.wait( + state.allParams.entries.where(isAsyncParam).map( + (param) async { + final doc = await asyncParams[param.key]!(param.value) + .onError((_, __) => null); + if (doc != null) { + futureParamValues[param.key] = doc; + return true; + } + return false; + }, + ), + ).onError((_, __) => [false]).then((v) => v.every((e) => e)); + + dynamic getParam( + String paramName, + ParamType type, [ + bool isList = false, + List? collectionNamePath, + ]) { + if (futureParamValues.containsKey(paramName)) { + return futureParamValues[paramName]; + } + if (!state.allParams.containsKey(paramName)) { + return null; + } + final param = state.allParams[paramName]; + // Got parameter from `extras`, so just directly return it. + if (param is! String) { + return param; + } + // Return serialized value. + return deserializeParam(param, type, isList, + collectionNamePath: collectionNamePath); + } +} + +class FFRoute { + const FFRoute({ + required this.name, + required this.path, + required this.builder, + this.requireAuth = false, + this.asyncParams = const {}, + this.routes = const [], + }); + + final String name; + final String path; + final bool requireAuth; + final Map Function(String)> asyncParams; + final Widget Function(BuildContext, FFParameters) builder; + final List routes; + + GoRoute toRoute(AppStateNotifier appStateNotifier) => GoRoute( + name: name, + path: path, + redirect: (context, state) { + if (appStateNotifier.shouldRedirect) { + final redirectLocation = appStateNotifier.getRedirectLocation(); + appStateNotifier.clearRedirectLocation(); + return redirectLocation; + } + + if (requireAuth && !appStateNotifier.loggedIn) { + appStateNotifier.setRedirectLocationIfUnset(state.location); + return '/login'; + } + return null; + }, + pageBuilder: (context, state) { + final ffParams = FFParameters(state, asyncParams); + final page = ffParams.hasFutures + ? FutureBuilder( + future: ffParams.completeFutures(), + builder: (context, _) => builder(context, ffParams), + ) + : builder(context, ffParams); + final child = appStateNotifier.loading + ? Container( + color: Color(0xD5000000), + child: Image.asset( + 'assets/images/splash.gif', + fit: BoxFit.fill, + ), + ) + : page; + + final transitionInfo = state.transitionInfo; + return transitionInfo.hasTransition + ? CustomTransitionPage( + key: state.pageKey, + child: child, + transitionDuration: transitionInfo.duration, + transitionsBuilder: PageTransition( + type: transitionInfo.transitionType, + duration: transitionInfo.duration, + reverseDuration: transitionInfo.duration, + alignment: transitionInfo.alignment, + child: child, + ).transitionsBuilder, + ) + : MaterialPage(key: state.pageKey, child: child); + }, + routes: routes, + ); +} + +class TransitionInfo { + const TransitionInfo({ + required this.hasTransition, + this.transitionType = PageTransitionType.fade, + this.duration = const Duration(milliseconds: 300), + this.alignment, + }); + + final bool hasTransition; + final PageTransitionType transitionType; + final Duration duration; + final Alignment? alignment; + + static TransitionInfo appDefault() => TransitionInfo(hasTransition: false); +} diff --git a/lib/flutter_flow/nav/serialization_util.dart b/lib/flutter_flow/nav/serialization_util.dart new file mode 100644 index 0000000..e236ee1 --- /dev/null +++ b/lib/flutter_flow/nav/serialization_util.dart @@ -0,0 +1,271 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:from_css_color/from_css_color.dart'; + +import '/backend/backend.dart'; + +import '../../flutter_flow/lat_lng.dart'; +import '../../flutter_flow/place.dart'; +import '../../flutter_flow/uploaded_file.dart'; + +/// SERIALIZATION HELPERS + +String dateTimeRangeToString(DateTimeRange dateTimeRange) { + final startStr = dateTimeRange.start.millisecondsSinceEpoch.toString(); + final endStr = dateTimeRange.end.millisecondsSinceEpoch.toString(); + return '$startStr|$endStr'; +} + +String placeToString(FFPlace place) => jsonEncode({ + 'latLng': place.latLng.serialize(), + 'name': place.name, + 'address': place.address, + 'city': place.city, + 'state': place.state, + 'country': place.country, + 'zipCode': place.zipCode, + }); + +String uploadedFileToString(FFUploadedFile uploadedFile) => + uploadedFile.serialize(); + +const _kDocIdDelimeter = '|'; +String _serializeDocumentReference(DocumentReference ref) { + final docIds = []; + DocumentReference? currentRef = ref; + while (currentRef != null) { + docIds.add(currentRef.id); + // Get the parent document (catching any errors that arise). + currentRef = safeGet(() => currentRef?.parent.parent); + } + // Reverse the list to get the correct ordering. + return docIds.reversed.join(_kDocIdDelimeter); +} + +String? serializeParam( + dynamic param, + ParamType paramType, [ + bool isList = false, +]) { + try { + if (param == null) { + return null; + } + if (isList) { + final serializedValues = (param as Iterable) + .map((p) => serializeParam(p, paramType, false)) + .where((p) => p != null) + .map((p) => p!) + .toList(); + return json.encode(serializedValues); + } + switch (paramType) { + case ParamType.int: + return param.toString(); + case ParamType.double: + return param.toString(); + case ParamType.String: + return param; + case ParamType.bool: + return param ? 'true' : 'false'; + case ParamType.DateTime: + return (param as DateTime).millisecondsSinceEpoch.toString(); + case ParamType.DateTimeRange: + return dateTimeRangeToString(param as DateTimeRange); + case ParamType.LatLng: + return (param as LatLng).serialize(); + case ParamType.Color: + return (param as Color).toCssString(); + case ParamType.FFPlace: + return placeToString(param as FFPlace); + case ParamType.FFUploadedFile: + return uploadedFileToString(param as FFUploadedFile); + case ParamType.JSON: + return json.encode(param); + case ParamType.DocumentReference: + return _serializeDocumentReference(param as DocumentReference); + case ParamType.Document: + final reference = (param as FirestoreRecord).reference; + return _serializeDocumentReference(reference); + + default: + return null; + } + } catch (e) { + print('Error serializing parameter: $e'); + return null; + } +} + +/// END SERIALIZATION HELPERS + +/// DESERIALIZATION HELPERS + +DateTimeRange? dateTimeRangeFromString(String dateTimeRangeStr) { + final pieces = dateTimeRangeStr.split('|'); + if (pieces.length != 2) { + return null; + } + return DateTimeRange( + start: DateTime.fromMillisecondsSinceEpoch(int.parse(pieces.first)), + end: DateTime.fromMillisecondsSinceEpoch(int.parse(pieces.last)), + ); +} + +LatLng? latLngFromString(String latLngStr) { + final pieces = latLngStr.split(','); + if (pieces.length != 2) { + return null; + } + return LatLng( + double.parse(pieces.first.trim()), + double.parse(pieces.last.trim()), + ); +} + +FFPlace placeFromString(String placeStr) { + final serializedData = jsonDecode(placeStr) as Map; + final data = { + 'latLng': serializedData.containsKey('latLng') + ? latLngFromString(serializedData['latLng'] as String) + : const LatLng(0.0, 0.0), + 'name': serializedData['name'] ?? '', + 'address': serializedData['address'] ?? '', + 'city': serializedData['city'] ?? '', + 'state': serializedData['state'] ?? '', + 'country': serializedData['country'] ?? '', + 'zipCode': serializedData['zipCode'] ?? '', + }; + return FFPlace( + latLng: data['latLng'] as LatLng, + name: data['name'] as String, + address: data['address'] as String, + city: data['city'] as String, + state: data['state'] as String, + country: data['country'] as String, + zipCode: data['zipCode'] as String, + ); +} + +FFUploadedFile uploadedFileFromString(String uploadedFileStr) => + FFUploadedFile.deserialize(uploadedFileStr); + +DocumentReference _deserializeDocumentReference( + String refStr, + List collectionNamePath, +) { + var path = ''; + final docIds = refStr.split(_kDocIdDelimeter); + for (int i = 0; i < docIds.length && i < collectionNamePath.length; i++) { + path += '/${collectionNamePath[i]}/${docIds[i]}'; + } + return FirebaseFirestore.instance.doc(path); +} + +enum ParamType { + int, + double, + String, + bool, + DateTime, + DateTimeRange, + LatLng, + Color, + FFPlace, + FFUploadedFile, + JSON, + Document, + DocumentReference, +} + +dynamic deserializeParam( + String? param, + ParamType paramType, + bool isList, { + List? collectionNamePath, +}) { + try { + if (param == null) { + return null; + } + if (isList) { + final paramValues = json.decode(param); + if (paramValues is! Iterable || paramValues.isEmpty) { + return null; + } + return paramValues + .where((p) => p is String) + .map((p) => p as String) + .map((p) => deserializeParam(p, paramType, false, + collectionNamePath: collectionNamePath)) + .where((p) => p != null) + .map((p) => p! as T) + .toList(); + } + switch (paramType) { + case ParamType.int: + return int.tryParse(param); + case ParamType.double: + return double.tryParse(param); + case ParamType.String: + return param; + case ParamType.bool: + return param == 'true'; + case ParamType.DateTime: + final milliseconds = int.tryParse(param); + return milliseconds != null + ? DateTime.fromMillisecondsSinceEpoch(milliseconds) + : null; + case ParamType.DateTimeRange: + return dateTimeRangeFromString(param); + case ParamType.LatLng: + return latLngFromString(param); + case ParamType.Color: + return fromCssColor(param); + case ParamType.FFPlace: + return placeFromString(param); + case ParamType.FFUploadedFile: + return uploadedFileFromString(param); + case ParamType.JSON: + return json.decode(param); + case ParamType.DocumentReference: + return _deserializeDocumentReference(param, collectionNamePath ?? []); + + default: + return null; + } + } catch (e) { + print('Error deserializing parameter: $e'); + return null; + } +} + +Future Function(String) getDoc( + List collectionNamePath, + RecordBuilder recordBuilder, +) { + return (String ids) => _deserializeDocumentReference(ids, collectionNamePath) + .get() + .then((s) => recordBuilder(s)); +} + +Future> Function(String) getDocList( + List collectionNamePath, + RecordBuilder recordBuilder, +) { + return (String idsList) { + List docIds = []; + try { + final ids = json.decode(idsList) as Iterable; + docIds = ids.where((d) => d is String).map((d) => d as String).toList(); + } catch (_) {} + return Future.wait( + docIds.map( + (ids) => _deserializeDocumentReference(ids, collectionNamePath) + .get() + .then((s) => recordBuilder(s)), + ), + ).then((docs) => docs.where((d) => d != null).map((d) => d!).toList()); + }; +} diff --git a/lib/flutter_flow/place.dart b/lib/flutter_flow/place.dart new file mode 100644 index 0000000..e3589b3 --- /dev/null +++ b/lib/flutter_flow/place.dart @@ -0,0 +1,46 @@ +import 'lat_lng.dart'; + +class FFPlace { + const FFPlace({ + this.latLng = const LatLng(0.0, 0.0), + this.name = '', + this.address = '', + this.city = '', + this.state = '', + this.country = '', + this.zipCode = '', + }); + + final LatLng latLng; + final String name; + final String address; + final String city; + final String state; + final String country; + final String zipCode; + + @override + String toString() => '''FFPlace( + latLng: $latLng, + name: $name, + address: $address, + city: $city, + state: $state, + country: $country, + zipCode: $zipCode, + )'''; + + @override + int get hashCode => latLng.hashCode; + + @override + bool operator ==(other) => + other is FFPlace && + latLng == other.latLng && + name == other.name && + address == other.address && + city == other.city && + state == other.state && + country == other.country && + zipCode == other.zipCode; +} diff --git a/lib/flutter_flow/uploaded_file.dart b/lib/flutter_flow/uploaded_file.dart new file mode 100644 index 0000000..2dfd2ff --- /dev/null +++ b/lib/flutter_flow/uploaded_file.dart @@ -0,0 +1,68 @@ +import 'dart:convert'; +import 'dart:typed_data' show Uint8List; + +class FFUploadedFile { + const FFUploadedFile({ + this.name, + this.bytes, + this.height, + this.width, + this.blurHash, + }); + + final String? name; + final Uint8List? bytes; + final double? height; + final double? width; + final String? blurHash; + + @override + String toString() => + 'FFUploadedFile(name: $name, bytes: ${bytes?.length ?? 0}, height: $height, width: $width, blurHash: $blurHash,)'; + + String serialize() => jsonEncode( + { + 'name': name, + 'bytes': bytes, + 'height': height, + 'width': width, + 'blurHash': blurHash, + }, + ); + + static FFUploadedFile deserialize(String val) { + final serializedData = jsonDecode(val) as Map; + final data = { + 'name': serializedData['name'] ?? '', + 'bytes': serializedData['bytes'] ?? Uint8List.fromList([]), + 'height': serializedData['height'], + 'width': serializedData['width'], + 'blurHash': serializedData['blurHash'], + }; + return FFUploadedFile( + name: data['name'] as String, + bytes: Uint8List.fromList(data['bytes'].cast().toList()), + height: data['height'] as double?, + width: data['width'] as double?, + blurHash: data['blurHash'] as String?, + ); + } + + @override + int get hashCode => Object.hash( + name, + bytes, + height, + width, + blurHash, + ); + + @override + bool operator ==(other) => + other is FFUploadedFile && + name == other.name && + bytes == other.bytes && + height == other.height && + width == other.width && + blurHash == other.blurHash; +} diff --git a/lib/index.dart b/lib/index.dart new file mode 100644 index 0000000..bbaf563 --- /dev/null +++ b/lib/index.dart @@ -0,0 +1,8 @@ +// Export pages +export '/pages/login/login_widget.dart' show LoginWidget; +export '/pages/signup/signup_widget.dart' show SignupWidget; +export '/pages/categories/categories_widget.dart' show CategoriesWidget; +export '/pages/task/task_widget.dart' show TaskWidget; +export '/pages/profile/profile_widget.dart' show ProfileWidget; +export '/pages/leaderboard/leaderboard_widget.dart' show LeaderboardWidget; +export '/pages/homepage/homepage_widget.dart' show HomepageWidget; diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..16782e2 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,202 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'auth/firebase_auth/firebase_user_provider.dart'; +import 'auth/firebase_auth/auth_util.dart'; + +import 'backend/firebase/firebase_config.dart'; +import 'flutter_flow/flutter_flow_theme.dart'; +import 'flutter_flow/flutter_flow_util.dart'; +import 'flutter_flow/internationalization.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'flutter_flow/nav/nav.dart'; +import 'index.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + usePathUrlStrategy(); + await initFirebase(); + + await FlutterFlowTheme.initialize(); + + final appState = FFAppState(); // Initialize FFAppState + await appState.initializePersistedState(); + + runApp(ChangeNotifierProvider( + create: (context) => appState, + child: MyApp(), + )); +} + +class MyApp extends StatefulWidget { + // This widget is the root of your application. + @override + State createState() => _MyAppState(); + + static _MyAppState of(BuildContext context) => + context.findAncestorStateOfType<_MyAppState>()!; +} + +class _MyAppState extends State { + Locale? _locale; + ThemeMode _themeMode = FlutterFlowTheme.themeMode; + + late Stream userStream; + + late AppStateNotifier _appStateNotifier; + late GoRouter _router; + + final authUserSub = authenticatedUserStream.listen((_) {}); + + @override + void initState() { + super.initState(); + + _appStateNotifier = AppStateNotifier.instance; + _router = createRouter(_appStateNotifier); + userStream = bharatTrackerFirebaseUserStream() + ..listen((user) => _appStateNotifier.update(user)); + jwtTokenStream.listen((_) {}); + Future.delayed( + Duration(milliseconds: 1000), + () => _appStateNotifier.stopShowingSplashImage(), + ); + } + + @override + void dispose() { + authUserSub.cancel(); + + super.dispose(); + } + + void setLocale(String language) { + setState(() => _locale = createLocale(language)); + } + + void setThemeMode(ThemeMode mode) => setState(() { + _themeMode = mode; + FlutterFlowTheme.saveThemeMode(mode); + }); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'BHARAT TRACKER', + localizationsDelegates: [ + FFLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + locale: _locale, + supportedLocales: const [Locale('en', '')], + theme: ThemeData( + brightness: Brightness.light, + scrollbarTheme: ScrollbarThemeData(), + ), + darkTheme: ThemeData( + brightness: Brightness.dark, + scrollbarTheme: ScrollbarThemeData(), + ), + themeMode: _themeMode, + routerConfig: _router, + ); + } +} + +class NavBarPage extends StatefulWidget { + NavBarPage({Key? key, this.initialPage, this.page}) : super(key: key); + + final String? initialPage; + final Widget? page; + + @override + _NavBarPageState createState() => _NavBarPageState(); +} + +/// This is the private State class that goes with NavBarPage. +class _NavBarPageState extends State { + String _currentPageName = 'categories'; + late Widget? _currentPage; + + @override + void initState() { + super.initState(); + _currentPageName = widget.initialPage ?? _currentPageName; + _currentPage = widget.page; + } + + @override + Widget build(BuildContext context) { + final tabs = { + 'homepage': HomepageWidget(), + 'categories': CategoriesWidget(), + 'task': TaskWidget(), + 'leaderboard': LeaderboardWidget(), + 'profile': ProfileWidget(), + }; + final currentIndex = tabs.keys.toList().indexOf(_currentPageName); + + return Scaffold( + body: _currentPage ?? tabs[_currentPageName], + bottomNavigationBar: BottomNavigationBar( + currentIndex: currentIndex, + onTap: (i) => setState(() { + _currentPage = null; + _currentPageName = tabs.keys.toList()[i]; + }), + backgroundColor: Color(0xD5000000), + selectedItemColor: Color(0xFFFFD700), + unselectedItemColor: FlutterFlowTheme.of(context).info, + showSelectedLabels: false, + showUnselectedLabels: false, + type: BottomNavigationBarType.fixed, + items: [ + BottomNavigationBarItem( + icon: Icon( + Icons.home_outlined, + ), + label: '', + tooltip: '', + ), + BottomNavigationBarItem( + icon: Icon( + Icons.category_outlined, + size: 24.0, + ), + label: 'Home', + tooltip: '', + ), + BottomNavigationBarItem( + icon: Icon( + Icons.ballot_sharp, + size: 24.0, + ), + label: 'TASKS', + tooltip: '', + ), + BottomNavigationBarItem( + icon: Icon( + Icons.leaderboard_outlined, + ), + label: '', + tooltip: '', + ), + BottomNavigationBarItem( + icon: Icon( + Icons.person, + size: 24.0, + ), + label: 'PROFILE', + tooltip: '', + ) + ], + ), + ); + } +} diff --git a/lib/pages/categories/categories_model.dart b/lib/pages/categories/categories_model.dart new file mode 100644 index 0000000..3c5c1f1 --- /dev/null +++ b/lib/pages/categories/categories_model.dart @@ -0,0 +1,36 @@ +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart' + as smooth_page_indicator; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; + +class CategoriesModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + // State field(s) for PageView widget. + PageController? pageViewController; + + int get pageViewCurrentIndex => pageViewController != null && + pageViewController!.hasClients && + pageViewController!.page != null + ? pageViewController!.page!.round() + : 0; + + /// Initialization and disposal methods. + + void initState(BuildContext context) {} + + void dispose() { + unfocusNode.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/categories/categories_widget.dart b/lib/pages/categories/categories_widget.dart new file mode 100644 index 0000000..c36fe2e --- /dev/null +++ b/lib/pages/categories/categories_widget.dart @@ -0,0 +1,521 @@ +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart' + as smooth_page_indicator; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; +import 'categories_model.dart'; +export 'categories_model.dart'; + +class CategoriesWidget extends StatefulWidget { + const CategoriesWidget({Key? key}) : super(key: key); + + @override + _CategoriesWidgetState createState() => _CategoriesWidgetState(); +} + +class _CategoriesWidgetState extends State { + late CategoriesModel _model; + + final scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _model = createModel(context, () => CategoriesModel()); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: FlutterFlowTheme.of(context).primaryText, + appBar: AppBar( + backgroundColor: Color(0x80000000), + automaticallyImplyLeading: false, + title: Padding( + padding: EdgeInsetsDirectional.fromSTEB(10.0, 0.0, 0.0, 0.0), + child: GradientText( + 'BE A PRO PLANET', + style: FlutterFlowTheme.of(context).headlineMedium.override( + fontFamily: 'Outfit', + color: Color(0xFFFFDD77), + fontSize: 22.0, + ), + colors: [ + FlutterFlowTheme.of(context).secondary, + Color(0xFFFFDD77) + ], + gradientDirection: GradientDirection.ltr, + gradientType: GradientType.linear, + ), + ), + actions: [], + centerTitle: false, + elevation: 2.0, + ), + body: SafeArea( + top: true, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(15.0, 15.0, 15.0, 15.0), + child: Container( + width: 391.56, + height: 368.0, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context).primaryBackground, + ), + child: Container( + width: double.infinity, + height: 583.0, + child: Stack( + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 40.0), + child: PageView( + controller: _model.pageViewController ??= + PageController(initialPage: 0), + scrollDirection: Axis.horizontal, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + 'https://www.invajy.com/wp-content/uploads/2019/05/Buddha-Quotes-3.jpg', + width: 300.0, + height: 232.0, + fit: BoxFit.cover, + ), + ), + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + 'https://www.careelite.de/wp-content/uploads/2022/08/animal-protection-quote-education-humanity-schweitzer.jpg', + width: 300.0, + height: 200.0, + fit: BoxFit.cover, + ), + ), + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image.network( + 'https://www.quoteslines.com/wp-content/uploads/motivational-sayings-about-air-pollution.jpg', + width: 300.0, + height: 203.0, + fit: BoxFit.cover, + ), + ), + ], + ), + ), + Align( + alignment: AlignmentDirectional(-1.00, 1.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 0.0, 16.0), + child: smooth_page_indicator.SmoothPageIndicator( + controller: _model.pageViewController ??= + PageController(initialPage: 0), + count: 3, + axisDirection: Axis.horizontal, + onDotClicked: (i) async { + await _model.pageViewController! + .animateToPage( + i, + duration: Duration(milliseconds: 500), + curve: Curves.ease, + ); + }, + effect: + smooth_page_indicator.ExpandingDotsEffect( + expansionFactor: 3.0, + spacing: 8.0, + radius: 16.0, + dotWidth: 16.0, + dotHeight: 8.0, + dotColor: + FlutterFlowTheme.of(context).primaryText, + activeDotColor: + FlutterFlowTheme.of(context).primary, + paintStyle: PaintingStyle.fill, + ), + ), + ), + ), + ], + ), + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 0.0, 0.0), + child: Container( + width: 167.0, + height: 100.0, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 20.0, 0.0, 0.0), + child: Icon( + Icons.water_drop_outlined, + color: FlutterFlowTheme.of(context).primaryText, + size: 42.0, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 10.0, 0.0, 0.0), + child: RichText( + textScaleFactor: + MediaQuery.of(context).textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Water', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: FlutterFlowTheme.of(context) + .primary, + fontWeight: FontWeight.bold, + ), + ) + ], + style: + FlutterFlowTheme.of(context).bodyMedium, + ), + ), + ), + ], + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 0.0, 0.0), + child: Container( + width: 167.0, + height: 100.0, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 20.0, 0.0, 0.0), + child: Icon( + Icons.air, + color: FlutterFlowTheme.of(context).primaryText, + size: 42.0, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 10.0, 10.0, 0.0), + child: RichText( + textScaleFactor: + MediaQuery.of(context).textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Air', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: FlutterFlowTheme.of(context) + .primary, + fontWeight: FontWeight.bold, + ), + ) + ], + style: + FlutterFlowTheme.of(context).bodyMedium, + ), + ), + ), + ], + ), + ), + ), + ], + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.0, 10.0, 0.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 0.0, 0.0), + child: Container( + width: 167.0, + height: 100.0, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 20.0, 0.0, 0.0), + child: Icon( + Icons.recycling, + color: + FlutterFlowTheme.of(context).primaryText, + size: 42.0, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 10.0, 0.0, 0.0), + child: RichText( + textScaleFactor: + MediaQuery.of(context).textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Recycle', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: + FlutterFlowTheme.of(context) + .primary, + fontWeight: FontWeight.bold, + ), + ) + ], + style: + FlutterFlowTheme.of(context).bodyMedium, + ), + ), + ), + ], + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 0.0, 0.0), + child: Container( + width: 167.0, + height: 100.0, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 20.0, 0.0, 0.0), + child: Icon( + Icons.delete_outlined, + color: + FlutterFlowTheme.of(context).primaryText, + size: 42.0, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 10.0, 0.0, 0.0), + child: RichText( + textScaleFactor: + MediaQuery.of(context).textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Waste Management', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: + FlutterFlowTheme.of(context) + .primary, + fontWeight: FontWeight.bold, + ), + ) + ], + style: + FlutterFlowTheme.of(context).bodyMedium, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.0, 10.0, 0.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 110.0, 0.0, 0.0, 0.0), + child: Container( + width: 167.0, + height: 100.0, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 20.0, 0.0, 0.0), + child: Icon( + Icons.pets_outlined, + color: + FlutterFlowTheme.of(context).primaryText, + size: 42.0, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 10.0, 0.0, 0.0), + child: RichText( + textScaleFactor: + MediaQuery.of(context).textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Animal Care', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: + FlutterFlowTheme.of(context) + .primary, + fontWeight: FontWeight.bold, + ), + ) + ], + style: + FlutterFlowTheme.of(context).bodyMedium, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/homepage/homepage_model.dart b/lib/pages/homepage/homepage_model.dart new file mode 100644 index 0000000..bdf8bbd --- /dev/null +++ b/lib/pages/homepage/homepage_model.dart @@ -0,0 +1,26 @@ +import '/flutter_flow/flutter_flow_icon_button.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; + +class HomepageModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + + /// Initialization and disposal methods. + + void initState(BuildContext context) {} + + void dispose() { + unfocusNode.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/homepage/homepage_widget.dart b/lib/pages/homepage/homepage_widget.dart new file mode 100644 index 0000000..39aca75 --- /dev/null +++ b/lib/pages/homepage/homepage_widget.dart @@ -0,0 +1,682 @@ +import '/flutter_flow/flutter_flow_icon_button.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; +import 'homepage_model.dart'; +export 'homepage_model.dart'; + +class HomepageWidget extends StatefulWidget { + const HomepageWidget({Key? key}) : super(key: key); + + @override + _HomepageWidgetState createState() => _HomepageWidgetState(); +} + +class _HomepageWidgetState extends State { + late HomepageModel _model; + + final scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _model = createModel(context, () => HomepageModel()); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: FlutterFlowTheme.of(context).primaryBackground, + appBar: AppBar( + backgroundColor: Color(0xDF000000), + automaticallyImplyLeading: false, + title: GradientText( + 'PRO PLANET', + style: FlutterFlowTheme.of(context).headlineMedium.override( + fontFamily: 'Outfit', + color: FlutterFlowTheme.of(context).primaryBackground, + fontSize: 22.0, + ), + colors: [FlutterFlowTheme.of(context).secondary, Color(0xFFFFD700)], + gradientDirection: GradientDirection.ltr, + gradientType: GradientType.linear, + ), + actions: [], + centerTitle: false, + elevation: 0.0, + ), + body: SafeArea( + top: true, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + AppBar( + backgroundColor: + FlutterFlowTheme.of(context).primaryBackground, + automaticallyImplyLeading: false, + title: Text( + 'Social Media', + style: FlutterFlowTheme.of(context).headlineMedium.override( + fontFamily: 'Outfit', + color: FlutterFlowTheme.of(context).primaryText, + fontSize: 22.0, + ), + ), + actions: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 24.0, 0.0), + child: FlutterFlowIconButton( + borderRadius: 30.0, + buttonSize: 60.0, + icon: Icon( + Icons.add, + color: FlutterFlowTheme.of(context).secondaryText, + size: 24.0, + ), + onPressed: () { + print('IconButton pressed ...'); + }, + ), + ), + ], + centerTitle: false, + elevation: 0.0, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 12.0, 16.0, 0.0), + child: Material( + color: Colors.transparent, + elevation: 1.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 12.0, 12.0, 12.0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + 'Welcome,', + style: + FlutterFlowTheme.of(context).headlineMedium, + ), + Text( + 'Explore and connect with friends', + style: FlutterFlowTheme.of(context) + .titleMedium + .override( + fontFamily: 'Readex Pro', + color: FlutterFlowTheme.of(context) + .secondaryText, + ), + ), + Divider( + thickness: 2.0, + color: FlutterFlowTheme.of(context).alternate, + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Friends', + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + Text( + '500', + style: FlutterFlowTheme.of(context) + .displaySmall + .override( + fontFamily: 'Outfit', + color: + FlutterFlowTheme.of(context) + .primaryText, + fontWeight: FontWeight.w300, + ), + ), + ], + ), + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Followers', + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + Text( + '1.2k', + style: FlutterFlowTheme.of(context) + .displaySmall + .override( + fontFamily: 'Outfit', + color: + FlutterFlowTheme.of(context) + .primaryText, + fontWeight: FontWeight.w300, + ), + ), + ], + ), + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Posts', + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + Text( + '250', + style: FlutterFlowTheme.of(context) + .displaySmall + .override( + fontFamily: 'Outfit', + color: + FlutterFlowTheme.of(context) + .primaryText, + fontWeight: FontWeight.w300, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 12.0, 0.0, 16.0), + child: Text( + 'Recent Posts', + style: FlutterFlowTheme.of(context).labelMedium, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.0, 16.0, 0.0, 0.0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 12.0, 16.0, 0.0), + child: Material( + color: Colors.transparent, + elevation: 1.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Container( + width: double.infinity, + height: 120.0, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context) + .secondaryBackground, + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 8.0, 8.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 1.0, 1.0, 1.0), + child: ClipRRect( + borderRadius: + BorderRadius.circular(8.0), + child: Image.network( + 'https://images.unsplash.com/photo-1516464731083-b50c0b838068?w=1280&h=720', + width: 70.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 0.0, 4.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Post Title', + style: + FlutterFlowTheme.of(context) + .bodyLarge, + ), + Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0.0, 4.0, 8.0, 0.0), + child: Text( + 'A description of the post', + textAlign: TextAlign.start, + style: + FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ), + ), + Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 0.0), + child: Icon( + Icons.favorite_border, + color: FlutterFlowTheme.of(context) + .secondaryText, + size: 24.0, + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 4.0, 8.0), + child: Text( + '2m', + textAlign: TextAlign.end, + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 12.0, 16.0, 0.0), + child: Material( + color: Colors.transparent, + elevation: 1.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Container( + width: double.infinity, + height: 120.0, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context) + .secondaryBackground, + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 8.0, 8.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 1.0, 1.0, 1.0), + child: ClipRRect( + borderRadius: + BorderRadius.circular(8.0), + child: Image.network( + 'https://images.unsplash.com/photo-1532749659856-5f1803b3365b?w=1280&h=720', + width: 70.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 0.0, 4.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Post Title', + style: + FlutterFlowTheme.of(context) + .bodyLarge, + ), + Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0.0, 4.0, 8.0, 0.0), + child: Text( + 'A description of the post', + textAlign: TextAlign.start, + style: + FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ), + ), + Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 0.0), + child: Icon( + Icons.favorite_border, + color: FlutterFlowTheme.of(context) + .secondaryText, + size: 24.0, + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 4.0, 8.0), + child: Text( + '2m', + textAlign: TextAlign.end, + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 12.0, 16.0, 0.0), + child: Material( + color: Colors.transparent, + elevation: 1.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Container( + width: double.infinity, + height: 120.0, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context) + .secondaryBackground, + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 8.0, 8.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 1.0, 1.0, 1.0), + child: ClipRRect( + borderRadius: + BorderRadius.circular(8.0), + child: Image.network( + 'https://images.unsplash.com/photo-1486276569340-2b1386164ac9?w=1280&h=720', + width: 70.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 0.0, 4.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Post Title', + style: + FlutterFlowTheme.of(context) + .bodyLarge, + ), + Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0.0, 4.0, 8.0, 0.0), + child: Text( + 'A description of the post', + textAlign: TextAlign.start, + style: + FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ), + ), + Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 0.0), + child: Icon( + Icons.favorite_border, + color: FlutterFlowTheme.of(context) + .secondaryText, + size: 24.0, + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 4.0, 8.0), + child: Text( + '2m', + textAlign: TextAlign.end, + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 12.0, 16.0, 0.0), + child: Material( + color: Colors.transparent, + elevation: 1.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Container( + width: double.infinity, + height: 120.0, + decoration: BoxDecoration( + color: FlutterFlowTheme.of(context) + .secondaryBackground, + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 8.0, 8.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 1.0, 1.0, 1.0), + child: ClipRRect( + borderRadius: + BorderRadius.circular(8.0), + child: Image.network( + 'https://images.unsplash.com/photo-1501504905252-473c47e087f8?w=1280&h=720', + width: 70.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 8.0, 0.0, 4.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Post Title', + style: + FlutterFlowTheme.of(context) + .bodyLarge, + ), + Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0.0, 4.0, 8.0, 0.0), + child: Text( + 'A description of the post', + textAlign: TextAlign.start, + style: + FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ), + ), + Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 0.0), + child: Icon( + Icons.favorite_border, + color: FlutterFlowTheme.of(context) + .secondaryText, + size: 24.0, + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 4.0, 8.0), + child: Text( + '2m', + textAlign: TextAlign.end, + style: FlutterFlowTheme.of(context) + .labelMedium, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/leaderboard/leaderboard_model.dart b/lib/pages/leaderboard/leaderboard_model.dart new file mode 100644 index 0000000..19ebba1 --- /dev/null +++ b/lib/pages/leaderboard/leaderboard_model.dart @@ -0,0 +1,25 @@ +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; + +class LeaderboardModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + + /// Initialization and disposal methods. + + void initState(BuildContext context) {} + + void dispose() { + unfocusNode.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/leaderboard/leaderboard_widget.dart b/lib/pages/leaderboard/leaderboard_widget.dart new file mode 100644 index 0000000..bdcecd0 --- /dev/null +++ b/lib/pages/leaderboard/leaderboard_widget.dart @@ -0,0 +1,309 @@ +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; +import 'leaderboard_model.dart'; +export 'leaderboard_model.dart'; + +class LeaderboardWidget extends StatefulWidget { + const LeaderboardWidget({Key? key}) : super(key: key); + + @override + _LeaderboardWidgetState createState() => _LeaderboardWidgetState(); +} + +class _LeaderboardWidgetState extends State { + late LeaderboardModel _model; + + final scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _model = createModel(context, () => LeaderboardModel()); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: FlutterFlowTheme.of(context).primaryBackground, + appBar: AppBar( + backgroundColor: FlutterFlowTheme.of(context).primaryText, + automaticallyImplyLeading: false, + title: GradientText( + 'LEADERBOARD', + style: FlutterFlowTheme.of(context).headlineMedium.override( + fontFamily: 'Outfit', + color: Colors.white, + fontSize: 22.0, + ), + colors: [FlutterFlowTheme.of(context).secondary, Color(0xFFFFD700)], + gradientDirection: GradientDirection.ltr, + gradientType: GradientType.linear, + ), + actions: [], + centerTitle: false, + elevation: 2.0, + ), + body: SafeArea( + top: true, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 21.0, 16.0, 0.0), + child: Container( + width: double.infinity, + height: 60.0, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + borderRadius: BorderRadius.circular(12.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 12.0, 12.0, 12.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + Icons.location_city_rounded, + color: FlutterFlowTheme.of(context).secondaryText, + size: 24.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Zonal Rank', + style: FlutterFlowTheme.of(context).labelMedium, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 10.0, 0.0, 0.0, 0.0), + child: Text( + '1st', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 21.0, 16.0, 0.0), + child: Container( + width: double.infinity, + height: 60.0, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + borderRadius: BorderRadius.circular(12.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 12.0, 12.0, 12.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + Icons.location_on_rounded, + color: FlutterFlowTheme.of(context).secondaryText, + size: 24.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'State Rank', + style: FlutterFlowTheme.of(context).labelMedium, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 10.0, 0.0, 0.0, 0.0), + child: Text( + '3rd', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 21.0, 16.0, 0.0), + child: Container( + width: double.infinity, + height: 60.0, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + borderRadius: BorderRadius.circular(12.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 12.0, 12.0, 12.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + Icons.public_rounded, + color: FlutterFlowTheme.of(context).secondaryText, + size: 24.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Country Rank', + style: FlutterFlowTheme.of(context).labelMedium, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 10.0, 0.0, 0.0, 0.0), + child: Text( + '15th', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 21.0, 16.0, 0.0), + child: Container( + width: double.infinity, + height: 60.0, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + FlutterFlowTheme.of(context).accent4, + FlutterFlowTheme.of(context).secondary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + borderRadius: BorderRadius.circular(12.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 12.0, 12.0, 12.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + Icons.person_rounded, + color: FlutterFlowTheme.of(context).secondaryText, + size: 24.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Overall Rank', + style: FlutterFlowTheme.of(context).labelMedium, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 10.0, 0.0, 0.0, 0.0), + child: Text( + '42nd', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/login/login_model.dart b/lib/pages/login/login_model.dart new file mode 100644 index 0000000..75d456d --- /dev/null +++ b/lib/pages/login/login_model.dart @@ -0,0 +1,41 @@ +import '/auth/firebase_auth/auth_util.dart'; +import '/flutter_flow/flutter_flow_animations.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; + +class LoginModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + // State field(s) for emailAddress widget. + TextEditingController? emailAddressController; + String? Function(BuildContext, String?)? emailAddressControllerValidator; + // State field(s) for password widget. + TextEditingController? passwordController; + late bool passwordVisibility; + String? Function(BuildContext, String?)? passwordControllerValidator; + + /// Initialization and disposal methods. + + void initState(BuildContext context) { + passwordVisibility = false; + } + + void dispose() { + unfocusNode.dispose(); + emailAddressController?.dispose(); + passwordController?.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/login/login_widget.dart b/lib/pages/login/login_widget.dart new file mode 100644 index 0000000..8cd7115 --- /dev/null +++ b/lib/pages/login/login_widget.dart @@ -0,0 +1,451 @@ +import '/auth/firebase_auth/auth_util.dart'; +import '/flutter_flow/flutter_flow_animations.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; +import 'login_model.dart'; +export 'login_model.dart'; + +class LoginWidget extends StatefulWidget { + const LoginWidget({Key? key}) : super(key: key); + + @override + _LoginWidgetState createState() => _LoginWidgetState(); +} + +class _LoginWidgetState extends State + with TickerProviderStateMixin { + late LoginModel _model; + + final scaffoldKey = GlobalKey(); + + final animationsMap = { + 'containerOnPageLoadAnimation': AnimationInfo( + trigger: AnimationTrigger.onPageLoad, + effects: [ + VisibilityEffect(duration: 1.ms), + FadeEffect( + curve: Curves.easeInOut, + delay: 0.ms, + duration: 300.ms, + begin: 0.0, + end: 1.0, + ), + MoveEffect( + curve: Curves.easeInOut, + delay: 0.ms, + duration: 300.ms, + begin: Offset(0.0, 140.0), + end: Offset(0.0, 0.0), + ), + ScaleEffect( + curve: Curves.easeInOut, + delay: 0.ms, + duration: 300.ms, + begin: Offset(0.9, 0.9), + end: Offset(1.0, 1.0), + ), + TiltEffect( + curve: Curves.easeInOut, + delay: 0.ms, + duration: 300.ms, + begin: Offset(-0.349, 0), + end: Offset(0, 0), + ), + ], + ), + }; + + @override + void initState() { + super.initState(); + _model = createModel(context, () => LoginModel()); + + _model.emailAddressController ??= TextEditingController(); + _model.passwordController ??= TextEditingController(); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: Colors.white, + body: Container( + height: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFF4B39EF), + FlutterFlowTheme.of(context).tertiary + ], + stops: [0.0, 1.0], + begin: AlignmentDirectional(0.0, -1.0), + end: AlignmentDirectional(0, 1.0), + ), + ), + alignment: AlignmentDirectional(0.00, -1.00), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.0, 70.0, 0.0, 32.0), + child: AnimatedContainer( + duration: Duration(milliseconds: 100), + curve: Curves.bounceOut, + width: 200.0, + height: 70.0, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + borderRadius: BorderRadius.circular(16.0), + shape: BoxShape.rectangle, + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: GradientText( + 'R U PRO PLANET ?', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context).titleLarge.override( + fontFamily: 'Outfit', + color: Color(0xFFFFD700), + ), + colors: [ + Color(0xFFFFD700), + FlutterFlowTheme.of(context).secondary + ], + gradientDirection: GradientDirection.ltr, + gradientType: GradientType.linear, + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 16.0, 16.0, 16.0), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + maxWidth: 570.0, + ), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + borderRadius: BorderRadius.circular(12.0), + ), + child: Align( + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 32.0, 32.0, 32.0, 32.0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Welcome Back', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .displaySmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 36.0, + fontWeight: FontWeight.w600, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 24.0), + child: Text( + 'Fill out the information below in order to access your account.', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: Container( + width: double.infinity, + child: TextFormField( + controller: _model.emailAddressController, + autofocus: true, + autofillHints: [AutofillHints.email], + obscureText: false, + decoration: InputDecoration( + labelText: 'Email', + labelStyle: FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFF1F4F8), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF4B39EF), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + filled: true, + fillColor: Color(0xFFF1F4F8), + ), + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + keyboardType: TextInputType.emailAddress, + validator: _model + .emailAddressControllerValidator + .asValidator(context), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: Container( + width: double.infinity, + child: TextFormField( + controller: _model.passwordController, + autofocus: true, + autofillHints: [AutofillHints.password], + obscureText: !_model.passwordVisibility, + decoration: InputDecoration( + labelText: 'Password', + labelStyle: FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFF1F4F8), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF4B39EF), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + filled: true, + fillColor: Color(0xFFF1F4F8), + suffixIcon: InkWell( + onTap: () => setState( + () => _model.passwordVisibility = + !_model.passwordVisibility, + ), + focusNode: FocusNode(skipTraversal: true), + child: Icon( + _model.passwordVisibility + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + ), + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + validator: _model.passwordControllerValidator + .asValidator(context), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: FFButtonWidget( + onPressed: () async { + GoRouter.of(context).prepareAuthEvent(); + + final user = + await authManager.signInWithEmail( + context, + _model.emailAddressController.text, + _model.passwordController.text, + ); + if (user == null) { + return; + } + + context.pushNamedAuth( + 'homepage', context.mounted); + }, + text: 'Sign In', + options: FFButtonOptions( + width: double.infinity, + height: 44.0, + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 0.0), + iconPadding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 0.0), + color: Color(0xFF4B39EF), + textStyle: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + elevation: 3.0, + borderSide: BorderSide( + color: Colors.transparent, + width: 1.0, + ), + borderRadius: BorderRadius.circular(12.0), + ), + ), + ), + + // You will have to add an action on this rich text to go to your login page. + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 12.0), + child: InkWell( + splashColor: Colors.transparent, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: () async { + context.pushNamed('signup'); + }, + child: RichText( + textScaleFactor: + MediaQuery.of(context).textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Don\'t have an account? ', + style: TextStyle(), + ), + TextSpan( + text: 'Sign Up here', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w600, + ), + ) + ], + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ).animateOnPageLoad( + animationsMap['containerOnPageLoadAnimation']!), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/profile/profile_model.dart b/lib/pages/profile/profile_model.dart new file mode 100644 index 0000000..fbed2d4 --- /dev/null +++ b/lib/pages/profile/profile_model.dart @@ -0,0 +1,27 @@ +import '/auth/firebase_auth/auth_util.dart'; +import '/flutter_flow/flutter_flow_icon_button.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; + +class ProfileModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + + /// Initialization and disposal methods. + + void initState(BuildContext context) {} + + void dispose() { + unfocusNode.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/profile/profile_widget.dart b/lib/pages/profile/profile_widget.dart new file mode 100644 index 0000000..6dc0f09 --- /dev/null +++ b/lib/pages/profile/profile_widget.dart @@ -0,0 +1,654 @@ +import '/auth/firebase_auth/auth_util.dart'; +import '/flutter_flow/flutter_flow_icon_button.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'profile_model.dart'; +export 'profile_model.dart'; + +class ProfileWidget extends StatefulWidget { + const ProfileWidget({Key? key}) : super(key: key); + + @override + _ProfileWidgetState createState() => _ProfileWidgetState(); +} + +class _ProfileWidgetState extends State { + late ProfileModel _model; + + final scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _model = createModel(context, () => ProfileModel()); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: Color(0xFF57636C), + appBar: AppBar( + backgroundColor: Color(0xFF57636C), + automaticallyImplyLeading: false, + leading: FlutterFlowIconButton( + borderColor: Colors.transparent, + borderRadius: 30.0, + borderWidth: 1.0, + buttonSize: 60.0, + icon: Icon( + Icons.arrow_back_rounded, + color: Colors.white, + size: 30.0, + ), + onPressed: () async { + context.pop(); + }, + ), + actions: [], + centerTitle: false, + elevation: 0.0, + ), + body: Align( + alignment: AlignmentDirectional(0.00, 0.00), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 140.0, + child: Stack( + children: [ + Align( + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.0, 12.0, 0.0, 0.0), + child: Container( + width: 100.0, + height: 100.0, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 2.0, 2.0, 2.0, 2.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(50.0), + child: Image.network( + 'https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w0NTYyMDF8MHwxfHNlYXJjaHw3fHxhbmltYXRlZCUyMHByb2ZpbGV8ZW58MHx8fHwxNjk1NzE0MDc0fDA&ixlib=rb-4.0.3&q=80&w=1080', + width: 100.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(1.00, 0.00), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.0, 12.0, 0.0, 0.0), + child: ClipOval( + child: Container( + width: 44.0, + height: 44.0, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + border: Border.all( + color: Color(0xFF57636C), + width: 4.0, + ), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 4.0, 4.0, 4.0, 4.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(50.0), + child: Image.asset( + Theme.of(context).brightness == + Brightness.dark + ? 'assets/images/@4xff_badgeDesign_dark_small.png' + : 'assets/images/@4xff_badgeDesign_light_small.png', + width: 100.0, + height: 100.0, + fit: BoxFit.cover, + ), + ), + ), + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.0, 16.0, 0.0, 12.0), + child: Text( + 'Nikhil Kumar', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context).headlineSmall.override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 22.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Text( + 'patnaiknikhil10@gmail.com', + style: FlutterFlowTheme.of(context).titleSmall.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xCCFFFFFF), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(16.0, 24.0, 16.0, 32.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 12.0), + child: Container( + width: 44.0, + height: 44.0, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Icon( + Icons.work_outline, + color: Color(0xFF101213), + size: 24.0, + ), + ), + ), + Text( + 'User Credentials', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Expanded( + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(4.0, 0.0, 4.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 12.0), + child: Container( + width: 44.0, + height: 44.0, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Icon( + Icons.notifications_outlined, + color: Color(0xFF101213), + size: 24.0, + ), + ), + ), + Text( + 'Tracker Notifications', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 12.0), + child: Container( + width: 44.0, + height: 44.0, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Icon( + Icons.help_outline_outlined, + color: Color(0xFF101213), + size: 24.0, + ), + ), + ), + Text( + 'Help Center', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ), + Expanded( + child: Container( + width: double.infinity, + height: 400.0, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + blurRadius: 3.0, + color: Color(0x33000000), + offset: Offset(0.0, -1.0), + ) + ], + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(0.0), + bottomRight: Radius.circular(0.0), + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 16.0, 16.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 12.0), + child: Text( + 'Settings', + style: FlutterFlowTheme.of(context) + .headlineSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 22.0, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 16.0, 8.0), + child: Icon( + Icons.work_outline, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Text( + 'Phone Number', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Text( + 'Add Number', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 16.0, 8.0), + child: Icon( + Icons.language_rounded, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Text( + 'Language', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Text( + 'English (eng)', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 16.0, 8.0), + child: FaIcon( + FontAwesomeIcons.tags, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Text( + 'Pro Planet Tags', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Text( + 'PLATINUM', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 16.0, 8.0), + child: Icon( + Icons.edit, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Text( + 'Profile Settings', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Text( + 'Edit Profile', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 16.0, 8.0), + child: Icon( + Icons.notifications_active, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Text( + 'Notification Settings', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Icon( + Icons.chevron_right_rounded, + color: Color(0xFF57636C), + size: 24.0, + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 16.0, 8.0), + child: Icon( + Icons.login_rounded, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Text( + 'Log out of account', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + InkWell( + splashColor: Colors.transparent, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: () async { + GoRouter.of(context).prepareAuthEvent(); + await authManager.signOut(); + GoRouter.of(context) + .clearRedirectLocation(); + + context.goNamedAuth( + 'login', context.mounted); + }, + child: Text( + 'Log Out?', + textAlign: TextAlign.center, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/signup/signup_model.dart b/lib/pages/signup/signup_model.dart new file mode 100644 index 0000000..a1e7458 --- /dev/null +++ b/lib/pages/signup/signup_model.dart @@ -0,0 +1,44 @@ +import '/auth/firebase_auth/auth_util.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; + +class SignupModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + // State field(s) for emailAddress widget. + TextEditingController? emailAddressController; + String? Function(BuildContext, String?)? emailAddressControllerValidator; + // State field(s) for password widget. + TextEditingController? passwordController; + late bool passwordVisibility; + String? Function(BuildContext, String?)? passwordControllerValidator; + // State field(s) for confirmPassword widget. + TextEditingController? confirmPasswordController; + late bool confirmPasswordVisibility; + String? Function(BuildContext, String?)? confirmPasswordControllerValidator; + + /// Initialization and disposal methods. + + void initState(BuildContext context) { + passwordVisibility = false; + confirmPasswordVisibility = false; + } + + void dispose() { + unfocusNode.dispose(); + emailAddressController?.dispose(); + passwordController?.dispose(); + confirmPasswordController?.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/signup/signup_widget.dart b/lib/pages/signup/signup_widget.dart new file mode 100644 index 0000000..efda56b --- /dev/null +++ b/lib/pages/signup/signup_widget.dart @@ -0,0 +1,520 @@ +import '/auth/firebase_auth/auth_util.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:simple_gradient_text/simple_gradient_text.dart'; +import 'signup_model.dart'; +export 'signup_model.dart'; + +class SignupWidget extends StatefulWidget { + const SignupWidget({Key? key}) : super(key: key); + + @override + _SignupWidgetState createState() => _SignupWidgetState(); +} + +class _SignupWidgetState extends State { + late SignupModel _model; + + final scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + _model = createModel(context, () => SignupModel()); + + _model.emailAddressController ??= TextEditingController(); + _model.passwordController ??= TextEditingController(); + _model.confirmPasswordController ??= TextEditingController(); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: FlutterFlowTheme.of(context).primaryText, + body: SafeArea( + top: true, + child: Align( + alignment: AlignmentDirectional(0.00, -1.00), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.0, 32.0, 0.0, 32.0), + child: Container( + width: double.infinity, + height: 99.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: GradientText( + 'PRO PLANET', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context).headlineLarge, + colors: [ + Color(0xFFFFD700), + FlutterFlowTheme.of(context).secondary + ], + gradientDirection: GradientDirection.ltr, + gradientType: GradientType.linear, + ), + ), + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(12.0, 12.0, 12.0, 12.0), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + maxWidth: 570.0, + ), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x33000000), + offset: Offset(0.0, 2.0), + ) + ], + borderRadius: BorderRadius.circular(12.0), + border: Border.all( + color: Color(0xFFF1F4F8), + width: 2.0, + ), + ), + child: Align( + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 24.0, 24.0, 24.0, 24.0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Create an account', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .displaySmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 36.0, + fontWeight: FontWeight.w600, + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 24.0), + child: Text( + 'Let\'s get started by filling out the form below.', + textAlign: TextAlign.start, + style: FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: Container( + width: double.infinity, + child: TextFormField( + controller: _model.emailAddressController, + autofocus: true, + autofillHints: [AutofillHints.email], + obscureText: false, + decoration: InputDecoration( + labelText: 'Email', + labelStyle: FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF4B39EF), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + filled: true, + fillColor: Colors.white, + contentPadding: + EdgeInsetsDirectional.fromSTEB( + 24.0, 24.0, 24.0, 24.0), + ), + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + keyboardType: TextInputType.emailAddress, + validator: _model + .emailAddressControllerValidator + .asValidator(context), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: Container( + width: double.infinity, + child: TextFormField( + controller: _model.passwordController, + autofocus: true, + autofillHints: [AutofillHints.password], + obscureText: !_model.passwordVisibility, + decoration: InputDecoration( + labelText: 'Password', + labelStyle: FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF4B39EF), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + filled: true, + fillColor: Colors.white, + contentPadding: + EdgeInsetsDirectional.fromSTEB( + 24.0, 24.0, 24.0, 24.0), + suffixIcon: InkWell( + onTap: () => setState( + () => _model.passwordVisibility = + !_model.passwordVisibility, + ), + focusNode: + FocusNode(skipTraversal: true), + child: Icon( + _model.passwordVisibility + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + ), + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + validator: _model + .passwordControllerValidator + .asValidator(context), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: Container( + width: double.infinity, + child: TextFormField( + controller: + _model.confirmPasswordController, + autofocus: true, + autofillHints: [AutofillHints.password], + obscureText: + !_model.confirmPasswordVisibility, + decoration: InputDecoration( + labelText: 'Confirm Password', + labelStyle: FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF57636C), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE0E3E7), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF4B39EF), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: + BorderRadius.circular(40.0), + ), + filled: true, + fillColor: Colors.white, + contentPadding: + EdgeInsetsDirectional.fromSTEB( + 24.0, 24.0, 24.0, 24.0), + suffixIcon: InkWell( + onTap: () => setState( + () => _model + .confirmPasswordVisibility = + !_model.confirmPasswordVisibility, + ), + focusNode: + FocusNode(skipTraversal: true), + child: Icon( + _model.confirmPasswordVisibility + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: Color(0xFF57636C), + size: 24.0, + ), + ), + ), + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + validator: _model + .confirmPasswordControllerValidator + .asValidator(context), + ), + ), + ), + Align( + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 16.0), + child: FFButtonWidget( + onPressed: () async { + GoRouter.of(context).prepareAuthEvent(); + if (_model.passwordController.text != + _model + .confirmPasswordController.text) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Passwords don\'t match!', + ), + ), + ); + return; + } + + final user = await authManager + .createAccountWithEmail( + context, + _model.emailAddressController.text, + _model.passwordController.text, + ); + if (user == null) { + return; + } + + context.pushNamedAuth( + 'homepage', context.mounted); + }, + text: 'Get Started', + options: FFButtonOptions( + width: 230.0, + height: 52.0, + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 0.0), + iconPadding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 0.0), + color: Color(0xFF4B39EF), + textStyle: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + elevation: 3.0, + borderSide: BorderSide( + color: Colors.transparent, + width: 1.0, + ), + borderRadius: BorderRadius.circular(40.0), + ), + ), + ), + ), + + // You will have to add an action on this rich text to go to your login page. + Align( + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 12.0), + child: InkWell( + splashColor: Colors.transparent, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: () async { + context.pushNamed('login'); + }, + child: RichText( + textScaleFactor: MediaQuery.of(context) + .textScaleFactor, + text: TextSpan( + children: [ + TextSpan( + text: 'Already have an account? ', + style: TextStyle(), + ), + TextSpan( + text: 'Sign In', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: + 'Plus Jakarta Sans', + color: Color(0xFF4B39EF), + fontSize: 14.0, + fontWeight: FontWeight.w600, + ), + ) + ], + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF101213), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/task/task_model.dart b/lib/pages/task/task_model.dart new file mode 100644 index 0000000..abd5516 --- /dev/null +++ b/lib/pages/task/task_model.dart @@ -0,0 +1,51 @@ +import '/flutter_flow/flutter_flow_animations.dart'; +import '/flutter_flow/flutter_flow_button_tabbar.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; + +class TaskModel extends FlutterFlowModel { + /// State fields for stateful widgets in this page. + + final unfocusNode = FocusNode(); + // State field(s) for TabBar widget. + TabController? tabBarController; + int get tabBarCurrentIndex => + tabBarController != null ? tabBarController!.index : 0; + + // State field(s) for Checkbox widget. + bool? checkboxValue1; + // State field(s) for Checkbox widget. + bool? checkboxValue2; + // State field(s) for Checkbox widget. + bool? checkboxValue3; + // State field(s) for Checkbox widget. + bool? checkboxValue4; + // State field(s) for Checkbox widget. + bool? checkboxValue5; + // State field(s) for Checkbox widget. + bool? checkboxValue6; + // State field(s) for TextField widget. + TextEditingController? textController; + String? Function(BuildContext, String?)? textControllerValidator; + + /// Initialization and disposal methods. + + void initState(BuildContext context) {} + + void dispose() { + unfocusNode.dispose(); + tabBarController?.dispose(); + textController?.dispose(); + } + + /// Action blocks are added here. + + /// Additional helper methods are added here. +} diff --git a/lib/pages/task/task_widget.dart b/lib/pages/task/task_widget.dart new file mode 100644 index 0000000..0de01d4 --- /dev/null +++ b/lib/pages/task/task_widget.dart @@ -0,0 +1,2257 @@ +import '/flutter_flow/flutter_flow_animations.dart'; +import '/flutter_flow/flutter_flow_button_tabbar.dart'; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import '/flutter_flow/flutter_flow_widgets.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'task_model.dart'; +export 'task_model.dart'; + +class TaskWidget extends StatefulWidget { + const TaskWidget({Key? key}) : super(key: key); + + @override + _TaskWidgetState createState() => _TaskWidgetState(); +} + +class _TaskWidgetState extends State with TickerProviderStateMixin { + late TaskModel _model; + + final scaffoldKey = GlobalKey(); + + final animationsMap = { + 'containerOnActionTriggerAnimation': AnimationInfo( + trigger: AnimationTrigger.onActionTrigger, + applyInitialState: true, + effects: [ + MoveEffect( + curve: Curves.easeInOut, + delay: 0.ms, + duration: 300.ms, + begin: Offset(-40.0, 0.0), + end: Offset(0.0, 0.0), + ), + ], + ), + }; + + @override + void initState() { + super.initState(); + _model = createModel(context, () => TaskModel()); + + _model.tabBarController = TabController( + vsync: this, + length: 3, + initialIndex: 0, + )..addListener(() => setState(() {})); + _model.textController ??= TextEditingController(); + setupAnimations( + animationsMap.values.where((anim) => + anim.trigger == AnimationTrigger.onActionTrigger || + !anim.applyInitialState), + this, + ); + } + + @override + void dispose() { + _model.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_model.unfocusNode), + child: Scaffold( + key: scaffoldKey, + backgroundColor: Colors.white, + body: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (responsiveVisibility( + context: context, + phone: false, + tablet: false, + )) + Container( + width: 270.0, + height: double.infinity, + decoration: BoxDecoration( + color: Color(0xFF6F61EF), + boxShadow: [ + BoxShadow( + blurRadius: 0.0, + color: Color(0xFFE5E7EB), + offset: Offset(1.0, 0.0), + ) + ], + borderRadius: BorderRadius.circular(0.0), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 16.0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 24.0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + boxShadow: [ + BoxShadow( + color: Color(0x4D9489F5), + offset: Offset(0.0, 1.0), + ) + ], + ), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 24.0, 16.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + Icons.add_task_rounded, + color: Colors.white, + size: 32.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'check.io', + style: FlutterFlowTheme.of(context) + .headlineMedium + .override( + fontFamily: 'Outfit', + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 20.0, 16.0, 16.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 44.0, + height: 44.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: + BorderRadius.circular(12.0), + border: Border.all( + color: Color(0xFF6F61EF), + width: 2.0, + ), + ), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 2.0, 2.0, 2.0, 2.0), + child: ClipRRect( + borderRadius: + BorderRadius.circular(8.0), + child: CachedNetworkImage( + fadeInDuration: + Duration(milliseconds: 500), + fadeOutDuration: + Duration(milliseconds: 500), + imageUrl: + 'https://images.unsplash.com/photo-1624561172888-ac93c696e10c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NjJ8fHVzZXJzfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=900&q=60', + width: 44.0, + height: 44.0, + fit: BoxFit.cover, + ), + ), + ), + ), + Expanded( + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Andrew D.', + style: + FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: + 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: + FontWeight.w600, + ), + ), + Padding( + padding: EdgeInsetsDirectional + .fromSTEB( + 0.0, 4.0, 0.0, 0.0), + child: Text( + 'admin@gmail.com', + style: FlutterFlowTheme.of( + context) + .labelSmall + .override( + fontFamily: + 'Plus Jakarta Sans', + color: + Color(0x9AFFFFFF), + fontSize: 12.0, + fontWeight: + FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + Icon( + Icons.notifications_none, + color: Colors.white, + size: 28.0, + ), + ], + ), + ), + ], + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 16.0, 12.0), + child: Container( + width: double.infinity, + height: 50.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: BorderRadius.circular(12.0), + shape: BoxShape.rectangle, + border: Border.all( + color: Color(0xFF6F61EF), + width: 1.0, + ), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 12.0, 12.0), + child: Container( + width: 4.0, + height: 100.0, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(12.0), + ), + ), + ), + Icon( + Icons.stacked_bar_chart_rounded, + color: Colors.white, + size: 28.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Dashboard', + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 16.0, 12.0), + child: Container( + width: double.infinity, + height: 50.0, + decoration: BoxDecoration( + color: Color(0xFF6F61EF), + borderRadius: BorderRadius.circular(12.0), + shape: BoxShape.rectangle, + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 12.0, 12.0), + child: Container( + width: 4.0, + height: 100.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: + BorderRadius.circular(12.0), + ), + ), + ), + Icon( + Icons.attach_money_rounded, + color: Colors.white, + size: 28.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Transactions', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 16.0, 12.0), + child: Container( + width: double.infinity, + height: 50.0, + decoration: BoxDecoration( + color: Color(0xFF6F61EF), + borderRadius: BorderRadius.circular(12.0), + shape: BoxShape.rectangle, + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 12.0, 12.0), + child: Container( + width: 4.0, + height: 100.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: + BorderRadius.circular(12.0), + ), + ), + ), + Icon( + Icons.folder_open, + color: Colors.white, + size: 28.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Projects', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 16.0, 12.0), + child: Container( + width: double.infinity, + height: 50.0, + decoration: BoxDecoration( + color: Color(0xFF6F61EF), + borderRadius: BorderRadius.circular(12.0), + shape: BoxShape.rectangle, + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 12.0, 12.0), + child: Container( + width: 4.0, + height: 100.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: + BorderRadius.circular(12.0), + ), + ), + ), + Icon( + Icons.groups, + color: Colors.white, + size: 28.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 0.0, 0.0), + child: Text( + 'Users', + style: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 16.0, 16.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Divider( + height: 12.0, + thickness: 2.0, + color: Color(0x4D9489F5), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 12.0, 0.0), + child: Container( + width: 80.0, + height: 40.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: BorderRadius.circular(20.0), + border: Border.all( + color: Color(0x4D9489F5), + width: 1.0, + ), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 2.0, 2.0, 2.0, 2.0), + child: Stack( + alignment: + AlignmentDirectional(0.0, 0.0), + children: [ + Align( + alignment: AlignmentDirectional( + -0.90, 0.00), + child: Padding( + padding: EdgeInsetsDirectional + .fromSTEB(6.0, 0.0, 0.0, 0.0), + child: Icon( + Icons.wb_sunny_rounded, + color: Colors.white, + size: 24.0, + ), + ), + ), + Align( + alignment: AlignmentDirectional( + 1.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional + .fromSTEB(0.0, 0.0, 6.0, 0.0), + child: Icon( + Icons.mode_night_rounded, + color: Colors.white, + size: 24.0, + ), + ), + ), + Align( + alignment: AlignmentDirectional( + 1.00, 0.00), + child: Container( + width: 36.0, + height: 36.0, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + blurRadius: 4.0, + color: Color(0x430B0D0F), + offset: Offset(0.0, 2.0), + ) + ], + borderRadius: + BorderRadius.circular(30.0), + shape: BoxShape.rectangle, + ), + ).animateOnActionTrigger( + animationsMap[ + 'containerOnActionTriggerAnimation']!, + ), + ), + ], + ), + ), + ), + ), + ].divide(SizedBox(height: 12.0)), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + child: Align( + alignment: AlignmentDirectional(0.00, -1.00), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 24.0, 16.0, 0.0), + child: Container( + constraints: BoxConstraints( + maxWidth: 900.0, + ), + decoration: BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(4.0, 4.0, 4.0, 4.0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 12.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + 'Tasks', + style: FlutterFlowTheme.of(context) + .displaySmall + .override( + fontFamily: 'Outfit', + color: Color(0xFF15161E), + fontSize: 36.0, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + Expanded( + child: Column( + children: [ + Align( + alignment: Alignment(-1.0, 0), + child: FlutterFlowButtonTabBar( + useToggleButtonStyle: false, + isScrollable: true, + labelStyle: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 16.0, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: + FlutterFlowTheme.of(context) + .labelLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + labelColor: Color(0xFF15161E), + unselectedLabelColor: Color(0xFF606A85), + backgroundColor: Color(0x4D9489F5), + unselectedBackgroundColor: Colors.white, + borderColor: Color(0xFF6F61EF), + borderWidth: 2.0, + borderRadius: 12.0, + elevation: 0.0, + labelPadding: + EdgeInsetsDirectional.fromSTEB( + 16.0, 0.0, 16.0, 0.0), + buttonMargin: + EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 16.0, 0.0), + tabs: [ + Tab( + text: 'My Tasks', + ), + Tab( + text: 'This Week', + ), + Tab( + text: 'Completed', + ), + ], + controller: _model.tabBarController, + ), + ), + Expanded( + child: TabBarView( + controller: _model.tabBarController, + children: [ + Align( + alignment: + AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 16.0, 0.0, 16.0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(8.0), + border: Border.all( + color: Color(0xFFE5E7EB), + ), + ), + child: Padding( + padding: EdgeInsetsDirectional + .fromSTEB( + 16.0, 24.0, 16.0, 24.0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisSize: + MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 4.0, + 0.0, + 0.0, + 0.0), + child: Icon( + Icons + .library_add_check_rounded, + color: + Color(0xFF6F61EF), + size: 36.0, + ), + ), + Expanded( + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 12.0, + 0.0, + 0.0, + 0.0), + child: Text( + 'Task Overview', + style: FlutterFlowTheme + .of(context) + .headlineSmall + .override( + fontFamily: + 'Outfit', + color: Color( + 0xFF15161E), + fontSize: + 22.0, + fontWeight: + FontWeight + .bold, + ), + ), + ), + ), + Container( + height: 32.0, + decoration: + BoxDecoration( + color: + Color(0xFFF1F4F8), + borderRadius: + BorderRadius + .circular( + 12.0), + ), + alignment: + AlignmentDirectional( + 0.00, 0.00), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 12.0, + 0.0, + 12.0, + 0.0), + child: Text( + '12', + style: FlutterFlowTheme + .of(context) + .bodyMedium + .override( + fontFamily: + 'Plus Jakarta Sans', + color: Color( + 0xFF15161E), + fontSize: + 14.0, + fontWeight: + FontWeight + .w500, + ), + ), + ), + ), + ], + ), + Padding( + padding: + EdgeInsetsDirectional + .fromSTEB(0.0, 8.0, + 0.0, 0.0), + child: ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + scrollDirection: + Axis.vertical, + children: [ + Container( + width: 100.0, + height: 100.0, + decoration: + BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 0.0, + 12.0, + 0.0, + 8.0), + child: Row( + mainAxisSize: + MainAxisSize + .max, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Theme( + data: + ThemeData( + checkboxTheme: + CheckboxThemeData( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + unselectedWidgetColor: + Color( + 0xFFE0E3E7), + ), + child: + Checkbox( + value: _model + .checkboxValue1 ??= + true, + onChanged: + (newValue) async { + setState(() => + _model.checkboxValue1 = + newValue!); + }, + activeColor: + Color( + 0xFF6F61EF), + checkColor: + Colors + .white, + ), + ), + Expanded( + child: Column( + mainAxisSize: + MainAxisSize + .max, + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + 'Plant a tree.\nFact:You will reduce pollution by2%', + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + '18 Jan, 2023', + style: FlutterFlowTheme.of(context).labelMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, + 8.0, + 0.0, + 0.0), + child: + Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0xFFF1F4F8), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 12.0, 0.0), + child: Text( + 'upload photo', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + Container( + width: 100.0, + decoration: + BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 0.0, + 12.0, + 0.0, + 8.0), + child: Row( + mainAxisSize: + MainAxisSize + .max, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Theme( + data: + ThemeData( + checkboxTheme: + CheckboxThemeData( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + unselectedWidgetColor: + Color( + 0xFF606A85), + ), + child: + Checkbox( + value: _model + .checkboxValue2 ??= + false, + onChanged: + (newValue) async { + setState(() => + _model.checkboxValue2 = + newValue!); + }, + activeColor: + Color( + 0xFF6F61EF), + checkColor: + Colors + .white, + ), + ), + Expanded( + child: Column( + mainAxisSize: + MainAxisSize + .max, + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + 'Feed an Animal.\nFact:You could reduce animal hunger by up to 0.00006667%.', + maxLines: + 3, + style: + FlutterFlowTheme.of(context).bodyMedium, + ), + ), + Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + '16 Jan, 2023', + style: FlutterFlowTheme.of(context).labelMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, + 8.0, + 0.0, + 0.0), + child: + Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0xFFF1F4F8), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 12.0, 0.0), + child: Text( + 'upload photo', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + Container( + width: 100.0, + decoration: + BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 0.0, + 12.0, + 0.0, + 8.0), + child: Row( + mainAxisSize: + MainAxisSize + .max, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Theme( + data: + ThemeData( + checkboxTheme: + CheckboxThemeData( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + unselectedWidgetColor: + Color( + 0xFF606A85), + ), + child: + Checkbox( + value: _model + .checkboxValue3 ??= + false, + onChanged: + (newValue) async { + setState(() => + _model.checkboxValue3 = + newValue!); + }, + activeColor: + Color( + 0xFF6F61EF), + checkColor: + Colors + .white, + ), + ), + Expanded( + child: Column( + mainAxisSize: + MainAxisSize + .max, + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + 'Use non plastic bag', + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 16.0, + fontWeight: FontWeight.w600, + ), + ), + ), + Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + '15 Jan, 2023', + style: FlutterFlowTheme.of(context).labelMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, + 8.0, + 0.0, + 0.0), + child: + Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0xFFF1F4F8), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 12.0, 0.0), + child: Text( + 'upload photo', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + Align( + alignment: + AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB( + 0.0, 16.0, 0.0, 16.0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(8.0), + border: Border.all( + color: Color(0xFFE5E7EB), + ), + ), + child: Padding( + padding: EdgeInsetsDirectional + .fromSTEB( + 16.0, 24.0, 16.0, 24.0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisSize: + MainAxisSize.max, + children: [ + Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 4.0, + 0.0, + 0.0, + 0.0), + child: Icon( + Icons + .library_add_check_rounded, + color: + Color(0xFF6F61EF), + size: 36.0, + ), + ), + Expanded( + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 12.0, + 0.0, + 0.0, + 0.0), + child: Text( + 'Due this week', + style: FlutterFlowTheme + .of(context) + .headlineSmall + .override( + fontFamily: + 'Outfit', + color: Color( + 0xFF15161E), + fontSize: + 22.0, + fontWeight: + FontWeight + .bold, + ), + ), + ), + ), + Container( + height: 32.0, + decoration: + BoxDecoration( + color: + Color(0xFFF1F4F8), + borderRadius: + BorderRadius + .circular( + 12.0), + ), + alignment: + AlignmentDirectional( + 0.00, 0.00), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 12.0, + 0.0, + 12.0, + 0.0), + child: Text( + '3', + style: FlutterFlowTheme + .of(context) + .bodyMedium + .override( + fontFamily: + 'Plus Jakarta Sans', + color: Color( + 0xFF15161E), + fontSize: + 14.0, + fontWeight: + FontWeight + .w500, + ), + ), + ), + ), + ], + ), + Padding( + padding: + EdgeInsetsDirectional + .fromSTEB(0.0, 8.0, + 0.0, 0.0), + child: ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + scrollDirection: + Axis.vertical, + children: [ + Container( + width: 100.0, + decoration: + BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 0.0, + 12.0, + 0.0, + 8.0), + child: Row( + mainAxisSize: + MainAxisSize + .max, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Theme( + data: + ThemeData( + checkboxTheme: + CheckboxThemeData( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + unselectedWidgetColor: + Color( + 0xFFE0E3E7), + ), + child: + Checkbox( + value: _model + .checkboxValue4 ??= + true, + onChanged: + (newValue) async { + setState(() => + _model.checkboxValue4 = + newValue!); + }, + activeColor: + Color( + 0xFF6F61EF), + checkColor: + Colors + .white, + ), + ), + Expanded( + child: Column( + mainAxisSize: + MainAxisSize + .max, + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + 'Water a plant for 7 days continuously...', + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + '18 Jan, 2023', + style: FlutterFlowTheme.of(context).labelMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, + 8.0, + 0.0, + 0.0), + child: + Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0xFFF1F4F8), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 12.0, 0.0), + child: Text( + 'upload photo', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + Container( + width: 100.0, + decoration: + BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 0.0, + 12.0, + 0.0, + 8.0), + child: Row( + mainAxisSize: + MainAxisSize + .max, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Theme( + data: + ThemeData( + checkboxTheme: + CheckboxThemeData( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + unselectedWidgetColor: + Color( + 0xFF606A85), + ), + child: + Checkbox( + value: _model + .checkboxValue5 ??= + false, + onChanged: + (newValue) async { + setState(() => + _model.checkboxValue5 = + newValue!); + }, + activeColor: + Color( + 0xFF6F61EF), + checkColor: + Colors + .white, + ), + ), + Expanded( + child: Column( + mainAxisSize: + MainAxisSize + .max, + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + 'Use public transport', + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + '16 Jan, 2023', + style: FlutterFlowTheme.of(context).labelMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, + 8.0, + 0.0, + 0.0), + child: + Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0xFFF1F4F8), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 12.0, 0.0), + child: Text( + 'upload photo', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + Container( + width: 100.0, + decoration: + BoxDecoration( + color: Colors.white, + ), + child: Padding( + padding: + EdgeInsetsDirectional + .fromSTEB( + 0.0, + 12.0, + 0.0, + 8.0), + child: Row( + mainAxisSize: + MainAxisSize + .max, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Theme( + data: + ThemeData( + checkboxTheme: + CheckboxThemeData( + shape: + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(4.0), + ), + ), + unselectedWidgetColor: + Color( + 0xFF606A85), + ), + child: + Checkbox( + value: _model + .checkboxValue6 ??= + false, + onChanged: + (newValue) async { + setState(() => + _model.checkboxValue6 = + newValue!); + }, + activeColor: + Color( + 0xFF6F61EF), + checkColor: + Colors + .white, + ), + ), + Expanded( + child: Column( + mainAxisSize: + MainAxisSize + .max, + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + 'Participate in an NGO\'s event', + style: FlutterFlowTheme.of(context) + .bodyLarge + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 16.0, + fontWeight: FontWeight.w600, + ), + ), + ), + Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, + 8.0, + 0.0, + 0.0), + child: + Text( + '15 Jan, 2023', + style: FlutterFlowTheme.of(context).labelMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, + 8.0, + 0.0, + 0.0), + child: + Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0xFFF1F4F8), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 12.0, 0.0), + child: Text( + 'upload photo', + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + Container(), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ), + + // This is an amazing side panel, we need to make sure we know how it works =) + if (responsiveVisibility( + context: context, + phone: false, + tablet: false, + tabletLandscape: false, + )) + Semantics( + label: + 'This side panel appears on the right of the screen and it houses the information of the viewed task.', + child: Container( + width: 430.0, + height: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + blurRadius: 3.0, + color: Color(0x33000000), + offset: Offset(0.0, 1.0), + ) + ], + border: Border.all( + color: Color(0xFFF1F4F8), + ), + ), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(16.0, 16.0, 16.0, 16.0), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 12.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'My Channels / Task / Task #4234', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + Icon( + Icons.more_vert_sharp, + color: Color(0xFF606A85), + size: 24.0, + ), + ], + ), + ), + Icon( + Icons.motion_photos_on_rounded, + color: Color(0xFF6F61EF), + size: 24.0, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 16.0, 0.0, 12.0), + child: Text( + 'Update our command Palette to be more usable.', + style: FlutterFlowTheme.of(context) + .titleLarge + .override( + fontFamily: 'Outfit', + color: Color(0xFF15161E), + fontSize: 22.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 2, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 4.0), + child: Text( + 'Subtasks', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Expanded( + flex: 6, + child: Text( + '0 tasks', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 2, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 4.0), + child: Text( + 'Created', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Expanded( + flex: 6, + child: Text( + 'Created', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 2, + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 0.0, 4.0), + child: Text( + 'Due Date', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Icon( + Icons.date_range_outlined, + color: Color(0xFF15161E), + size: 24.0, + ), + ), + Expanded( + flex: 6, + child: Text( + '10 Jan, 2023', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 0.0, 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 4.0, 24.0, 4.0), + child: Text( + 'Categories', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 8.0, 0.0), + child: Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0x4D9489F5), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Text( + 'Design System', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 8.0, 0.0), + child: Container( + height: 28.0, + decoration: BoxDecoration( + color: Color(0x4C39D2C0), + borderRadius: BorderRadius.circular(8.0), + ), + alignment: AlignmentDirectional(0.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 12.0, 0.0, 12.0, 0.0), + child: Text( + 'Product', + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 24.0, 4.0), + child: Text( + 'Description', + style: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 8.0, 0.0, 0.0), + child: TextFormField( + controller: _model.textController, + obscureText: false, + decoration: InputDecoration( + hintText: 'Leave a note here...', + hintStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF606A85), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFE5E7EB), + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF6F61EF), + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF5963), + width: 2.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + filled: true, + fillColor: Colors.white, + ), + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Plus Jakarta Sans', + color: Color(0xFF15161E), + fontSize: 14.0, + fontWeight: FontWeight.w500, + ), + maxLines: 8, + minLines: 4, + validator: _model.textControllerValidator + .asValidator(context), + ), + ), + Align( + alignment: AlignmentDirectional(1.00, 0.00), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 12.0, 0.0, 0.0), + child: FFButtonWidget( + onPressed: () { + print('Button pressed ...'); + }, + text: 'Post Note', + options: FFButtonOptions( + width: 130.0, + height: 40.0, + padding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 0.0), + iconPadding: EdgeInsetsDirectional.fromSTEB( + 0.0, 0.0, 0.0, 0.0), + color: Color(0xFF6F61EF), + textStyle: FlutterFlowTheme.of(context) + .titleSmall + .override( + fontFamily: 'Plus Jakarta Sans', + color: Colors.white, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + elevation: 2.0, + borderSide: BorderSide( + color: Colors.transparent, + width: 1.0, + ), + borderRadius: BorderRadius.circular(8.0), + hoverColor: Color(0xFF2B16ED), + hoverTextColor: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..a24e33d --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,151 @@ +name: bharat_tracker +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + auto_size_text: 3.0.0 + cached_network_image: 3.2.1 + cloud_firestore: 4.8.0 + cloud_firestore_platform_interface: 5.15.0 + cloud_firestore_web: 3.6.0 + firebase_auth: 4.6.2 + firebase_auth_platform_interface: 6.15.2 + firebase_auth_web: 5.5.2 + firebase_core: 2.13.1 + firebase_core_platform_interface: 4.8.0 + firebase_core_web: 2.5.0 + firebase_performance: 0.9.2+2 + firebase_performance_platform_interface: 0.1.4+2 + firebase_performance_web: 0.1.4+2 + flutter_animate: 4.1.1+1 + flutter_cache_manager: 3.3.0 + flutter_native_splash: 2.3.1 + font_awesome_flutter: 10.1.0 + from_css_color: 2.0.0 + go_router: 7.1.1 + google_fonts: 4.0.3 + google_sign_in: 6.0.2 + google_sign_in_android: 6.1.8 + google_sign_in_ios: 5.6.1 + google_sign_in_platform_interface: 2.4.0 + google_sign_in_web: 0.11.0+2 + intl: 0.18.0 + json_path: 0.6.2 + page_transition: 2.0.4 + path_provider: 2.0.14 + path_provider_android: 2.0.25 + path_provider_foundation: 2.2.2 + path_provider_platform_interface: 2.0.6 + plugin_platform_interface: 2.1.3 + provider: 6.0.4 + rxdart: 0.27.7 + shared_preferences: 2.0.15 + shared_preferences_android: 2.1.0 + shared_preferences_ios: 2.1.1 + shared_preferences_platform_interface: 2.2.0 + shared_preferences_web: 2.1.0 + sign_in_with_apple: 4.3.0 + sign_in_with_apple_platform_interface: 1.0.0 + sign_in_with_apple_web: 1.0.1 + simple_gradient_text: 1.2.3 + smooth_page_indicator: 1.0.1 + sqflite: 2.2.6 + stream_transform: 2.1.0 + timeago: 3.2.2 + universal_io: 2.2.2 + url_launcher: 6.1.10 + url_launcher_android: 6.0.27 + url_launcher_ios: 6.1.4 + url_launcher_platform_interface: 2.1.2 + xml: 6.3.0 + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.0 + +dev_dependencies: + flutter_launcher_icons: 0.12.0 + image: 4.0.17 + + flutter_test: + sdk: flutter + + +flutter_icons: + android: true + ios: true + remove_alpha_ios: true + image_path: 'assets/images/app_launcher_icon.png' + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/fonts/ + - assets/images/ + - assets/videos/ + - assets/audios/ + - assets/lottie_animations/ + - assets/rive_animations/ + - assets/pdfs/ + + + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..f75c48c --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,18 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:bharat_tracker/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..300efae Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/app_launcher_icon.png b/web/icons/app_launcher_icon.png new file mode 100644 index 0000000..b8a3fb7 Binary files /dev/null and b/web/icons/app_launcher_icon.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..8fabad2 --- /dev/null +++ b/web/index.html @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BHARAT TRACKER + + + + + + + + + + + + + + + + + + + +