Initial commit project Flutter dengan .gitignore

This commit is contained in:
fathurrizqiG 2025-09-08 13:48:58 +07:00
parent 5054ce4e1d
commit efa803298f
165 changed files with 7363 additions and 1 deletions

48
.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
# Flutter/Dart related
.dart_tool/
.packages
.flutter-plugins
.flutter-plugins-dependencies
.metadata
# Build output
/build/
**/build/
# iOS
ios/Flutter/Flutter.framework
ios/Flutter/Flutter.podspec
ios/Pods/
ios/.symlinks/
ios/Flutter/Generated.xcconfig
ios/Flutter/ephemeral/
# Android
android/.gradle/
android/local.properties
android/**/GeneratedPluginRegistrant.java
android/key.properties
# Web
web/.dart_tool/
# Windows
windows/flutter/ephemeral/
# macOS
macos/Flutter/ephemeral/
# Linux
linux/flutter/ephemeral/
# IDE / Editor
.idea/
.vscode/
*.iml
# Logs
*.log
# System files
.DS_Store
Thumbs.db

View File

@ -1,2 +1,11 @@
# codeprogramecomonitor
kode program dari alat ecomonitor
Kode program dari alat **EcoMonitor** untuk monitoring kondisi lingkungan.
## 📌 Deskripsi
EcoMonitor adalah sistem monitoring berbasis IoT yang menggunakan ESP32 dan sensor (DHT22, LDR, sensor hujan, dsb).
Data dikirim ke Firebase lalu ditampilkan dalam aplikasi Flutter dan website monitoring.
## 🚀 Cara Menjalankan Project Flutter
1. Clone repository ini:
```bash
git clone https://github.com/fathurrizqiG/codeprogramecomonitor.git

28
analysis_options.yaml Normal file
View File

@ -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

13
android/.gitignore vendored Normal file
View File

@ -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

46
android/app/build.gradle Normal file
View File

@ -0,0 +1,46 @@
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
id "dev.flutter.flutter-gradle-plugin"
id "com.google.gms.google-services"
}
android {
namespace "com.example.ecomoni"
compileSdk 35
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
defaultConfig {
applicationId "com.example.ecomoni"
minSdk 23
targetSdk 35
versionCode flutter.versionCode
versionName flutter.versionName
}
buildTypes {
release {
signingConfig signingConfigs.debug
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
}
flutter {
source "../.."
}
dependencies {
// firebase dependencies otomatis dari Flutter
}

View File

@ -0,0 +1,30 @@
{
"project_info": {
"project_number": "756703024037",
"firebase_url": "https://ecomoni-dc5fd-default-rtdb.firebaseio.com",
"project_id": "ecomoni-dc5fd",
"storage_bucket": "ecomoni-dc5fd.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:756703024037:android:eff05c828502af5e674e4c",
"android_client_info": {
"package_name": "com.example.ecomoni"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDRLGhMZcthLyDFqJb1xkFoFQBnFQ0JPzs"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="EcoMonitor"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package com.example.ecomoni
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

31
android/build.gradle Normal file
View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '2.0.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.15'
}
}
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
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -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-7.6.3-all.zip

25
android/settings.gradle Normal file
View File

@ -0,0 +1,25 @@
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 "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
}
include ":app"

3
assets/config.json Normal file
View File

@ -0,0 +1,3 @@
{
"apiKey": "6630cac090c9ecd2b317dff5a092cb80"
}

34
ios/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -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 = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* 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 = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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 = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* 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.ecomoni;
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.ecomoni.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.ecomoni.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.ecomoni.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.ecomoni;
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.ecomoni;
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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -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)
}
}

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -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.

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

49
ios/Runner/Info.plist Normal file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Ecomoni</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ecomoni</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -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.
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/cuaca_controller.dart';
class CuacaBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<CuacaController>(
() => CuacaController(),
);
}
}

View File

@ -0,0 +1,115 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../../../routes/app_pages.dart';
class CuacaController extends GetxController {
var hourlyForecast = <Map<String, String>>[].obs;
var isLoading = true.obs;
var kota = "Jember, Jawa Timur".obs;
late String apiKey; // akan diisi dari config
var historyList = <Map<String, dynamic>>[].obs;
@override
void onInit() {
super.onInit();
loadConfigAndFetch();
listenToHistory();
}
Future<void> loadConfigAndFetch() async {
try {
final configString = await rootBundle.loadString('assets/config.json');
final configData = json.decode(configString);
apiKey = configData['apiKey'] ?? '';
} catch (_) {
apiKey = '';
}
await fetchForecast();
}
Future<void> fetchForecast() async {
if (apiKey.isEmpty) {
// Jika apiKey kosong, hentikan dan beri pesan biasa
Get.snackbar("Gagal", "API key tidak tersedia");
isLoading(false);
return;
}
try {
isLoading(true);
final response = await http.get(Uri.parse(
"https://api.openweathermap.org/data/2.5/forecast?q=Jember&appid=$apiKey&units=metric",
));
if (response.statusCode == 200) {
final data = json.decode(response.body);
final list = (data['list'] as List).take(6).toList();
hourlyForecast.value = list.map<Map<String, String>>((f) {
final dt = DateTime.parse(f['dt_txt']);
final time = "${dt.hour.toString().padLeft(2,'0')}:00";
final temp = "${f['main']['temp'].round()}°C";
final iconCode = f['weather'][0]['icon'];
final iconEmoji = _mapIconCodeToEmoji(iconCode);
return {
'time': time,
'temp': temp,
'icon': iconEmoji,
};
}).toList();
} else {
// Gagal ambil data, tapi jangan tampilkan error detail
Get.snackbar("Gagal", "Tidak dapat mengambil prakiraan cuaca");
}
} catch (_) {
// Tangkap semua error tapi jangan tampilkan pesan teknis
Get.snackbar("Gagal", "Terjadi kesalahan saat mengambil data");
} finally {
isLoading(false);
}
}
void listenToHistory() {
FirebaseFirestore.instance
.collection('history')
.orderBy('timestamp', descending: true)
.limit(3)
.snapshots()
.listen((snapshot) {
historyList.value = snapshot.docs.map((doc) {
final data = doc.data();
return {
'intensitas_cahaya': data['intensitas_cahaya'],
'kelembaban': data['kelembaban'],
'sensor_hujan': data['sensor_hujan'],
'suhu': data['suhu'],
'timestamp': data['timestamp'],
};
}).toList();
}, onError: (e) {
// Jangan tampilkan error ke UI, cukup log ke console
print("Gagal mendengarkan data history: $e");
});
}
String _mapIconCodeToEmoji(String code) {
if (code.startsWith('01')) return '☀️';
if (code.startsWith('02')) return '🌤️';
if (code.startsWith('03') || code.startsWith('04')) return '☁️';
if (code.startsWith('09') || code.startsWith('10')) return '🌧️';
if (code.startsWith('11')) return '⛈️';
if (code.startsWith('13')) return '❄️';
if (code.startsWith('50')) return '🌫️';
return '';
}
void keSetting() => Get.toNamed(Routes.SETTING);
void keRiwayat() => Get.toNamed(Routes.HISTORY);
}

View File

@ -0,0 +1,219 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
// import 'package:cloud_firestore/cloud_firestore.dart';
import '../controllers/cuaca_controller.dart';
class CuacaView extends GetView<CuacaController> {
const CuacaView({super.key});
@override
Widget build(BuildContext context) {
final now = DateTime.now();
final formattedDate = DateFormat("EEEE, d MMMM yyyy", "id_ID").format(now);
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// === HEADER ===
Container(
width: size.width,
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius:
BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Obx(() => Text(
controller.kota.value,
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)),
const SizedBox(height: 4),
Text(
formattedDate,
style: GoogleFonts.poppins(
color: Colors.white70,
fontSize: 13,
),
),
],
),
),
IconButton(
icon: const Icon(Icons.settings, color: Colors.white),
onPressed: controller.keSetting,
)
],
),
),
const SizedBox(height: 20),
// === PRAKIRAAN CUACA ===
Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
if (controller.hourlyForecast.isEmpty) {
return const Padding(
padding: EdgeInsets.all(20),
child: Text("Prakiraan cuaca tidak tersedia"),
);
}
return Container(
color: const Color(0xFF7AD6F0),
padding: const EdgeInsets.symmetric(vertical: 12),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
child: Row(
children: controller.hourlyForecast.map((f) {
return Container(
width: 70,
margin: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
f['time'] ?? '',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
const SizedBox(height: 4),
Text(
f['icon'] ?? '',
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 4),
Text(
f['temp'] ?? '',
style: const TextStyle(color: Colors.white),
),
],
),
);
}).toList(),
),
),
);
}),
const SizedBox(height: 20),
// === HISTORY HEADER ===
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'History Data',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
GestureDetector(
onTap: controller.keRiwayat,
child: Text(
'Selengkapnya',
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
),
),
],
),
),
const SizedBox(height: 10),
// === HISTORY LIST dari Firestore ===
Obx(() {
final history = controller.historyList;
if (history.isEmpty) {
return const Padding(
padding: EdgeInsets.all(20),
child: Text("Belum ada data histori."),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: history.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) {
final item = history[index];
final timestamp = item['timestamp'];
final dateFormatted = timestamp != null
? DateFormat('dd MMM yyyy HH:mm')
.format(timestamp.toDate())
: '-';
final hujanText =
(item['sensor_hujan'] ?? false) ? "Hujan" : "Tidak Hujan";
final details =
"Suhu: ${item['suhu']}°C, Kelembaban: ${item['kelembaban']}%, IC: ${item['intensitas_cahaya']} Lux, $hujanText";
return ListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Kondisi Lingkungan'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(details),
const SizedBox(height: 2),
Text(
dateFormatted,
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
),
],
),
trailing: const Icon(Icons.chevron_right),
);
},
);
}),
const SizedBox(height: 20),
],
),
),
),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/grafik_controller.dart';
class GrafikBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<GrafikController>(
() => GrafikController(),
);
}
}

View File

@ -0,0 +1,36 @@
import 'package:get/get.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class GrafikController extends GetxController {
var chartData = <Map<String, dynamic>>[].obs;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@override
void onInit() {
super.onInit();
_listenToDailyData();
}
// Dengarkan data real-time dari Firestore
void _listenToDailyData() {
_firestore
.collection('history')
.orderBy('timestamp', descending: true)
.limit(30) // ambil 30 data terakhir
.snapshots()
.listen((snapshot) {
chartData.value = snapshot.docs.map((doc) {
var data = doc.data();
return {
'suhu': (data['suhu'] ?? 0.0).toDouble(),
'kelembaban': (data['kelembaban'] ?? 0.0).toDouble(),
'intensitas_cahaya': (data['intensitas_cahaya'] ?? 0.0).toDouble(),
'hujan': (data['hujan'] == 'Hujan') ? 1.0 : 0.0,
'timestamp': data['timestamp'] is Timestamp
? (data['timestamp'] as Timestamp).toDate()
: null,
};
}).toList().reversed.toList(); // dibalik biar data terlama ke terbaru
});
}
}

View File

@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:fl_chart/fl_chart.dart';
import '../controllers/grafik_controller.dart';
class GrafikView extends GetView<GrafikController> {
const GrafikView({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: ListView(
children: [
// Header
Container(
width: size.width,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Get.back(),
),
Text(
'Grafik Harian',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 48), // biar rata tengah
],
),
),
const SizedBox(height: 16),
Obx(() {
if (controller.chartData.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
List<FlSpot> suhuSpots = [];
List<FlSpot> kelembabanSpots = [];
List<FlSpot> intensitasCahayaSpots = [];
List<FlSpot> hujanSpots = [];
for (int i = 0; i < controller.chartData.length; i++) {
suhuSpots.add(FlSpot(i.toDouble(), controller.chartData[i]['suhu'] ?? 0.0));
kelembabanSpots.add(FlSpot(i.toDouble(), controller.chartData[i]['kelembaban'] ?? 0.0));
intensitasCahayaSpots.add(FlSpot(i.toDouble(), controller.chartData[i]['intensitas_cahaya'] ?? 0.0));
hujanSpots.add(FlSpot(i.toDouble(), controller.chartData[i]['hujan'] ?? 0.0));
}
Widget buildChart(String title, List<FlSpot> spots, Color color,
{double? minY, double? maxY, bool isCurved = true, bool isHujan = false}) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
title,
style: GoogleFonts.poppins(fontSize: 18, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 8),
SizedBox(
height: 300,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: LineChart(
LineChartData(
minY: minY ?? 0,
maxY: maxY,
gridData: FlGridData(show: true, drawVerticalLine: true),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: isHujan ? 1 : 20,
getTitlesWidget: (value, meta) {
if (isHujan) {
if (value == 0) return const Text("Tidak Hujan", style: TextStyle(fontSize: 8));
if (value == 1) return const Text("Hujan", style: TextStyle(fontSize: 8));
}
return Text(value.toInt().toString(), style: const TextStyle(fontSize: 10));
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: (value, meta) {
if (value.toInt() >= 0 && value.toInt() < controller.chartData.length) {
var timestamp = controller.chartData[value.toInt()]['timestamp'];
if (timestamp != null) {
String hour = timestamp.hour.toString().padLeft(2, '0');
String minute = timestamp.minute.toString().padLeft(2, '0');
return Text("$hour:$minute", style: const TextStyle(fontSize: 10));
}
}
return const Text("");
},
),
),
),
borderData: FlBorderData(
show: true,
border: const Border(
left: BorderSide(color: Colors.black),
bottom: BorderSide(color: Colors.black),
),
),
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: isCurved,
color: color,
barWidth: 2,
dotData: FlDotData(show: !isHujan ? false : true),
belowBarData: BarAreaData(show: true, color: color.withOpacity(0.15)),
),
],
),
),
),
),
const SizedBox(height: 16),
],
);
}
return Column(
children: [
buildChart("Grafik Suhu", suhuSpots, Colors.blue),
buildChart("Grafik Kelembaban", kelembabanSpots, Colors.green),
buildChart("Grafik Intensitas Cahaya", intensitasCahayaSpots, Colors.orange),
buildChart("Grafik Hujan", hujanSpots, Colors.purple, minY: 0, maxY: 1, isCurved: false, isHujan: true),
],
);
}),
const SizedBox(height: 20),
],
),
),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/history_controller.dart';
class HistoryBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<HistoryController>(
() => HistoryController(),
);
}
}

View File

@ -0,0 +1,111 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
class HistoryController extends GetxController {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final RxList<DocumentSnapshot> historyDocs = <DocumentSnapshot>[].obs;
final int limit = 10;
DocumentSnapshot? lastDocument;
DocumentSnapshot? firstDocument;
bool hasMore = true;
bool isLoading = false;
int currentPage = 1;
@override
void onInit() {
super.onInit();
fetchData(isInitial: true);
}
void fetchData({bool isNext = true, bool isInitial = false}) async {
if (isLoading) return;
isLoading = true;
Query query = _firestore
.collection('history')
.orderBy('timestamp', descending: true)
.limit(limit);
if (isNext && lastDocument != null && !isInitial) {
query = query.startAfterDocument(lastDocument!);
} else if (!isNext && firstDocument != null) {
query = query.endBeforeDocument(firstDocument!).limitToLast(limit);
}
final snapshot = await query.get();
if (snapshot.docs.isNotEmpty) {
if (isNext && !isInitial) currentPage++;
if (!isNext && currentPage > 1) currentPage--;
historyDocs.value = snapshot.docs;
firstDocument = snapshot.docs.first;
lastDocument = snapshot.docs.last;
hasMore = snapshot.docs.length == limit;
}
isLoading = false;
}
void nextPage() => fetchData(isNext: true);
void previousPage() => fetchData(isNext: false);
String formatTimestamp(Timestamp? timestamp) {
if (timestamp == null) return '-';
final date = timestamp.toDate();
final day = date.day.toString().padLeft(2, '0');
final month = date.month.toString().padLeft(2, '0');
final year = date.year;
final hour = date.hour.toString().padLeft(2, '0');
final minute = date.minute.toString().padLeft(2, '0');
return "$day/$month/$year $hour:$minute";
}
String formatSensorHujan(dynamic value) {
if (value == true) return 'Hujan';
if (value == false) return 'Tidak Hujan';
return '-';
}
Future<void> downloadPDF() async {
final pdf = pw.Document();
pdf.addPage(
pw.MultiPage(
build: (context) => [
pw.Text('Data History', style: pw.TextStyle(fontSize: 24)),
pw.SizedBox(height: 20),
pw.Table.fromTextArray(
headers: ['Timestamp', 'Suhu (°C)', 'Kelembaban (%)', 'Intensitas', 'Sensor Hujan'],
data: historyDocs.map((doc) {
final data = doc.data() as Map<String, dynamic>;
return [
formatTimestamp(data['timestamp']),
data['suhu']?.toString() ?? '-',
data['kelembaban']?.toString() ?? '-',
data['intensitas_cahaya']?.toString() ?? '-',
formatSensorHujan(data['sensor_hujan']),
];
}).toList(),
),
],
),
);
await Printing.layoutPdf(onLayout: (PdfPageFormat format) async => pdf.save());
}
Future<void> deleteAllData() async {
final snapshots = await _firestore.collection('history').get();
for (final doc in snapshots.docs) {
await doc.reference.delete();
}
fetchData(isInitial: true);
Get.snackbar("Berhasil", "Semua data berhasil dihapus.");
}
}

View File

@ -0,0 +1,249 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/history_controller.dart';
class HistoryView extends GetView<HistoryController> {
const HistoryView({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
Container(
width: size.width,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Stack(
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.centerLeft,
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Get.back(),
),
),
Center(
child: Text(
'History Data',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Align(
alignment: Alignment.centerRight,
child: Theme(
data: Theme.of(context).copyWith(
popupMenuTheme: PopupMenuThemeData(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
textStyle: GoogleFonts.poppins(
color: Colors.black87,
fontSize: 14,
),
elevation: 10,
),
),
child: PopupMenuButton<String>(
icon: const Icon(Icons.more_vert, color: Colors.white),
onSelected: (value) {
if (value == 'download') {
controller.downloadPDF();
} else if (value == 'delete') {
controller.deleteAllData();
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'download',
child: Row(
children: const [
Icon(Icons.download_rounded, color: Colors.blueAccent),
SizedBox(width: 10),
Text('Download PDF'),
],
),
),
PopupMenuItem(
value: 'delete',
child: Row(
children: const [
Icon(Icons.delete_forever_rounded, color: Colors.redAccent),
SizedBox(width: 10),
Text('Hapus Semua Data'),
],
),
),
],
),
),
),
],
),
),
const SizedBox(height: 16),
// Area scrollable seluruh isi
Expanded(
child: Obx(() {
final docs = controller.historyDocs;
if (controller.isLoading && docs.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (docs.isEmpty) {
return const Center(child: Text('Tidak ada data.'));
}
return SingleChildScrollView(
child: Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: 800,
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: Colors.grey[200],
child: Row(
children: [
_headerCell('Timestamp', 180),
_headerCell('Suhu (°C)', 100),
_headerCell('Kelembaban (%)', 130),
_headerCell('Intensitas (lux)', 120),
_headerCell('Sensor Hujan', 130),
],
),
),
...docs.asMap().entries.map((entry) {
final index = entry.key;
final data = entry.value.data() as Map<String, dynamic>;
return Container(
color: index % 2 == 0
? Colors.grey.shade100
: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
_dataCell(controller.formatTimestamp(data['timestamp']), 180),
_dataCell('${data['suhu'] ?? '-'}', 100),
_dataCell('${data['kelembaban'] ?? '-'}', 130),
_dataCell('${data['intensitas_cahaya'] ?? '-'}', 120),
_dataCell(controller.formatSensorHujan(data['sensor_hujan']), 130),
],
),
);
}).toList(),
],
),
),
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: controller.currentPage > 1
? controller.previousPage
: null,
style: ElevatedButton.styleFrom(
backgroundColor: controller.currentPage > 1
? const Color(0xFF51C3E8)
: Colors.grey,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("Previous"),
),
const SizedBox(width: 20),
Text(
'Page ${controller.currentPage}',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
fontSize: 12,
),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: controller.hasMore ? controller.nextPage : null,
style: ElevatedButton.styleFrom(
backgroundColor: controller.hasMore
? const Color(0xFF51C3E8)
: Colors.grey,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("Next"),
),
],
),
)
],
),
);
}),
),
],
),
),
);
}
Widget _headerCell(String label, double width) {
return SizedBox(
width: width,
child: Text(
label,
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
),
);
}
Widget _dataCell(String value, double width) {
return SizedBox(
width: width,
child: Text(
value,
style: GoogleFonts.poppins(fontSize: 14, color: Colors.grey[800]),
),
);
}
}

View File

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
}
}

View File

@ -0,0 +1,123 @@
import 'package:firebase_database/firebase_database.dart';
import 'package:get/get.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/intl.dart';
import '../../../routes/app_pages.dart';
class HomeController extends GetxController {
final suhu = 0.0.obs;
final kelembaban = 0.0.obs;
final intensitasCahaya = 0.0.obs;
final sensorHujan = ''.obs; // string bukan bool
final koneksiAlat = true.obs;
final suhuThreshold = 35.0.obs;
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final box = GetStorage();
final database = FirebaseDatabase.instance.ref();
DateTime lastDataTime = DateTime.now();
bool suhuNotifSent = false;
bool hujanNotifSent = false;
@override
void onInit() {
super.onInit();
_initNotifications();
_listenToSensorData();
_startTimeoutCheck();
}
void _initNotifications() async {
const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
final initSettings = InitializationSettings(android: androidInit);
await flutterLocalNotificationsPlugin.initialize(initSettings);
}
void _showNotification(String title, String body) async {
const androidDetails = AndroidNotificationDetails(
'channel_id',
'Peringatan',
channelDescription: 'Notifikasi penting',
importance: Importance.max,
priority: Priority.high,
);
const platformDetails = NotificationDetails(android: androidDetails);
await flutterLocalNotificationsPlugin.show(
DateTime.now().millisecondsSinceEpoch ~/ 1000,
title,
body,
platformDetails,
);
_saveNotificationToStorage(title, body);
}
void _saveNotificationToStorage(String title, String body) {
final now = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
final List history = box.read('notification_history') ?? [];
history.add({'judul': title, 'deskripsi': body, 'waktu': now});
box.write('notification_history', history);
}
void _listenToSensorData() {
// Listen to threshold changes
database.child('threshold/suhu').onValue.listen((event) {
final value = event.snapshot.value;
if (value is num) suhuThreshold.value = value.toDouble();
});
// Listen to sensor data
database.child('sensor').onValue.listen((event) {
final data = event.snapshot.value as Map?;
if (data == null) return;
lastDataTime = DateTime.now();
koneksiAlat.value = true;
suhu.value = (data['suhu'] ?? 0).toDouble();
kelembaban.value = (data['kelembaban'] ?? 0).toDouble();
intensitasCahaya.value = (data['intensitas_cahaya'] ?? 0).toDouble();
sensorHujan.value = data['sensor_hujan']?.toString() ?? '';
// Handle suhu notification
if (suhu.value > suhuThreshold.value && !suhuNotifSent) {
_showNotification("Suhu Tinggi", "Suhu melebihi batas: ${suhuThreshold.value}°C");
suhuNotifSent = true;
} else if (suhu.value <= suhuThreshold.value) {
suhuNotifSent = false;
}
// Handle hujan notification (jika bukan "Cerah" maka dianggap hujan)
if (sensorHujan.value != "Cerah" && !hujanNotifSent) {
_showNotification("Hujan Terdeteksi", "Sensor mendeteksi hujan (${sensorHujan.value}).");
hujanNotifSent = true;
} else if (sensorHujan.value == "Cerah") {
hujanNotifSent = false;
}
});
}
void _startTimeoutCheck() {
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 30));
final diff = DateTime.now().difference(lastDataTime);
if (diff.inSeconds >= 120 && koneksiAlat.value) {
koneksiAlat.value = false;
_showNotification(
"Koneksi Terputus",
"Tidak ada data baru selama 2 menit. Periksa koneksi alat.",
);
}
return true;
});
}
void keNotifikasi() => Get.toNamed(Routes.NOTIFIKASI);
void keGrafik() => Get.toNamed(Routes.GRAFIK);
}

View File

@ -0,0 +1,283 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import '../controllers/home_controller.dart';
class HomeView extends GetView<HomeController> {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
final now = DateTime.now();
final formattedDate = DateFormat("EEEE, d MMMM yyyy", "id_ID").format(now);
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Obx(() {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header
Container(
width: size.width,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Jember, Jawa Timur",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(formattedDate,
style: GoogleFonts.poppins(color: Colors.white70, fontSize: 13)),
],
),
IconButton(
icon: const Icon(Icons.notifications, color: Colors.white),
onPressed: controller.keNotifikasi,
)
],
),
),
const SizedBox(height: 20),
// Koneksi alat
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade300),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 5, offset: Offset(0, 3)),
],
),
child: Row(
children: [
CircleAvatar(
backgroundColor:
controller.koneksiAlat.value ? const Color(0xFF51C3E8) : Colors.red,
radius: 24,
child: const Icon(Icons.sync, color: Colors.white),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Koneksi Alat",
style: GoogleFonts.poppins(fontWeight: FontWeight.w500)),
Text(
controller.koneksiAlat.value ? "Connect" : "Disconnect",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
color: controller.koneksiAlat.value
? const Color(0xFF51C3E8)
: Colors.red,
),
),
],
),
],
),
),
const SizedBox(height: 20),
// Kartu suhu
Container(
width: size.width - 40,
margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28),
gradient: const LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
const Icon(Icons.thermostat_outlined, color: Colors.white, size: 40),
const SizedBox(height: 10),
Text(
"${controller.suhu.value.toStringAsFixed(1)}ºC",
style: GoogleFonts.poppins(
color: Colors.white, fontSize: 36, fontWeight: FontWeight.bold),
),
const SizedBox(height: 6),
Text("Suhu",
style: GoogleFonts.poppins(color: Colors.white, fontSize: 18)),
],
),
),
const SizedBox(height: 20),
// Kelembaban, Intensitas Cahaya, Sensor Hujan
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: size.width > 600
? Row(
children: [
Expanded(
child: _miniCard(
Icons.water_drop,
"${controller.kelembaban.value.toStringAsFixed(0)}%",
"Kelembaban"),
),
const SizedBox(width: 10),
Expanded(
child: _miniCard(
Icons.wb_sunny,
"${controller.intensitasCahaya.value.toStringAsFixed(0)} LUX",
"Intensitas Cahaya"),
),
const SizedBox(width: 10),
Expanded(
child: _miniCard(
Icons.grain,
controller.sensorHujan.value.isEmpty
? "Tidak Ada Data"
: controller.sensorHujan.value,
"Sensor Hujan"),
),
],
)
: Wrap(
spacing: 10,
runSpacing: 10,
children: [
SizedBox(
width: (size.width - 60) / 3,
child: _miniCard(
Icons.water_drop,
"${controller.kelembaban.value.toStringAsFixed(0)}%",
"Kelembaban"),
),
SizedBox(
width: (size.width - 60) / 3,
child: _miniCard(
Icons.wb_sunny,
"${controller.intensitasCahaya.value.toStringAsFixed(0)} LUX",
"Intensitas Cahaya"),
),
SizedBox(
width: (size.width - 60) / 3,
child: _miniCard(
Icons.grain,
controller.sensorHujan.value.isEmpty
? "Tidak Ada Data"
: controller.sensorHujan.value,
"Sensor Hujan"),
),
],
),
),
const SizedBox(height: 20),
// Grafik & Analisis
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF7AD6F0), // Background biru muda
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.transparent), // Border transparan
),
child: ElevatedButton(
onPressed: controller.keGrafik, // Function from controller
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent, // Transparan untuk button
shadowColor: Colors.transparent,
padding: EdgeInsets.zero,
),
child: Row(
children: [
CircleAvatar(
backgroundColor: Colors.white, // Warna putih untuk CircleAvatar
radius: 24,
child: const Icon(Icons.show_chart, color: Color(0xFF7AD6F0)), // Ikon biru
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Grafik & Analisis",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
color: Colors.white, // Warna teks tetap putih
),
),
Text(
"Lihat Grafik",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
color: Colors.white, // Warna teks tetap putih
),
),
],
),
],
),
),
)
],
),
),
);
}),
),
);
}
Widget _miniCard(IconData icon, String value, String label) {
return Container(
height: 90,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: const LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: const EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Colors.white, size: 26),
const SizedBox(height: 6),
Text(value,
style: GoogleFonts.poppins(
color: Colors.white, fontWeight: FontWeight.bold)),
Text(label, style: GoogleFonts.poppins(color: Colors.white, fontSize: 8)),
],
),
);
}
}

View File

@ -0,0 +1,13 @@
import 'package:get/get.dart';
import '../controllers/navbar_controller.dart';
import '../../home/controllers/home_controller.dart';
import '../../cuaca/controllers/cuaca_controller.dart';
class NavbarBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<NavbarController>(() => NavbarController());
Get.lazyPut<HomeController>(() => HomeController()); // Tambah ini
Get.lazyPut<CuacaController>(() => CuacaController()); // Tambah ini
}
}

View File

@ -0,0 +1,9 @@
import 'package:get/get.dart';
class NavbarController extends GetxController {
var selectedIndex = 0.obs;
void changeTabIndex(int index) {
selectedIndex.value = index;
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/navbar_controller.dart';
import '../../home/views/home_view.dart';
import '../../cuaca/views/cuaca_view.dart';
class NavbarView extends StatelessWidget {
const NavbarView({super.key});
@override
Widget build(BuildContext context) {
final NavbarController controller = Get.find<NavbarController>();
final List<Widget> pages = const [
HomeView(),
CuacaView(),
];
return Obx(() => Scaffold(
body: IndexedStack(
index: controller.selectedIndex.value,
children: pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: controller.selectedIndex.value,
onTap: controller.changeTabIndex,
backgroundColor: const Color(0xFF7AD6F0),
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white70,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Beranda',
),
BottomNavigationBarItem(
icon: Icon(Icons.cloud),
label: 'Cuaca',
),
],
),
));
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/notifikasi_controller.dart';
class NotifikasiBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<NotifikasiController>(
() => NotifikasiController(),
);
}
}

View File

@ -0,0 +1,40 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
class NotifikasiController extends GetxController {
final notifikasiList = <Map<String, dynamic>>[].obs;
final storage = GetStorage();
@override
void onInit() {
super.onInit();
loadNotifications();
}
void loadNotifications() {
final data = storage.read<List>('notification_history');
if (data != null) {
final sorted = List<Map<String, dynamic>>.from(data.reversed);
notifikasiList.value = sorted.where((item) =>
item['waktu'] != null &&
item['judul'] != null &&
item['deskripsi'] != null
).toList();
}
}
void hapusNotifikasi(String waktu) {
final updatedList = List<Map<String, dynamic>>.from(notifikasiList);
updatedList.removeWhere((item) => item['waktu'] == waktu);
notifikasiList.value = updatedList;
// Simpan ke storage
storage.write('notification_history', updatedList.reversed.toList());
}
void hapusSemua() {
notifikasiList.clear();
storage.write('notification_history', []);
}
}

View File

@ -0,0 +1,150 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/notifikasi_controller.dart';
class NotifikasiView extends GetView<NotifikasiController> {
const NotifikasiView({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
// === CUSTOM APPBAR MELENGKUNG ===
Container(
width: size.width,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Back button
IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Get.back(),
),
// Title
Text(
'Notifikasi',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
// Delete all icon
IconButton(
icon: const Icon(Icons.delete_sweep, color: Colors.white),
onPressed: () {
if (controller.notifikasiList.isEmpty) return;
controller.hapusSemua();
},
),
],
),
),
const SizedBox(height: 16),
// === DAFTAR NOTIFIKASI ===
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Obx(() {
final list = controller.notifikasiList;
if (list.isEmpty) {
return const Center(
child: Text(
"Belum ada notifikasi.",
style: TextStyle(fontSize: 16, color: Colors.grey),
),
);
}
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
final item = list[index];
return Dismissible(
key: Key(item['waktu'] ?? 'key_$index'),
background: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
alignment: Alignment.centerLeft,
child: const Icon(Icons.delete, color: Colors.grey),
),
secondaryBackground: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
alignment: Alignment.centerRight,
child: const Icon(Icons.delete, color: Colors.grey),
),
onDismissed: (_) => controller.hapusNotifikasi(item['waktu'] ?? ''),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white,
elevation: 3,
margin: const EdgeInsets.only(bottom: 15),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['judul'] ?? 'Tidak ada judul',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
item['deskripsi'] ?? 'Tidak ada deskripsi',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[700],
),
),
const SizedBox(height: 10),
Align(
alignment: Alignment.bottomRight,
child: Text(
item['waktu'] ?? '-',
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey[600],
),
),
),
],
),
),
),
);
},
);
}),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/setting_controller.dart';
class SettingBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<SettingController>(
() => SettingController(),
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:firebase_database/firebase_database.dart';
class SettingController extends GetxController {
final formKey = GlobalKey<FormState>();
final thresholdController = TextEditingController();
final intervalController = TextEditingController();
final suhuController = TextEditingController();
final database = FirebaseDatabase.instance.ref();
@override
void onInit() {
super.onInit();
ambilDataDariFirebase();
}
void ambilDataDariFirebase() async {
final snapshot = await database.child("threshold").get();
if (snapshot.exists) {
final data = snapshot.value as Map;
intervalController.text = data['interval'].toString();
suhuController.text = data['suhu'].toString();
}
}
void simpanKeFirebase() {
if (formKey.currentState?.validate() != true) return;
final data = {
"interval": int.tryParse(intervalController.text) ?? 0,
"suhu": int.tryParse(suhuController.text) ?? 0,
};
database.child("threshold").set(data).then((_) {
Get.snackbar("Berhasil", "Pengaturan berhasil disimpan",
snackPosition: SnackPosition.BOTTOM);
}).catchError((e) {
Get.snackbar("Gagal", "Terjadi kesalahan saat menyimpan",
snackPosition: SnackPosition.BOTTOM);
});
}
@override
void onClose() {
thresholdController.dispose();
intervalController.dispose();
suhuController.dispose();
super.onClose();
}
}

View File

@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/setting_controller.dart';
class SettingView extends GetView<SettingController> {
const SettingView({super.key});
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
// === APPBAR GRADIENT MELENGKUNG ===
Container(
width: size.width,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7AD6F0), Color(0xFF51C3E8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Get.back(),
),
Text(
'Pengaturan',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 48), // Dummy agar rata tengah
],
),
),
const SizedBox(height: 16),
// === FORM SETTING ===
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildField("Interval (menit)", controller.intervalController),
const SizedBox(height: 16),
buildField("Suhu (°C)", controller.suhuController),
const SizedBox(height: 30),
// === TOMBOL SIMPAN ===
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF51C3E8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 16),
),
onPressed: controller.simpanKeFirebase,
child: Text(
"Simpan",
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
)
],
),
),
),
),
],
),
),
);
}
Widget buildField(String label, TextEditingController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
const SizedBox(height: 8),
TextFormField(
controller: controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
filled: true,
fillColor: Colors.grey[100],
),
validator: (value) {
if (value == null || value.isEmpty) return 'Tidak boleh kosong';
return null;
},
),
],
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import '../controllers/splash_screen_controller.dart';
class SplashScreenBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<SplashScreenController>(
() => SplashScreenController(),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:get/get.dart';
import 'package:ecomoni/app/routes/app_pages.dart'; // Sesuaikan dengan nama project kamu
class SplashScreenController extends GetxController {
@override
void onInit() {
super.onInit();
Future.delayed(const Duration(seconds: 5), () {
Get.offAllNamed(Routes.NAVBAR); // atau Routes.NAVBAR jika kamu punya bottom nav
});
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/splash_screen_controller.dart';
class SplashScreenView extends StatelessWidget {
const SplashScreenView({super.key});
@override
Widget build(BuildContext context) {
final SplashScreenController controller = Get.put(SplashScreenController());
return Scaffold(
backgroundColor: const Color(0xFF7AD6F0), // Warna biru muda (light blue)
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.cloud, // Ikon yang sesuai untuk tema lingkungan
size: 100,
color: Colors.white,
),
const SizedBox(height: 24),
const Text(
'EcoMonitor',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
const Text(
'Pantau Kebunmu Secara Cerdas\nKapan Saja dan di Mana Saja',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Colors.white70,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,69 @@
import 'package:get/get.dart';
import '../modules/cuaca/bindings/cuaca_binding.dart';
import '../modules/cuaca/views/cuaca_view.dart';
import '../modules/grafik/bindings/grafik_binding.dart';
import '../modules/grafik/views/grafik_view.dart';
import '../modules/history/bindings/history_binding.dart';
import '../modules/history/views/history_view.dart';
import '../modules/home/bindings/home_binding.dart';
import '../modules/home/views/home_view.dart';
import '../modules/navbar/bindings/navbar_binding.dart';
import '../modules/navbar/views/navbar_view.dart';
import '../modules/notifikasi/bindings/notifikasi_binding.dart';
import '../modules/notifikasi/views/notifikasi_view.dart';
import '../modules/setting/bindings/setting_binding.dart';
import '../modules/setting/views/setting_view.dart';
import '../modules/splash_screen/bindings/splash_screen_binding.dart';
import '../modules/splash_screen/views/splash_screen_view.dart';
part 'app_routes.dart';
class AppPages {
AppPages._();
static const INITIAL = Routes.HOME;
static final routes = [
GetPage(
name: _Paths.HOME,
page: () => const HomeView(),
binding: HomeBinding(),
),
GetPage(
name: _Paths.CUACA,
page: () => const CuacaView(),
binding: CuacaBinding(),
),
GetPage(
name: _Paths.HISTORY,
page: () => const HistoryView(),
binding: HistoryBinding(),
),
GetPage(
name: _Paths.SPLASH_SCREEN,
page: () => const SplashScreenView(),
binding: SplashScreenBinding(),
),
GetPage(
name: _Paths.NOTIFIKASI,
page: () => const NotifikasiView(),
binding: NotifikasiBinding(),
),
GetPage(
name: _Paths.NAVBAR,
page: () => const NavbarView(),
binding: NavbarBinding(),
),
GetPage(
name: _Paths.SETTING,
page: () => const SettingView(),
binding: SettingBinding(),
),
GetPage(
name: _Paths.GRAFIK,
page: () => const GrafikView(),
binding: GrafikBinding(),
),
];
}

View File

@ -0,0 +1,26 @@
part of 'app_pages.dart';
// DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart
abstract class Routes {
Routes._();
static const HOME = _Paths.HOME;
static const CUACA = _Paths.CUACA;
static const HISTORY = _Paths.HISTORY;
static const SPLASH_SCREEN = _Paths.SPLASH_SCREEN;
static const NOTIFIKASI = _Paths.NOTIFIKASI;
static const NAVBAR = _Paths.NAVBAR;
static const SETTING = _Paths.SETTING;
static const GRAFIK = _Paths.GRAFIK;
}
abstract class _Paths {
_Paths._();
static const HOME = '/home';
static const CUACA = '/cuaca';
static const HISTORY = '/history';
static const SPLASH_SCREEN = '/splash-screen';
static const NOTIFIKASI = '/notifikasi';
static const NAVBAR = '/navbar';
static const SETTING = '/setting';
static const GRAFIK = '/grafik';
}

35
lib/main.dart Normal file
View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:firebase_core/firebase_core.dart';
import 'app/routes/app_pages.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); // Inisialisasi Firebase
await initializeDateFormatting('id_ID', null);
await _initLocalNotification(); // Inisialisasi notifikasi lokal
runApp(
GetMaterialApp(
debugShowCheckedModeBanner: false,
title: "EcoMoni",
initialRoute: Routes.SPLASH_SCREEN,
getPages: AppPages.routes,
),
);
}
// Dipindah ke luar dari main()
Future<void> _initLocalNotification() async {
const AndroidInitializationSettings androidSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initSettings =
InitializationSettings(android: androidSettings);
await flutterLocalNotificationsPlugin.initialize(initSettings);
}

Some files were not shown because too many files have changed in this diff Show More