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 =
+