commit 7727fa09edeeeaf8e6b810975eb7a2d70a04ab31 Author: developer Date: Tue Aug 19 16:03:24 2025 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# 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 +.pub-cache/ +.pub/ +/build/ + +# 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..5b39692 --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# 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: "d8a9f9a52e5af486f80d932e838ee93861ffd863" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: android + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: ios + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: linux + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: macos + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: web + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: windows + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d8e598 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# smartflow + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..55afd91 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..b137752 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.smartflow" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId = "com.example.smartflow" + minSdk = 23 // ← ubah langsung ke 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName +} + + + 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 = "../.." +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..73fe832 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "482063931859", + "project_id": "smartflow-4e1bf", + "storage_bucket": "smartflow-4e1bf.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:482063931859:android:2682986f8d1d4f9e4bf726", + "android_client_info": { + "package_name": "com.example.smartflow" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCK3SvB94v4sZOGldEXxCtF_iSzvGzym34" + } + ], + "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..399f698 --- /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..e9d6f39 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/smartflow/MainActivity.kt b/android/app/src/main/kotlin/com/example/smartflow/MainActivity.kt new file mode 100644 index 0000000..84ecd8f --- /dev/null +++ b/android/app/src/main/kotlin/com/example/smartflow/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.smartflow + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + 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..304732f --- /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..06952be --- /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/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /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..399f698 --- /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..d2ffbff --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..2597170 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7bb2df6 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..9759a22 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,28 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + // END: FlutterFire Configuration + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..b902760 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"smartflow-4e1bf","appId":"1:482063931859:android:2682986f8d1d4f9e4bf726","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"smartflow-4e1bf","configurations":{"android":"1:482063931859:android:2682986f8d1d4f9e4bf726"}}}}}} \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +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..7c56964 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7766461 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + 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 */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 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 */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + 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 = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 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 */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + 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 */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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 = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.smartflow; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.smartflow.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.smartflow.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.smartflow.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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 = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.smartflow; + 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 = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.smartflow; + 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 */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 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..919434a --- /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..8e3ca5d --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..6266644 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@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..7353c41 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..797d452 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..6ed2d93 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..4cd7b00 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..fe73094 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..321773c 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..797d452 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..502f463 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..0ec3034 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..0ec3034 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..e9f5fea 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..84ac32a 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..8953cba 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..0467bf1 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/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..d393fd3 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Smartflow + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + smartflow + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + 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/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..fa4b8ba --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'routes/app_routes.dart'; + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Monitoring Debit Air', + debugShowCheckedModeBanner: false, + theme: ThemeData( + primarySwatch: Colors.blue, + useMaterial3: true, + ), + initialRoute: '/', + routes: AppRoutes.routes, + ); + } +} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..c57f1aa --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,62 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for ios - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyCK3SvB94v4sZOGldEXxCtF_iSzvGzym34', + appId: '1:482063931859:android:2682986f8d1d4f9e4bf726', + messagingSenderId: '482063931859', + projectId: 'smartflow-4e1bf', + storageBucket: 'smartflow-4e1bf.firebasestorage.app', + ); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..a79c646 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:intl/date_symbol_data_local.dart'; // Tambahkan ini +import 'app.dart'; +import '/firebase_options.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + + await initializeDateFormatting('id_ID', null); // Tambahkan ini + + runApp(const MyApp()); +} diff --git a/lib/models/sensor_data.dart b/lib/models/sensor_data.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart new file mode 100644 index 0000000..463763b --- /dev/null +++ b/lib/routes/app_routes.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import '../widgets/bottom_bar.dart'; + + +// Import semua halaman yang digunakan +import '../screens/splash_screen.dart'; +import '../screens/auth/login_screen.dart'; +import '../screens/auth/register_screen.dart'; +import '../screens/dashboard/dashboard_screen.dart'; +import '../screens/history/history_screen.dart'; +import '../screens/billing/billing_screen.dart'; +import '../screens/settings/settings_screen.dart'; +import '../screens/notifications/notifcations_screen.dart'; +import '../screens/dashboard/admin_dashboard_screen.dart'; + +class AppRoutes { + static Map routes = { + '/': (context) => const SplashScreen(), + '/login': (context) => const LoginScreen(), + '/register': (context) => const RegisterScreen(), + '/dashboard': (context) => const DashboardScreen(), + '/history': (context) => const HistoryScreen(), + '/billing': (context) => const BillingScreen(), + '/settings': (context) => const SettingsScreen(), + '/home': (context) => const CustomBottomNav(), + '/notifications': (context) => const NotificationScreen(), + '/adminDashboard': (context) => const AdminDashboardScreen(), + }; +} diff --git a/lib/screens/auth/login_screen.dart b/lib/screens/auth/login_screen.dart new file mode 100644 index 0000000..0de0ef0 --- /dev/null +++ b/lib/screens/auth/login_screen.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + + bool _isLoading = false; + String? _errorMessage; + + Future _login() async { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + + try { + UserCredential userCredential = await FirebaseAuth.instance + .signInWithEmailAndPassword( + email: _emailController.text.trim(), + password: _passwordController.text.trim()); + + User? user = userCredential.user; + + if (user != null) { + final userDoc = await FirebaseFirestore.instance + .collection('users') + .doc(user.uid) + .get(); + + if (userDoc.exists) { + final role = userDoc['role']; + + if (role == 'admin') { + Navigator.pushReplacementNamed(context, '/adminDashboard'); + } else { + Navigator.pushReplacementNamed(context, '/home'); + } + } else { + setState(() { + _errorMessage = 'Data pengguna tidak ditemukan.'; + }); + } + } + } on FirebaseAuthException catch (e) { + setState(() { + _errorMessage = e.message; + }); + } + + setState(() { + _isLoading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color.fromARGB(255, 107, 139, 255), + Color.fromARGB(255, 140, 163, 247) + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.water_drop_rounded, + size: 80, color: Colors.white), + const SizedBox(height: 16), + const Text( + "Login Monitoring Air", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 32), + Card( + color: Colors.white.withOpacity(0.9), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 6, + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + TextField( + controller: _emailController, + decoration: const InputDecoration( + labelText: 'Email', + prefixIcon: Icon(Icons.email), + ), + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 16), + TextField( + controller: _passwordController, + decoration: const InputDecoration( + labelText: 'Password', + prefixIcon: Icon(Icons.lock), + ), + obscureText: true, + ), + const SizedBox(height: 24), + if (_errorMessage != null) + Text( + _errorMessage!, + style: const TextStyle(color: Colors.red), + ), + if (_isLoading) + const CircularProgressIndicator() + else + ElevatedButton( + onPressed: _login, + style: ElevatedButton.styleFrom( + backgroundColor: + const Color.fromARGB(255, 107, 139, 255), + minimumSize: const Size.fromHeight(50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: const Text( + "Login", + style: + TextStyle(fontSize: 16, color: Colors.white), + ), + ), + const SizedBox(height: 12), + TextButton( + onPressed: () { + Navigator.pushNamed(context, '/register'); + }, + child: const Text("Belum punya akun? Daftar di sini"), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/auth/register_screen.dart b/lib/screens/auth/register_screen.dart new file mode 100644 index 0000000..95ce075 --- /dev/null +++ b/lib/screens/auth/register_screen.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class RegisterScreen extends StatelessWidget { + const RegisterScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center(child: Text("reg")), + ); + } +} diff --git a/lib/screens/billing/billing_screen.dart b/lib/screens/billing/billing_screen.dart new file mode 100644 index 0000000..17d4165 --- /dev/null +++ b/lib/screens/billing/billing_screen.dart @@ -0,0 +1,251 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class BillingScreen extends StatefulWidget { + const BillingScreen({super.key}); + + @override + State createState() => _BillingScreenState(); +} + +class _BillingScreenState extends State { + String selectedMonth = + DateFormat('MMMM yyyy', 'en_US').format(DateTime.now()); + final user = FirebaseAuth.instance.currentUser; + + @override + Widget build(BuildContext context) { + final tagihanRef = FirebaseFirestore.instance + .collection('tagihan') + .where('uid', isEqualTo: user?.uid); + + return Scaffold( + backgroundColor: const Color(0xFFF2F6FF), + appBar: AppBar( + title: const Text("Tagihan Air Bulanan"), + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + elevation: 0, + ), + body: StreamBuilder( + stream: tagihanRef.snapshots(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) + return const Center(child: CircularProgressIndicator()); + if (!snapshot.hasData || snapshot.data!.docs.isEmpty) + return const Center(child: Text("Belum ada data tagihan")); + + final tagihanList = snapshot.data!.docs; + + // Ambil list bulan unik + final uniqueMonths = tagihanList + .map((doc) => + (doc.data() as Map)['bulan'] as String) + .toSet() + .toList() + ..sort((a, b) => DateFormat('MMMM yyyy', 'en_US') + .parse(a) + .compareTo(DateFormat('MMMM yyyy', 'en_US').parse(b))); + + // Pastikan selectedMonth valid + if (!uniqueMonths.contains(selectedMonth)) { + selectedMonth = uniqueMonths.first; + } + + final selectedDoc = tagihanList + .where((e) => + (e.data() as Map)['bulan'] == selectedMonth) + .toList(); + + Map? selectedData = selectedDoc.isNotEmpty + ? selectedDoc.first.data() as Map + : null; + + return Column( + children: [ + // Header Total Tagihan + Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + margin: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF6B8BFF), + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 10, + offset: Offset(0, 4)) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Total Tagihan Bulan Ini", + style: TextStyle(color: Colors.white70, fontSize: 14)), + const SizedBox(height: 6), + Text( + selectedData != null + ? "Rp ${NumberFormat("#,##0", "id_ID").format(selectedData['tagihan'])}" + : "-", + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 8), + Text( + selectedData != null + ? "Pemakaian: ${selectedData['pemakaian']} m³" + : "", + style: + const TextStyle(color: Colors.white70, fontSize: 14), + ), + ], + ), + ), + + // Filter Bulan + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: DropdownButtonFormField( + value: selectedMonth, + items: uniqueMonths.map((month) { + // Convert ke Indonesia untuk ditampilkan jika ingin + DateTime parsedMonth = + DateFormat('MMMM yyyy', 'en_US').parse(month); + String displayMonth = + DateFormat('MMMM yyyy', 'id_ID').format(parsedMonth); + + return DropdownMenuItem( + value: month, + child: Text(displayMonth), + ); + }).toList(), + onChanged: (value) { + setState(() => selectedMonth = value!); + }, + decoration: InputDecoration( + labelText: 'Pilih Bulan', + filled: true, + fillColor: Colors.white, + prefixIcon: const Icon(Icons.calendar_month_rounded), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Riwayat Tagihan + Expanded( + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: tagihanList.length, + itemBuilder: (context, index) { + final data = + tagihanList[index].data() as Map; + final isPaid = data['status'] == 'Lunas'; + + // Convert bulan ke Indonesia untuk tampilan + DateTime parsedMonth = + DateFormat('MMMM yyyy', 'en_US').parse(data['bulan']); + String displayMonth = + DateFormat('MMMM yyyy', 'id_ID').format(parsedMonth); + + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isPaid + ? Colors.green.withOpacity(0.3) + : Colors.orange.withOpacity(0.3), + width: 1, + ), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 5, + offset: Offset(0, 2)), + ], + ), + child: Row( + children: [ + CircleAvatar( + backgroundColor: isPaid + ? Colors.green.withOpacity(0.1) + : Colors.orange.withOpacity(0.1), + child: Icon( + isPaid + ? Icons.check_circle + : Icons.warning_amber_rounded, + color: isPaid ? Colors.green : Colors.orange, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + displayMonth, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + Text("Pemakaian: ${data['pemakaian']} m³"), + Text( + "Tagihan: Rp ${NumberFormat("#,##0", "id_ID").format(data['tagihan'])}", + style: const TextStyle(color: Colors.black87), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Chip( + label: Text( + data['status'], + style: const TextStyle( + color: Colors.white, fontSize: 12), + ), + backgroundColor: + isPaid ? Colors.green : Colors.orange, + ), + IconButton( + icon: const Icon(Icons.download_rounded), + tooltip: "Unduh PDF", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Unduh tagihan $displayMonth")), + ); + }, + color: const Color(0xFF6B8BFF), + ) + ], + ) + ], + ), + ); + }, + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/screens/dashboard/admin_dashboard_screen.dart b/lib/screens/dashboard/admin_dashboard_screen.dart new file mode 100644 index 0000000..77043c8 --- /dev/null +++ b/lib/screens/dashboard/admin_dashboard_screen.dart @@ -0,0 +1,180 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class AdminDashboardScreen extends StatefulWidget { + const AdminDashboardScreen({super.key}); + + @override + State createState() => _AdminDashboardScreenState(); +} + +class _AdminDashboardScreenState extends State { + String _selectedMonth = DateFormat('MMMM yyyy').format(DateTime.now()); + + @override + Widget build(BuildContext context) { + final tagihanRef = FirebaseFirestore.instance.collection('tagihan'); + + return Scaffold( + appBar: AppBar( + title: const Text("Dashboard Admin"), + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + ), + backgroundColor: Colors.blue[50], + body: Padding( + padding: const EdgeInsets.all(16), + child: StreamBuilder( + stream: tagihanRef.where('bulan', isEqualTo: _selectedMonth).snapshots(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator()); + + final data = snapshot.data?.docs ?? []; + + final totalUser = data.map((e) => e['uid']).toSet().length; + final totalPemakaian = data.fold(0, (sum, e) => sum + (e['pemakaian'] ?? 0)); + final totalTagihan = data.fold(0, (sum, e) => sum + (e['tagihan'] ?? 0)); + final lunasCount = data.where((e) => e['status'] == 'Lunas').length; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildMonthFilter(), + const SizedBox(height: 16), + _buildStats(totalUser, totalPemakaian, totalTagihan, lunasCount), + const SizedBox(height: 24), + const Text( + "Daftar Tagihan Bulan Ini", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF6B8BFF)), + ), + const SizedBox(height: 12), + Expanded( + child: data.isEmpty + ? const Center(child: Text("Tidak ada data tagihan.")) + : ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + final doc = data[index]; + return Card( + margin: const EdgeInsets.symmetric(vertical: 8), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: ListTile( + leading: const Icon(Icons.person, color: Color(0xFF6B8BFF)), + title: Text(doc['email'] ?? '-'), + subtitle: Text( + 'Pemakaian: ${doc['pemakaian'] ?? 0} m³\nTagihan: Rp${(doc['tagihan'] ?? 0).toStringAsFixed(0)}', + ), + trailing: DropdownButton( + value: doc['status'], + onChanged: (value) { + if (value != null) { + doc.reference.update({'status': value}); + } + }, + items: ['Lunas', 'Belum Lunas'] + .map((status) => DropdownMenuItem( + value: status, + child: Text(status), + )) + .toList(), + ), + ), + ); + }, + ), + ) + ], + ); + }, + ), + ), + ); + } + + Widget _buildMonthFilter() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Filter Bulan:", + style: TextStyle(fontSize: 16, color: Colors.grey[700]), + ), + ElevatedButton.icon( + onPressed: () async { + final selected = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2023), + lastDate: DateTime.now(), + helpText: 'Pilih bulan dan tahun', + ); + + if (selected != null) { + setState(() { + _selectedMonth = DateFormat('MMMM yyyy').format(selected); + }); + } + }, + icon: const Icon(Icons.calendar_month), + label: Text(_selectedMonth), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + ), + ], + ); + } + + Widget _buildStats(int totalUser, double totalPemakaian, double totalTagihan, int lunasCount) { + return GridView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisExtent: 100, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + _buildStatCard("Pengguna", totalUser.toString(), Icons.people, Colors.blue), + _buildStatCard("Pemakaian", "${totalPemakaian.toStringAsFixed(1)} m³", Icons.water_drop, Colors.indigo), + _buildStatCard("Total Tagihan", "Rp${totalTagihan.toStringAsFixed(0)}", Icons.attach_money, Colors.green), + _buildStatCard("Lunas", lunasCount.toString(), Icons.check_circle, Colors.teal), + ], + ); + } + + Widget _buildStatCard(String title, String value, IconData icon, Color color) { + return Card( + elevation: 4, + color: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + CircleAvatar( + backgroundColor: color.withOpacity(0.15), + child: Icon(icon, color: color), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(height: 4), + Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/dashboard/dashboard_screen.dart b/lib/screens/dashboard/dashboard_screen.dart new file mode 100644 index 0000000..7844d39 --- /dev/null +++ b/lib/screens/dashboard/dashboard_screen.dart @@ -0,0 +1,521 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import '../../widgets/costum_header.dart'; + +class DashboardScreen extends StatefulWidget { + const DashboardScreen({super.key}); + + @override + State createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + DatabaseReference get _ref { + final uid = FirebaseAuth.instance.currentUser?.uid; + return FirebaseDatabase.instance.ref('monitoring/$uid'); + } + + double _debit = 0.0; + double _volume = 0.0; + bool _isPumpOn = false; + String _selectedRange = 'day'; + List _chartData = []; + + @override + void initState() { + super.initState(); + _listenToRealtimeData(); + _loadChartDataFromFirestore(); + } + + void _listenToRealtimeData() { + _ref.onValue.listen((event) { + final data = event.snapshot.value as Map?; + if (data != null) { + setState(() { + _debit = (data['debit'] ?? 0).toDouble(); + _volume = (data['volume'] ?? 0).toDouble(); + + final pumpRaw = data['pump']; + if (pumpRaw is bool) { + _isPumpOn = pumpRaw; + } else if (pumpRaw is int) { + _isPumpOn = pumpRaw == 1; + } else { + _isPumpOn = false; + } + }); + + // Kirim volume saat ini ke fungsi tagihan + generateMonthlyBillIfNeeded(_volume); + } + }); + } + + Future generateMonthlyBillIfNeeded(double currentVolume) async { + final user = FirebaseAuth.instance.currentUser; + if (user == null) return; + + final uid = user.uid; + final email = user.email ?? ''; + final now = DateTime.now(); + final monthName = "${_getMonthName(now.month)} ${now.year}"; + final docId = "$uid-$monthName"; + + final tagihanRef = + FirebaseFirestore.instance.collection('tagihan').doc(docId); + final tagihanSnap = await tagihanRef.get(); + + if (tagihanSnap.exists) { + print("ℹ️ Tagihan bulan $monthName sudah ada, tidak dibuat ulang."); + return; + } + + // Ambil tarif dari Realtime Database + final tarifRef = FirebaseDatabase.instance.ref("tarif/$uid"); + final tarifSnap = await tarifRef.get(); + if (!tarifSnap.exists) { + print("❌ Tarif belum diatur untuk user $uid"); + return; + } + final rate = int.tryParse(tarifSnap.value.toString()) ?? 0; + + // Ambil volume bulan lalu dari Firestore + final lastVolRef = + FirebaseFirestore.instance.collection('last_volume').doc(uid); + final lastVolSnap = await lastVolRef.get(); + final lastVolume = (lastVolSnap.data()?['volume'] ?? 0).toDouble(); + + // Hitung pemakaian dan tagihan + final pemakaianBulanIni = currentVolume - lastVolume; + final tagihan = (pemakaianBulanIni * rate).toInt(); + + await tagihanRef.set({ + 'bulan': monthName, + 'email': email, + 'pemakaian': pemakaianBulanIni, + 'status': 'Belum Lunas', + 'tagihan': tagihan, + 'uid': uid, + 'timestamp': FieldValue.serverTimestamp(), + }); + + await lastVolRef.set({'volume': currentVolume}); + print("✅ Tagihan bulan $monthName berhasil dibuat"); + } + + String _getMonthName(int month) { + const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + return months[month - 1]; + } + +Future _loadChartDataFromFirestore() async { + final uid = FirebaseAuth.instance.currentUser?.uid; + if (uid == null) return; + + final now = DateTime.now(); + late DateTime startDate; + late DateTime endDate; + + if (_selectedRange == 'day') { + startDate = DateTime(now.year, now.month, now.day); + endDate = startDate.add(Duration(days: 1)); + } else if (_selectedRange == 'week') { + startDate = now.subtract(Duration(days: 6)); + endDate = now.add(Duration(days: 1)); + } else { + startDate = now.subtract(Duration(days: 29)); + endDate = now.add(Duration(days: 1)); + } + + final querySnapshot = await FirebaseFirestore.instance + .collection('history') + .doc(uid) + .collection('data') + .where('timestamp', + isGreaterThanOrEqualTo: Timestamp.fromDate(startDate)) + .where('timestamp', isLessThan: Timestamp.fromDate(endDate)) + .orderBy('timestamp') + .get(); + + final List spots = []; + + for (var doc in querySnapshot.docs) { + final data = doc.data(); + final timestampRaw = data['timestamp']; + final debit = (data['debit'] ?? 0).toDouble(); + + try { + final timestamp = (timestampRaw as Timestamp).toDate(); + double x; + if (_selectedRange == 'day') { + x = timestamp.hour.toDouble(); + } else if (_selectedRange == 'week') { + x = timestamp.weekday.toDouble(); // 1 = Monday + } else { + x = timestamp.day.toDouble(); + } + + spots.add(FlSpot(x, debit)); + } catch (e) { + continue; + } + } + + setState(() { + _chartData = spots; + }); + } + + +void _onRangeChanged(String range) { + setState(() { + _selectedRange = range; + }); + _loadChartDataFromFirestore(); +} + + +void _togglePump(bool value) { + setState(() { + _isPumpOn = value; + }); + + _ref.update({ + 'pump': value, // langsung boolean + }); + } + + + void _goToBillingPage() { + Navigator.pushNamed(context, '/billing'); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.blue[50], + appBar: const CustomHeader( + deviceName: 'SmartFlow', + // notificationCount: 3, + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Sensor Cards + Row( + children: [ + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.speed, + size: 28, + color: Color.fromARGB(255, 107, 139, 255)), + SizedBox(width: 6), + Text( + "Debit Air", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Color.fromARGB(255, 107, 139, 255)), + ), + ], + ), + const SizedBox(height: 12), + _buildSensorCard( + value: _debit, + unit: "L/min", + cardColor: Color.fromARGB(255, 107, 139, 255), + ), + ], + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.water, + size: 28, + color: Color.fromARGB(255, 107, 139, 255)), + SizedBox(width: 6), + Text( + "Volume Air", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Color.fromARGB(255, 107, 139, 255), + ), + ), + ], + ), + const SizedBox(height: 12), + _buildSensorCard( + value: _volume, + unit: "Liter", + cardColor: Color.fromARGB(255, 107, 139, 255), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 24), + + // Pompa dan Tombol Tagihan + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Card( + color: Colors.white, + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 12.0), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Switch( + value: _isPumpOn, + onChanged: _togglePump, + activeColor: + const Color.fromARGB(255, 107, 139, 255), + ), + const SizedBox(width: 8), + const Text( + "Control Pompa", + style: TextStyle( + fontWeight: FontWeight.w600, + color: Color.fromARGB(255, 107, 139, 255), + ), + ), + ], + ), + ), + ElevatedButton.icon( + onPressed: _goToBillingPage, + icon: + const Icon(Icons.receipt_long, color: Colors.white), + label: const Text("Tagihan"), + style: ElevatedButton.styleFrom( + backgroundColor: + const Color.fromARGB(255, 107, 139, 255), + foregroundColor: Colors.white, + ), + ), + ], + ), + ), + ), + ), + + // Tombol Range Filter + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildRangeButton('day', 'Per Hari'), + _buildRangeButton('week', 'Per Minggu'), + _buildRangeButton('month', 'Per Bulan'), + ], + ), + const SizedBox(height: 16), + + // Grafik Debit Air + SizedBox( + height: 280, + child: Card( + color: Colors.white, + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Grafik Debit Air", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color.fromARGB(255, 107, 139, 255))), + const SizedBox(height: 12), + Expanded( + child: LineChart( + LineChartData( + minY: 0, + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (value) => FlLine( + color: Colors.grey[300], + strokeWidth: 1, + ), + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 20, + reservedSize: 32, + getTitlesWidget: (value, meta) => Text( + value.toInt().toString(), + style: const TextStyle( + fontSize: 12, color: Colors.black54), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: _selectedRange == 'day' ? 4 : 1, + getTitlesWidget: (value, meta) => Text( + _selectedRange == 'day' + ? '${value.toInt()}h' + : '${value.toInt() + 1}', + style: const TextStyle( + fontSize: 12, color: Colors.black54), + ), + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false)), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData( + show: true, + border: const Border( + left: BorderSide(color: Colors.black12), + bottom: BorderSide(color: Colors.black12), + ), + ), + lineBarsData: [ + LineChartBarData( + spots: _chartData, + isCurved: true, + color: const Color.fromARGB(255, 107, 139, 255), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 107, 139, 255) + .withOpacity(0.4), + const Color.fromARGB(255, 107, 139, 255) + .withOpacity(0.1), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + barWidth: 3, + dotData: FlDotData(show: false), + ), + ], + + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildRangeButton(String key, String label) { + final isSelected = _selectedRange == key; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: OutlinedButton( + onPressed: () => _onRangeChanged(key), + style: OutlinedButton.styleFrom( + backgroundColor: isSelected + ? const Color.fromARGB(255, 107, 139, 255) + : Colors.white, + foregroundColor: isSelected + ? Colors.white + : const Color.fromARGB(255, 107, 139, 255), + ), + child: Text(label), + ), + ); + } + + Widget _buildSensorCard({ + required double value, + required String unit, + required Color cardColor, + }) { + return Card( + color: cardColor, + elevation: 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: cardColor, + border: Border.all(color: Colors.white, width: 7), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + value.toStringAsFixed(1), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + Text( + unit, + style: const TextStyle( + fontSize: 12, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/history/history_screen.dart b/lib/screens/history/history_screen.dart new file mode 100644 index 0000000..2daa274 --- /dev/null +++ b/lib/screens/history/history_screen.dart @@ -0,0 +1,298 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:printing/printing.dart'; +import '../../widgets/costum_header.dart'; + +class HistoryScreen extends StatefulWidget { + const HistoryScreen({super.key}); + + @override + State createState() => _HistoryScreenState(); +} + +class _HistoryScreenState extends State { + DateTime? _startDate; + DateTime? _endDate; + List _selectedDocIds = []; + + final user = FirebaseAuth.instance.currentUser; + + Future _selectDate({required bool isStart}) async { + final now = DateTime.now(); + final initialDate = isStart ? (_startDate ?? now) : (_endDate ?? now); + final picked = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: DateTime(now.year - 1), + lastDate: now, + ); + if (picked != null) { + setState(() { + if (isStart) { + _startDate = picked; + } else { + _endDate = picked; + } + }); + } + } + + Stream>> _getFilteredHistory() { + final uid = user?.uid; + if (uid == null) return const Stream.empty(); + + final collection = + FirebaseFirestore.instance.collection('history').doc(uid).collection('data'); + + if (_startDate != null && _endDate != null) { + return collection + .where('timestamp', isGreaterThanOrEqualTo: _startDate) + .where('timestamp', + isLessThanOrEqualTo: _endDate!.add(const Duration(days: 1))) + .orderBy('timestamp', descending: true) + .snapshots(); + } + + return collection.orderBy('timestamp', descending: true).snapshots(); + } + + Future _downloadSelected(List>> docs) async { + final pdf = pw.Document(); + + pdf.addPage( + pw.Page( + build: (pw.Context context) => pw.Column( + children: [ + pw.Text("Laporan Riwayat Penggunaan Air", + style: pw.TextStyle(fontSize: 18)), + pw.SizedBox(height: 20), + pw.Table.fromTextArray( + headers: ["Tanggal", "Debit (L/min)", "Volume (L)"], + data: docs.map((doc) { + final data = doc.data(); + final rawTimestamp = data['timestamp']; + DateTime? dateTime; + + if (rawTimestamp is Timestamp) { + dateTime = rawTimestamp.toDate(); + } else if (rawTimestamp is String) { + dateTime = DateTime.tryParse(rawTimestamp); + } + + final dateStr = dateTime != null + ? DateFormat('dd MMM yyyy, HH:mm').format(dateTime) + : '-'; + + return [ + dateStr, + "${data['debit']?.toStringAsFixed(1) ?? '-'}", + "${data['volume']?.toStringAsFixed(1) ?? '-'}", + ]; + }).toList(), + ), + ], + ), + ), + ); + + await Printing.layoutPdf(onLayout: (format) async => pdf.save()); + } + + void _showCustomAlert() { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: const Text( + "Pilih Data Dulu", + style: + TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF6B8BFF)), + ), + content: const Text( + "Silakan pilih minimal satu data untuk diunduh.", + style: TextStyle(fontSize: 16), + ), + actions: [ + TextButton( + style: TextButton.styleFrom( + foregroundColor: const Color(0xFF6B8BFF), + ), + onPressed: () => Navigator.pop(context), + child: const Text("Tutup"), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.blue[50], + appBar: const CustomHeader( + deviceName: "SmartFlow", + // notificationCount: 0, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Filter tanggal & tombol download + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () => _selectDate(isStart: true), + icon: const Icon(Icons.calendar_today), + label: Text( + _startDate == null + ? "Mulai" + : DateFormat('dd MMM yyyy').format(_startDate!), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: () => _selectDate(isStart: false), + icon: const Icon(Icons.calendar_today_outlined), + label: Text( + _endDate == null + ? "Akhir" + : DateFormat('dd MMM yyyy').format(_endDate!), + ), + ), + ), + const SizedBox(width: 12), + ElevatedButton.icon( + onPressed: () async { + if (_selectedDocIds.isEmpty) { + _showCustomAlert(); + return; + } + + final uid = user?.uid; + if (uid == null) return; + + final snapshot = await FirebaseFirestore.instance + .collection('history') + .doc(uid) + .collection('data') + .where(FieldPath.documentId, whereIn: _selectedDocIds) + .get(); + + await _downloadSelected(snapshot.docs); + }, + icon: const Icon(Icons.download), + label: const Text("Download"), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + textStyle: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ), + const SizedBox(height: 16), + + // Tabel data + Expanded( + child: StreamBuilder>>( + stream: _getFilteredHistory(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + + final docs = snapshot.data!.docs; + + if (docs.isEmpty) { + return const Center(child: Text("Tidak ada data.")); + } + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + child: DataTable( + columnSpacing: 32, + headingRowColor: + MaterialStateProperty.all(const Color(0xFF6B8BFF)), + headingTextStyle: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), + columns: const [ + DataColumn( + label: + SizedBox(width: 140, child: Text('Tanggal'))), + DataColumn( + label: + SizedBox(width: 50, child: Text('Debit'))), + DataColumn( + label: + SizedBox(width: 50, child: Text('Volume'))), + ], + rows: docs.map((doc) { + final data = doc.data(); + final id = doc.id; + + final rawTimestamp = data['timestamp']; + DateTime? dateTime; + + if (rawTimestamp is Timestamp) { + dateTime = rawTimestamp.toDate(); + } else if (rawTimestamp is String) { + dateTime = DateTime.tryParse(rawTimestamp); + } + + final dateStr = dateTime != null + ? DateFormat('dd MMM yyyy, HH:mm') + .format(dateTime) + : '-'; + + final debit = + "${data['debit']?.toStringAsFixed(1) ?? '-'}"; + final volume = + "${data['volume']?.toStringAsFixed(1) ?? '-'}"; + + final selected = _selectedDocIds.contains(id); + + return DataRow( + selected: selected, + onSelectChanged: (_) { + setState(() { + if (selected) { + _selectedDocIds.remove(id); + } else { + _selectedDocIds.add(id); + } + }); + }, + cells: [ + DataCell( + SizedBox(width: 140, child: Text(dateStr))), + DataCell( + SizedBox(width: 50, child: Text(debit))), + DataCell( + SizedBox(width: 50, child: Text(volume))), + ], + ); + }).toList(), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/notifications/notifcations_screen.dart b/lib/screens/notifications/notifcations_screen.dart new file mode 100644 index 0000000..a2e3e0a --- /dev/null +++ b/lib/screens/notifications/notifcations_screen.dart @@ -0,0 +1,189 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class NotificationScreen extends StatelessWidget { + const NotificationScreen({super.key}); + + IconData getIcon(String level) { + switch (level) { + case 'warning': + return Icons.warning_amber; + case 'info': + return Icons.info_outline; + case 'success': + return Icons.check_circle; + case 'error': + return Icons.error; + default: + return Icons.notifications; + } + } + + Color getColor(String level) { + switch (level) { + case 'warning': + return Colors.orange; + case 'info': + return Colors.blue; + case 'success': + return Colors.green; + case 'error': + return Colors.redAccent; + default: + return Colors.grey; + } + } + + String formatWaktu(Timestamp? timestamp) { + if (timestamp == null) return ""; + final date = timestamp.toDate(); + return DateFormat("dd MMMM yyyy • HH:mm", "id_ID").format(date); + } + + @override + Widget build(BuildContext context) { + final uid = FirebaseAuth.instance.currentUser?.uid; + + if (uid == null) { + return const Scaffold( + body: Center(child: Text("Anda belum login")), + ); + } + + final messenger = ScaffoldMessenger.of(context); + final notifRef = FirebaseFirestore.instance + .collection('notifikasi') + .doc(uid) + .collection('data') + .orderBy('timestamp', descending: true); + + return Scaffold( + backgroundColor: const Color(0xFFF1F5FF), + appBar: AppBar( + title: const Text("Notifikasi"), + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + elevation: 0, + ), + body: StreamBuilder( + stream: notifRef.snapshots(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + final docs = snapshot.data?.docs ?? []; + + if (docs.isEmpty) { + return const Center( + child: Text("Tidak ada notifikasi."), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: docs.length, + itemBuilder: (context, index) { + final doc = docs[index]; + final data = doc.data() as Map; + final title = data['judul'] ?? 'Notifikasi'; + final message = data['pesan'] ?? ''; + final level = data['level'] ?? 'info'; + final timestamp = data['timestamp'] as Timestamp?; + + return Dismissible( + key: Key(doc.id), + background: Container( + decoration: BoxDecoration( + color: Colors.redAccent, + borderRadius: BorderRadius.circular(16), + ), + alignment: Alignment.centerRight, + padding: const EdgeInsets.symmetric(horizontal: 24), + child: const Icon(Icons.delete, color: Colors.white), + ), + direction: DismissDirection.endToStart, + onDismissed: (_) { + doc.reference.delete().then((_) { + messenger.showSnackBar( + const SnackBar(content: Text('Notifikasi dihapus')), + ); + }).catchError((e) { + messenger.showSnackBar( + SnackBar(content: Text('Gagal menghapus: $e')), + ); + }); + }, + child: Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 6, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: getColor(level).withOpacity(0.12), + shape: BoxShape.circle, + ), + child: Icon( + getIcon(level), + color: getColor(level), + size: 28, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFF1D2939), + ), + ), + const SizedBox(height: 4), + Text( + message, + style: const TextStyle( + fontSize: 14, + color: Color(0xFF475467), + ), + ), + const SizedBox(height: 4), + Text( + formatWaktu(timestamp), + style: const TextStyle( + fontSize: 12, + color: Color(0xFF98A2B3), + ), + ), + ], + ), + ) + ], + ), + ), + ); + }, + ); + }, + ), + ); + } +} diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart new file mode 100644 index 0000000..424f454 --- /dev/null +++ b/lib/screens/settings/settings_screen.dart @@ -0,0 +1,336 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter/material.dart'; +import '../../widgets/costum_header.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + final DatabaseReference _dbKalibrasi = + FirebaseDatabase.instance.ref('kalibrasi'); + double? _calibrationValue; + int? _tarifValue; + String? uid; + + @override + void initState() { + super.initState(); + uid = FirebaseAuth.instance.currentUser?.uid; + _fetchCalibration(); + _fetchTarif(); + } + + Future _fetchCalibration() async { + final snapshot = await _dbKalibrasi.get(); + if (snapshot.exists) { + setState(() { + _calibrationValue = double.tryParse(snapshot.value.toString()); + }); + } + } + + Future _fetchTarif() async { + if (uid == null) return; + final snapshot = + await FirebaseDatabase.instance.ref('tarif/$uid').get(); + if (snapshot.exists) { + setState(() { + _tarifValue = int.tryParse(snapshot.value.toString()); + }); + } + } + + Future _showCalibrationDialog() async { + final controller = TextEditingController( + text: _calibrationValue?.toString() ?? '', + ); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text( + "Edit Nilai Kalibrasi", + style: + TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF6B8BFF)), + ), + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + TextField( + controller: controller, + keyboardType: + const TextInputType.numberWithOptions(decimal: true), + decoration: const InputDecoration( + labelText: "Masukkan nilai kalibrasi (misal: 4.5)", + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Batal", style: TextStyle(color: Colors.grey)), + ), + ElevatedButton( + onPressed: () async { + final value = double.tryParse(controller.text); + if (value == null) { + Navigator.pop(context); + _showMessage("Input tidak valid!", isError: true); + return; + } + setState(() => _calibrationValue = value); + await _dbKalibrasi.set(value); + Navigator.pop(context); + _showMessage("Kalibrasi berhasil disimpan!", isError: false); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + ), + child: const Text("Simpan"), + ), + ], + ), + ); + } + + Future _showTarifDialog() async { + final controller = TextEditingController( + text: _tarifValue?.toString() ?? '', + ); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text( + "Edit Tarif Air (Rp/liter)", + style: + TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF6B8BFF)), + ), + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + TextField( + controller: controller, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: "Masukkan tarif air (misal: 5)", + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Batal", style: TextStyle(color: Colors.grey)), + ), + ElevatedButton( + onPressed: () async { + final value = int.tryParse(controller.text); + if (value == null || uid == null) { + Navigator.pop(context); + _showMessage("Input tidak valid!", isError: true); + return; + } + setState(() => _tarifValue = value); + await FirebaseDatabase.instance.ref('tarif/$uid').set(value); + Navigator.pop(context); + _showMessage("Tarif berhasil disimpan!", isError: false); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + ), + child: const Text("Simpan"), + ), + ], + ), + ); + } + + void _logout() async { + await FirebaseAuth.instance.signOut(); + if (mounted) { + Navigator.pushReplacementNamed(context, '/login'); + } + } + + void _showMessage(String message, {required bool isError}) { + showDialog( + context: context, + builder: (_) => AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + contentPadding: const EdgeInsets.all(24), + titlePadding: const EdgeInsets.only(top: 24), + title: Column( + children: [ + Icon( + isError ? Icons.error_outline : Icons.check_circle_outline, + color: isError ? Colors.redAccent : const Color(0xFF6B8BFF), + size: 48, + ), + const SizedBox(height: 16), + Text( + isError ? "Gagal" : "Berhasil", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: isError ? Colors.redAccent : const Color(0xFF6B8BFF), + ), + textAlign: TextAlign.center, + ), + ], + ), + content: Text( + message, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text( + "OK", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF6B8BFF), + ), + ), + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.blue[50], + appBar: const CustomHeader( + deviceName: "SmartFlow", + // notificationCount: 8, + ), + body: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + // Kalibrasi + Card( + color: Colors.white, + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + child: ListTile( + contentPadding: const EdgeInsets.all(16), + title: const Text( + "Kalibrasi Sensor", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Color(0xFF6B8BFF), + ), + ), + subtitle: Text( + _calibrationValue != null + ? "${_calibrationValue!.toStringAsFixed(2)} (L/pulse)" + : "Belum ada data", + style: const TextStyle(fontSize: 14), + ), + trailing: ElevatedButton.icon( + onPressed: _showCalibrationDialog, + icon: const Icon(Icons.edit), + label: const Text("Edit"), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 10), + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Tarif + Card( + color: Colors.white, + elevation: 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + child: ListTile( + contentPadding: const EdgeInsets.all(16), + title: const Text( + "Tarif Air", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Color(0xFF6B8BFF), + ), + ), + subtitle: Text( + _tarifValue != null + ? "Rp$_tarifValue / liter" + : "Belum ada data", + style: const TextStyle(fontSize: 14), + ), + trailing: ElevatedButton.icon( + onPressed: _showTarifDialog, + icon: const Icon(Icons.edit), + label: const Text("Edit"), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6B8BFF), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 10), + ), + ), + ), + ), + + const SizedBox(height: 32), + + // Logout + ElevatedButton.icon( + onPressed: _logout, + icon: const Icon(Icons.logout), + label: const Text("Logout"), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.redAccent, + foregroundColor: Colors.white, + minimumSize: const Size.fromHeight(50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart new file mode 100644 index 0000000..84ff408 --- /dev/null +++ b/lib/screens/splash_screen.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({super.key}); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + @override + void initState() { + super.initState(); + _checkAuthStatus(); + } + +Future _checkAuthStatus() async { + await Future.delayed(const Duration(seconds: 2)); + final user = FirebaseAuth.instance.currentUser; + + try { + if (user != null) { + // Paksa refresh untuk validasi user masih ada di server + await user.reload(); + final refreshedUser = FirebaseAuth.instance.currentUser; + + if (refreshedUser == null) { + // User sudah dihapus dari server + Navigator.pushReplacementNamed(context, '/login'); + } else { + Navigator.pushReplacementNamed(context, '/home'); + } + } else { + Navigator.pushReplacementNamed(context, '/login'); + } + } catch (e) { + // Jika reload gagal (misalnya user sudah tidak ada) + await FirebaseAuth.instance.signOut(); + Navigator.pushReplacementNamed(context, '/login'); + } +} + + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 107, 139, 255), + Color.fromARGB(255, 139, 164, 255) + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + width: double.infinity, + child: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.water_drop_rounded, size: 100, color: Colors.white), + SizedBox(height: 24), + Text( + 'Monitoring Debit Air', + style: TextStyle( + fontSize: 26, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ], + ), + ), + ); + } +} diff --git a/lib/services/auth_services.dart b/lib/services/auth_services.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/services/billing_services.dart b/lib/services/billing_services.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/services/notification_services.dart b/lib/services/notification_services.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/services/sensor_services.dart b/lib/services/sensor_services.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/widgets/bottom_bar.dart b/lib/widgets/bottom_bar.dart new file mode 100644 index 0000000..879756e --- /dev/null +++ b/lib/widgets/bottom_bar.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:smartflow/screens/settings/settings_screen.dart'; +import '../../screens/dashboard/dashboard_screen.dart'; +import '../../screens/history/history_screen.dart'; + +class CustomBottomNav extends StatefulWidget { + const CustomBottomNav({super.key}); + + @override + State createState() => _CustomBottomNavState(); +} + +class _CustomBottomNavState extends State { + int _currentIndex = 1; // Dashboard di tengah, index = 1 + + final List _screens = const [ + HistoryScreen(), // index 0 (kiri) + DashboardScreen(), // index 1 (tengah - FAB) + SettingsScreen(), // index 2 (kanan) + ]; + + void _onTap(int index) { + setState(() { + _currentIndex = index; + }); + } + + void _onMiddleTap() { + _onTap(1); // Dashboard + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[100], + extendBody: true, + resizeToAvoidBottomInset: + false, // <--- Fix FAB ikut naik saat keyboard muncul + body: _screens[_currentIndex], + floatingActionButton: FloatingActionButton( + onPressed: _onMiddleTap, + backgroundColor: const Color.fromARGB(255, 107, 139, 255), + elevation: 8, + shape: const CircleBorder(), + child: Icon( + _currentIndex == 1 ? Icons.dashboard : Icons.dashboard_outlined, + size: 30, + color: Colors.white, + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + bottomNavigationBar: BottomAppBar( + shape: const CircularNotchedRectangle(), + notchMargin: 10, + elevation: 10, + color: Colors.white, + child: SizedBox( + height: 70, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildNavItem( + Icons.history_outlined, + Icons.history, + "History", + 0, + ), + const SizedBox(width: 48), // Spacer untuk FAB + _buildNavItem( + Icons.settings_outlined, + Icons.settings, + "Setting", + 2, + ), + ], + ), + ), + ), + ); + } + + Widget _buildNavItem(IconData icon, IconData activeIcon, String label, int index) { + final isSelected = _currentIndex == index; + + return GestureDetector( + onTap: () => _onTap(index), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + isSelected ? activeIcon : icon, + color: isSelected + ? const Color.fromARGB(255, 107, 139, 255) + : Colors.grey, + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 12, + color: isSelected + ? const Color.fromARGB(255, 107, 139, 255) + : Colors.grey, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/costum_button.dart b/lib/widgets/costum_button.dart new file mode 100644 index 0000000..d1bb6ba --- /dev/null +++ b/lib/widgets/costum_button.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import '../../screens/dashboard/dashboard_screen.dart'; +import '../../screens/history/history_screen.dart'; +import '../../screens/billing/billing_screen.dart'; + +class CustomBottomNav extends StatefulWidget { + const CustomBottomNav({super.key}); + + @override + State createState() => _CustomBottomNavState(); +} + +class _CustomBottomNavState extends State { + int _currentIndex = 0; + + final List _screens = const [ + DashboardScreen(), + HistoryScreen(), + BillingScreen(), + ]; + + void _onTap(int index) { + setState(() { + _currentIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: _screens[_currentIndex], + bottomNavigationBar: BottomNavigationBar( + currentIndex: _currentIndex, + onTap: _onTap, + selectedItemColor: Colors.blueAccent, + unselectedItemColor: Colors.grey, + backgroundColor: Colors.white, + type: BottomNavigationBarType.fixed, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.dashboard), + label: 'Dashboard', + ), + BottomNavigationBarItem( + icon: Icon(Icons.history), + label: 'Riwayat', + ), + BottomNavigationBarItem( + icon: Icon(Icons.receipt_long), + label: 'Tagihan', + ), + ], + ), + ); + } +} diff --git a/lib/widgets/costum_header.dart b/lib/widgets/costum_header.dart new file mode 100644 index 0000000..d4e00f2 --- /dev/null +++ b/lib/widgets/costum_header.dart @@ -0,0 +1,116 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class CustomHeader extends StatelessWidget implements PreferredSizeWidget { + final String deviceName; + + const CustomHeader({ + super.key, + required this.deviceName, + }); + + @override + Widget build(BuildContext context) { + final String today = + DateFormat("d MMMM yyyy", 'id_ID').format(DateTime.now()); + + final user = FirebaseAuth.instance.currentUser; + final uid = user?.uid; + + return AppBar( + backgroundColor: const Color(0xFF6B8BFF), + elevation: 0, + automaticallyImplyLeading: false, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + deviceName, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 2), + Text( + today, + style: const TextStyle( + fontSize: 14, + color: Colors.white70, + ), + ), + ], + ), + actions: [ + if (uid != null) + StreamBuilder( + stream: FirebaseFirestore.instance + .collection("notifikasi") + .doc(uid) + .collection("data") + .snapshots(), + builder: (context, snapshot) { + final notifCount = snapshot.data?.docs.length ?? 0; + + return Padding( + padding: const EdgeInsets.only(right: 16), + child: GestureDetector( + onTap: () { + Navigator.pushNamed(context, '/notifications'); + }, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 44, + height: 44, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white24, + ), + child: const Icon( + Icons.notifications, + color: Colors.white, + size: 26, + ), + ), + if (notifCount > 0) + Positioned( + right: -2, + top: -2, + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + constraints: + const BoxConstraints(minWidth: 20, minHeight: 20), + child: Center( + child: Text( + notifCount > 99 ? '99+' : '$notifCount', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ), + ], + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight + 4); +} diff --git a/lib/widgets/sensor_card.dart b/lib/widgets/sensor_card.dart new file mode 100644 index 0000000..e69de29 diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..f469919 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,626 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "214e6f07e2a44f45972e0365c7b537eaeaddb4598db0778dd4ac64b4acd3f5b1" + url: "https://pub.dev" + source: hosted + version: "1.3.55" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + barcode: + dependency: transitive + description: + name: barcode + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + bidi: + dependency: transitive + description: + name: bidi + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" + url: "https://pub.dev" + source: hosted + version: "2.0.13" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: d25c956be5261c14bc9a69c9662de8addb308376b4b53a64469aade52e7b02f8 + url: "https://pub.dev" + source: hosted + version: "5.6.8" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: ee2b8f8c602ede36073afd3741e99cfea9dd982b4a44833daf665134d151c32a + url: "https://pub.dev" + source: hosted + version: "6.6.8" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: b99bc4f1f70787f694b73bc6fce238d4d6cc822c9b31ba8ef1578b180b6f77bc + url: "https://pub.dev" + source: hosted + version: "4.4.8" + collection: + dependency: transitive + description: + name: collection + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" + source: hosted + version: "1.19.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "10cd3f00a247f33b0a5c77574011a87379432bf3fec77a500b55f2bcc30ddd8b" + url: "https://pub.dev" + source: hosted + version: "5.5.4" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "2d15872a8899b0459fab6b4c148fd142e135acfc8a303d383d80b455e4dba7bd" + url: "https://pub.dev" + source: hosted + version: "7.6.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: efba45393050ca03d992eae1d305d5fc8c0c9f5980624053512e935c23767c4f + url: "https://pub.dev" + source: hosted + version: "5.14.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "8cfe3c900512399ce8d50fcc817e5758ff8615eeb6fa5c846a4cc47bbf6353b6" + url: "https://pub.dev" + source: hosted + version: "3.13.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf + url: "https://pub.dev" + source: hosted + version: "5.4.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e + url: "https://pub.dev" + source: hosted + version: "2.23.0" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + sha256: e98c32b9462779f4c45eb036dcaf22cd6a17204e80f77c292bd1d05b08c8ef81 + url: "https://pub.dev" + source: hosted + version: "11.3.6" + firebase_database_platform_interface: + dependency: transitive + description: + name: firebase_database_platform_interface + sha256: "3e81c5e2152ad996309458f8338977989d81a44a692e27180db9380f6faffafb" + url: "https://pub.dev" + source: hosted + version: "0.2.6+6" + firebase_database_web: + dependency: transitive + description: + name: firebase_database_web + sha256: "0289c1c7832b3ca79520517c54bf42a40b9bfefa8198906145ab17e75989d80f" + url: "https://pub.dev" + source: hosted + version: "0.2.6+12" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + url: "https://pub.dev" + source: hosted + version: "10.0.7" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + url: "https://pub.dev" + source: hosted + version: "3.0.8" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + url: "https://pub.dev" + source: hosted + version: "3.11.3" + pdf_widget_wrapper: + dependency: transitive + description: + name: pdf_widget_wrapper + sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + printing: + dependency: "direct main" + description: + name: printing + sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93" + url: "https://pub.dev" + source: hosted + version: "5.14.2" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + url: "https://pub.dev" + source: hosted + version: "0.7.3" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + url: "https://pub.dev" + source: hosted + version: "14.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" +sdks: + dart: ">=3.6.2 <4.0.0" + flutter: ">=3.27.4" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..4487863 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,99 @@ +name: smartflow +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.6.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + firebase_core: ^3.13.1 + firebase_auth: ^5.5.4 + firebase_database: ^11.3.6 + fl_chart: ^1.0.0 + intl: ^0.20.2 + cloud_firestore: ^5.6.8 + pdf: ^3.11.3 + printing: ^5.14.2 + path_provider: ^2.1.5 + shared_preferences: ^2.5.3 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..3126aae --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + smartflow + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..1f9d3c5 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "smartflow", + "short_name": "smartflow", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}