first commit

This commit is contained in:
Nisacarolina 2025-08-19 14:07:00 +07:00
commit 7a95472d9c
163 changed files with 8453 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

45
.metadata Normal file
View File

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: android
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: ios
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: linux
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: macos
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: web
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: windows
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

218
FIREBASE_SETUP.md Normal file
View File

@ -0,0 +1,218 @@
# Firebase Setup Guide for Debartis Fire Detection
## Langkah-langkah Setup Firebase
### 1. Buat Project Firebase
1. Buka [Firebase Console](https://console.firebase.google.com/)
2. Klik "Add project" atau "Tambah project"
3. Masukkan nama project: `debartis-fire-detection`
4. Ikuti langkah-langkah setup hingga selesai
### 2. Enable Authentication
1. Di Firebase Console, pilih project `debartis-fire-detection`
2. Pilih "Authentication" di sidebar
3. Pilih tab "Sign-in method"
4. Enable "Email/Password" authentication
5. Klik "Save"
### 3. Setup Firestore Database
1. Pilih "Firestore Database" di sidebar
2. Klik "Create database"
3. Pilih "Start in test mode" (untuk development)
4. Pilih lokasi server (pilih yang paling dekat dengan lokasi Anda)
5. Klik "Done"
### 4. Setup Storage
1. Pilih "Storage" di sidebar
2. Klik "Get started"
3. Gunakan default rules untuk sekarang
4. Pilih lokasi yang sama dengan Firestore
5. Klik "Done"
### 5. Add Flutter App ke Firebase Project
1. Di Firebase Console, klik ikon Flutter (atau Add app > Flutter)
2. Masukkan informasi berikut:
- **Apple bundle ID**: `com.example.debartis`
- **Android package name**: `com.example.debartis`
- **App nickname**: `Debartis Fire Detection`
### 6. Download Configuration Files
1. Download `google-services.json` untuk Android
2. Download `GoogleService-Info.plist` untuk iOS
3. Tempatkan file tersebut di lokasi yang sesuai:
- Android: `android/app/google-services.json`
- iOS: `ios/Runner/GoogleService-Info.plist`
### 7. Update firebase_options.dart
1. Buka file `lib/firebase_options.dart`
2. Ganti semua placeholder dengan konfigurasi yang benar dari Firebase Console
3. Untuk mendapatkan konfigurasi:
- Buka Project Settings di Firebase Console
- Scroll ke bawah ke "Your apps"
- Pilih platform yang ingin dikonfigurasi
- Copy konfigurasi yang diperlukan
### 8. Konfigurasi Android (android/app/build.gradle)
Tambahkan di bagian bawah file:
```gradle
apply plugin: 'com.google.gms.google-services'
```
### 9. Konfigurasi Android (android/build.gradle)
Tambahkan di dependencies:
```gradle
dependencies {
classpath 'com.google.gms:google-services:4.3.15'
}
```
### 10. Konfigurasi iOS (ios/Runner/Info.plist)
Tambahkan konfigurasi URL scheme jika diperlukan.
### 11. Test Setup
1. Jalankan `flutter clean`
2. Jalankan `flutter pub get`
3. Jalankan `flutter run`
4. Coba register user baru
5. Cek Firebase Console untuk memastikan user terdaftar
## Database Structure
### Collections yang digunakan:
#### 1. users
- uid (document ID)
- email: string
- displayName: string
- photoURL: string
- role: string (default: "user")
- isActive: boolean
- createdAt: timestamp
- updatedAt: timestamp
#### 2. detections
- userId: string
- timestamp: timestamp
- location: string
- confidence: number (0.0 - 1.0)
- imageUrl: string
- status: string (detected, confirmed, false_alarm, resolved)
- severity: string (low, medium, high, critical)
- description: string
- resolved: boolean
- createdAt: timestamp
#### 3. alerts
- userId: string
- detectionId: string
- title: string
- message: string
- severity: string
- location: string
- timestamp: timestamp
- isRead: boolean
- isResolved: boolean
- createdAt: timestamp
#### 4. devices
- userId: string
- deviceName: string
- deviceType: string
- location: string
- status: string
- lastSeen: timestamp
- createdAt: timestamp
## Security Rules
### Firestore Rules
```javascript
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only access their own data
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Detections - users can only access their own detections
match /detections/{detectionId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
}
// Alerts - users can only access their own alerts
match /alerts/{alertId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
}
// Devices - users can only access their own devices
match /devices/{deviceId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
}
}
}
```
### Storage Rules
```javascript
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
```
## Environment Variables (Opsional)
Untuk keamanan tambahan, Anda bisa menggunakan environment variables untuk API keys.
## Troubleshooting
- Jika error "Firebase project not found", pastikan project ID benar
- Jika error authentication, cek apakah Email/Password sudah dienable
- Jika error Firestore, cek rules dan pastikan database sudah dibuat
- Jika error di build, pastikan google-services.json sudah di tempatkan dengan benar
### Windows Build Issues
Firebase SDK untuk Windows masih dalam tahap pengembangan dan sering mengalami masalah:
1. **Disk Space Error**:
- Pastikan ada space disk yang cukup (minimal 5GB free)
- Hapus folder `build` dan jalankan `flutter clean`
2. **Linking Errors** (LNK2019, LNK1120):
- Firebase Windows SDK memiliki compatibility issues
- **Solusi**: Gunakan platform lain untuk development:
```bash
# Untuk Android
flutter run -d android
# Untuk Web
flutter run -d chrome
# Untuk testing di emulator
flutter emulators --launch <emulator_name>
```
3. **CMake Deprecation Warnings**:
- Ini hanya warning dan tidak mempengaruhi functionality
- Akan diperbaiki di versi Firebase SDK selanjutnya
4. **Alternative untuk Windows Development**:
- Gunakan Firebase Web SDK melalui browser
- Develop dan test di Android emulator
- Deploy ke Android device untuk testing
### Recommended Development Flow:
1. **Primary Development**: Android (emulator atau device)
2. **Testing**: Web browser (Chrome)
3. **Production**: Android APK build
4. **Windows**: Tunggu Firebase SDK update atau gunakan alternatif
## Next Steps
1. Buat halaman register
2. Buat halaman home/dashboard
3. Implementasi real-time fire detection
4. Implementasi push notifications
5. Implementasi file upload untuk images

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# debartis
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

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

14
android/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,45 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
id("com.google.gms.google-services")
}
android {
namespace = "com.example.debartis"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.debartis"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 23
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@ -0,0 +1,87 @@
{
"project_info": {
"project_number": "844787133360",
"firebase_url": "https://deteksi-kebakaran-berbas-915bd-default-rtdb.firebaseio.com",
"project_id": "deteksi-kebakaran-berbas-915bd",
"storage_bucket": "deteksi-kebakaran-berbas-915bd.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:844787133360:android:31f7628397719b40494475",
"android_client_info": {
"package_name": "com.example.debartis"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:844787133360:android:2213fea564d5cbb0494475",
"android_client_info": {
"package_name": "com.example.deteksi_kebakaran_iot"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:844787133360:android:b1b14f81067aeb08494475",
"android_client_info": {
"package_name": "com.example.deteksikebakaran"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:844787133360:android:cc39404d02d57b15494475",
"android_client_info": {
"package_name": "deteksikebakaran.com"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI"
}
],
"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="debartis"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<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.debartis
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: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 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.kts Normal file
View File

@ -0,0 +1,31 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.google.gms:google-services:4.3.15")
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -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-8.10.2-all.zip

View File

@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")

BIN
assets/icons/app_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

69
assets/icons/app_icon.svg Normal file
View File

@ -0,0 +1,69 @@
<!-- Simple API Fire Detection Icon -->
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<!-- Background -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1976D2;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2196F3;stop-opacity:1" />
</linearGradient>
<linearGradient id="fireGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#FF5722;stop-opacity:1" />
<stop offset="100%" style="stop-color:#F44336;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Rounded background -->
<rect width="512" height="512" rx="80" ry="80" fill="url(#bgGradient)"/>
<!-- API Connection Lines -->
<g stroke="#ffffff" stroke-width="8" fill="none">
<!-- Left connections -->
<line x1="80" y1="200" x2="180" y2="200" stroke-linecap="round"/>
<line x1="80" y1="256" x2="180" y2="256" stroke-linecap="round"/>
<line x1="80" y1="312" x2="180" y2="312" stroke-linecap="round"/>
<!-- Right connections -->
<line x1="332" y1="200" x2="432" y2="200" stroke-linecap="round"/>
<line x1="332" y1="256" x2="432" y2="256" stroke-linecap="round"/>
<line x1="332" y1="312" x2="432" y2="312" stroke-linecap="round"/>
<!-- Central connections -->
<line x1="200" y1="200" x2="220" y2="200" stroke-linecap="round"/>
<line x1="200" y1="256" x2="220" y2="256" stroke-linecap="round"/>
<line x1="200" y1="312" x2="220" y2="312" stroke-linecap="round"/>
<line x1="292" y1="200" x2="312" y2="200" stroke-linecap="round"/>
<line x1="292" y1="256" x2="312" y2="256" stroke-linecap="round"/>
<line x1="292" y1="312" x2="312" y2="312" stroke-linecap="round"/>
</g>
<!-- API Nodes -->
<circle cx="60" cy="200" r="20" fill="#ffffff"/>
<circle cx="60" cy="256" r="20" fill="#ffffff"/>
<circle cx="60" cy="312" r="20" fill="#ffffff"/>
<circle cx="452" cy="200" r="20" fill="#ffffff"/>
<circle cx="452" cy="256" r="20" fill="#ffffff"/>
<circle cx="452" cy="312" r="20" fill="#ffffff"/>
<!-- Central Processing Unit -->
<rect x="220" y="180" width="72" height="132" rx="12" ry="12" fill="#ffffff"/>
<!-- Fire Detection Symbol -->
<g transform="translate(240, 190)">
<!-- Fire flame -->
<path d="M16 52 C16 52, 8 44, 8 32 C8 20, 16 16, 20 20 C24 12, 32 16, 32 24 C40 20, 44 28, 40 36 C48 32, 52 40, 48 48 C48 56, 40 60, 32 60 L16 60 C8 60, 0 52, 0 44 C0 36, 8 32, 16 32 Z"
fill="url(#fireGradient)" stroke="none"/>
<!-- Sensor waves -->
<g stroke="#1976D2" stroke-width="2" fill="none">
<path d="M16 80 Q24 76 32 80" stroke-linecap="round"/>
<path d="M12 88 Q24 84 36 88" stroke-linecap="round"/>
<path d="M8 96 Q24 92 40 96" stroke-linecap="round"/>
</g>
</g>
<!-- Status indicator -->
<circle cx="400" cy="112" r="24" fill="#4CAF50"/>
<path d="M388 112 L396 120 L412 104" stroke="#ffffff" stroke-width="4" fill="none" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

66
generate_icon.bat Normal file
View File

@ -0,0 +1,66 @@
@echo off
echo Generating app icon...
REM Buat icon sederhana menggunakan PowerShell dan .NET
powershell -Command "
Add-Type -AssemblyName System.Drawing
$bitmap = New-Object System.Drawing.Bitmap(512, 512)
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
# Background gradient
$brush = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
(New-Object System.Drawing.Point(0, 0)),
(New-Object System.Drawing.Point(512, 512)),
[System.Drawing.Color]::FromArgb(25, 118, 210),
[System.Drawing.Color]::FromArgb(33, 150, 243)
)
$graphics.FillRectangle($brush, 0, 0, 512, 512)
# White elements
$whiteBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
$whitePen = New-Object System.Drawing.Pen([System.Drawing.Color]::White, 8)
# Draw API nodes (circles)
$graphics.FillEllipse($whiteBrush, 40, 180, 40, 40)
$graphics.FillEllipse($whiteBrush, 40, 236, 40, 40)
$graphics.FillEllipse($whiteBrush, 40, 292, 40, 40)
$graphics.FillEllipse($whiteBrush, 432, 180, 40, 40)
$graphics.FillEllipse($whiteBrush, 432, 236, 40, 40)
$graphics.FillEllipse($whiteBrush, 432, 292, 40, 40)
# Draw connection lines
$graphics.DrawLine($whitePen, 80, 200, 180, 200)
$graphics.DrawLine($whitePen, 80, 256, 180, 256)
$graphics.DrawLine($whitePen, 80, 312, 180, 312)
$graphics.DrawLine($whitePen, 332, 200, 432, 200)
$graphics.DrawLine($whitePen, 332, 256, 432, 256)
$graphics.DrawLine($whitePen, 332, 312, 432, 312)
# Central box
$graphics.FillRectangle($whiteBrush, 220, 180, 72, 132)
# Fire symbol (simple triangle)
$fireBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(244, 67, 54))
$firePoints = @(
(New-Object System.Drawing.Point(256, 190)),
(New-Object System.Drawing.Point(245, 230)),
(New-Object System.Drawing.Point(267, 230))
)
$graphics.FillPolygon($fireBrush, $firePoints)
# Status indicator
$statusBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(76, 175, 80))
$graphics.FillEllipse($statusBrush, 376, 88, 48, 48)
$graphics.Dispose()
$bitmap.Save('assets\icons\app_icon.png', [System.Drawing.Imaging.ImageFormat]::Png)
$bitmap.Dispose()
Write-Host 'Icon generated successfully!'
"
echo Icon generated at assets\icons\app_icon.png
pause

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.debartis;
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.debartis.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.debartis.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.debartis.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 = AppIcon;
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 = AppIcon;
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.debartis;
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.debartis;
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,99 @@
<?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"
enableGPUValidationMode = "1"
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: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 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>Debartis</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>debartis</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.
}
}

89
lib/firebase_options.dart Normal file
View File

@ -0,0 +1,89 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
return windows;
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI',
appId: '1:844787133360:web:your-web-app-id',
messagingSenderId: '844787133360',
projectId: 'deteksi-kebakaran-berbas-915bd',
authDomain: 'deteksi-kebakaran-berbas-915bd.firebaseapp.com',
storageBucket: 'deteksi-kebakaran-berbas-915bd.firebasestorage.app',
databaseURL:
'https://deteksi-kebakaran-berbas-915bd-default-rtdb.firebaseio.com',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI',
appId: '1:844787133360:android:31f7628397719b40494475',
messagingSenderId: '844787133360',
projectId: 'deteksi-kebakaran-berbas-915bd',
storageBucket: 'deteksi-kebakaran-berbas-915bd.firebasestorage.app',
databaseURL:
'https://deteksi-kebakaran-berbas-915bd-default-rtdb.firebaseio.com',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI',
appId: '1:844787133360:ios:your-ios-app-id',
messagingSenderId: '844787133360',
projectId: 'deteksi-kebakaran-berbas-915bd',
storageBucket: 'deteksi-kebakaran-berbas-915bd.firebasestorage.app',
iosBundleId: 'com.example.debartis',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI',
appId: '1:844787133360:macos:your-macos-app-id',
messagingSenderId: '844787133360',
projectId: 'deteksi-kebakaran-berbas-915bd',
storageBucket: 'deteksi-kebakaran-berbas-915bd.firebasestorage.app',
iosBundleId: 'com.example.debartis',
);
static const FirebaseOptions windows = FirebaseOptions(
apiKey: 'AIzaSyBWdmHcBVtmkZ8Eeg5aaLF9nmsiL47MXaI',
appId: '1:844787133360:windows:your-windows-app-id',
messagingSenderId: '844787133360',
projectId: 'deteksi-kebakaran-berbas-915bd',
storageBucket: 'deteksi-kebakaran-berbas-915bd.firebasestorage.app',
);
}

26
lib/main.dart Normal file
View File

@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'screens/login_screen.dart';
import 'utils/app_theme.dart';
import 'utils/app_icons.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fire Detection System',
theme: AppTheme.lightTheme,
home: const LoginScreen(),
debugShowCheckedModeBanner: false,
);
}
}

View File

@ -0,0 +1,119 @@
import 'package:cloud_firestore/cloud_firestore.dart';
class FireDetectionModel {
final String id;
final String userId;
final DateTime timestamp;
final String location;
final double confidence;
final String imageUrl;
final String status;
final String severity;
final String description;
final bool resolved;
final DateTime createdAt;
FireDetectionModel({
required this.id,
required this.userId,
required this.timestamp,
required this.location,
required this.confidence,
required this.imageUrl,
required this.status,
required this.severity,
required this.description,
required this.resolved,
required this.createdAt,
});
factory FireDetectionModel.fromFirestore(DocumentSnapshot doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
return FireDetectionModel(
id: doc.id,
userId: data['userId'] ?? '',
timestamp: data['timestamp']?.toDate() ?? DateTime.now(),
location: data['location'] ?? '',
confidence: (data['confidence'] ?? 0.0).toDouble(),
imageUrl: data['imageUrl'] ?? '',
status: data['status'] ?? 'detected',
severity: data['severity'] ?? 'medium',
description: data['description'] ?? '',
resolved: data['resolved'] ?? false,
createdAt: data['createdAt']?.toDate() ?? DateTime.now(),
);
}
Map<String, dynamic> toFirestore() {
return {
'userId': userId,
'timestamp': Timestamp.fromDate(timestamp),
'location': location,
'confidence': confidence,
'imageUrl': imageUrl,
'status': status,
'severity': severity,
'description': description,
'resolved': resolved,
'createdAt': Timestamp.fromDate(createdAt),
};
}
FireDetectionModel copyWith({
String? id,
String? userId,
DateTime? timestamp,
String? location,
double? confidence,
String? imageUrl,
String? status,
String? severity,
String? description,
bool? resolved,
DateTime? createdAt,
}) {
return FireDetectionModel(
id: id ?? this.id,
userId: userId ?? this.userId,
timestamp: timestamp ?? this.timestamp,
location: location ?? this.location,
confidence: confidence ?? this.confidence,
imageUrl: imageUrl ?? this.imageUrl,
status: status ?? this.status,
severity: severity ?? this.severity,
description: description ?? this.description,
resolved: resolved ?? this.resolved,
createdAt: createdAt ?? this.createdAt,
);
}
String get severityText {
switch (severity) {
case 'low':
return 'Rendah';
case 'medium':
return 'Sedang';
case 'high':
return 'Tinggi';
case 'critical':
return 'Kritis';
default:
return 'Tidak diketahui';
}
}
String get statusText {
switch (status) {
case 'detected':
return 'Terdeteksi';
case 'confirmed':
return 'Dikonfirmasi';
case 'false_alarm':
return 'Alarm Palsu';
case 'resolved':
return 'Diselesaikan';
default:
return 'Tidak diketahui';
}
}
}

View File

@ -0,0 +1,68 @@
class SensorData {
final bool apiTerdeteksi;
final bool buzzerActive;
final bool exhaustActive;
final int gasValue;
final int kelembapan;
final bool pumpActive;
final double suhu;
SensorData({
required this.apiTerdeteksi,
required this.buzzerActive,
required this.exhaustActive,
required this.gasValue,
required this.kelembapan,
required this.pumpActive,
required this.suhu,
});
factory SensorData.fromJson(Map<String, dynamic> json) {
return SensorData(
apiTerdeteksi: json['api_terdeteksi'] ?? false,
buzzerActive: json['buzzer_active'] ?? false,
exhaustActive: json['exhaust_active'] ?? false,
gasValue: json['gas_value'] ?? 0,
kelembapan: json['kelembapan'] ?? 0,
pumpActive: json['pump_active'] ?? false,
suhu: (json['suhu'] ?? 0).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {
'api_terdeteksi': apiTerdeteksi,
'buzzer_active': buzzerActive,
'exhaust_active': exhaustActive,
'gas_value': gasValue,
'kelembapan': kelembapan,
'pump_active': pumpActive,
'suhu': suhu,
};
}
// Helper methods untuk status
String get statusApi => apiTerdeteksi ? 'Terdeteksi' : 'Aman';
String get statusSuhu =>
suhu > 35
? 'Panas'
: suhu > 25
? 'Normal'
: 'Dingin';
String get statusKelembapan =>
kelembapan > 70
? 'Tinggi'
: kelembapan > 40
? 'Normal'
: 'Rendah';
String get statusGas =>
gasValue > 300
? 'Bahaya'
: gasValue > 150
? 'Waspada'
: 'Aman';
// Helper untuk warna status
bool get isFireDetected => apiTerdeteksi || gasValue > 300 || suhu > 40;
bool get isWarning => gasValue > 150 || suhu > 35 || kelembapan > 70;
}

View File

@ -0,0 +1,77 @@
import 'package:cloud_firestore/cloud_firestore.dart';
class UserModel {
final String uid;
final String email;
final String displayName;
final String photoURL;
final String role;
final bool isActive;
final DateTime? createdAt;
final DateTime? updatedAt;
UserModel({
required this.uid,
required this.email,
required this.displayName,
required this.photoURL,
required this.role,
required this.isActive,
this.createdAt,
this.updatedAt,
});
factory UserModel.fromFirestore(DocumentSnapshot doc) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
return UserModel(
uid: doc.id,
email: data['email'] ?? '',
displayName: data['displayName'] ?? '',
photoURL: data['photoURL'] ?? '',
role: data['role'] ?? 'user',
isActive: data['isActive'] ?? true,
createdAt: data['createdAt']?.toDate(),
updatedAt: data['updatedAt']?.toDate(),
);
}
Map<String, dynamic> toFirestore() {
return {
'email': email,
'displayName': displayName,
'photoURL': photoURL,
'role': role,
'isActive': isActive,
'createdAt':
createdAt != null
? Timestamp.fromDate(createdAt!)
: FieldValue.serverTimestamp(),
'updatedAt':
updatedAt != null
? Timestamp.fromDate(updatedAt!)
: FieldValue.serverTimestamp(),
};
}
UserModel copyWith({
String? uid,
String? email,
String? displayName,
String? photoURL,
String? role,
bool? isActive,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return UserModel(
uid: uid ?? this.uid,
email: email ?? this.email,
displayName: displayName ?? this.displayName,
photoURL: photoURL ?? this.photoURL,
role: role ?? this.role,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

View File

@ -0,0 +1,447 @@
import 'package:flutter/material.dart';
import '../models/sensor_data_model.dart';
import '../services/realtime_database_service.dart';
import '../services/auth_service.dart';
import '../utils/app_colors.dart';
import '../widgets/sensor_card.dart';
import '../widgets/custom_button.dart';
import 'login_screen.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({Key? key}) : super(key: key);
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
final RealtimeDatabaseService _dbService = RealtimeDatabaseService();
final AuthService _authService = AuthService();
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard Deteksi Kebakaran'),
backgroundColor: AppColors.primaryRed,
foregroundColor: AppColors.textLight,
elevation: 0,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() {});
},
),
PopupMenuButton<String>(
onSelected: (value) {
if (value == 'logout') {
_handleLogout();
}
},
itemBuilder:
(BuildContext context) => [
PopupMenuItem<String>(
value: 'logout',
child: Row(
children: [
Icon(Icons.logout, color: AppColors.textError),
const SizedBox(width: 8),
Text('Logout'),
],
),
),
],
),
],
),
body: StreamBuilder<SensorData>(
stream: _dbService.sensorDataStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryRed),
),
);
}
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: AppColors.textError,
),
const SizedBox(height: 16),
Text(
'Gagal memuat data sensor',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textError,
),
),
const SizedBox(height: 8),
Text(
'Periksa koneksi internet Anda',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {});
},
child: const Text('Coba Lagi'),
),
],
),
);
}
final sensorData = snapshot.data!;
return RefreshIndicator(
onRefresh: () async {
setState(() {});
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status Overview
_buildStatusOverview(sensorData),
const SizedBox(height: 24),
// Sensor Data Section
_buildSectionTitle('Data Sensor'),
_buildSensorCards(sensorData),
const SizedBox(height: 24),
// Control Section
// _buildSectionTitle('Kontrol Sistem'),
// _buildControlCards(sensorData),
const SizedBox(height: 24),
// Emergency Actions
// _buildEmergencyActions(),
],
),
),
);
},
),
);
}
Widget _buildStatusOverview(SensorData sensorData) {
Color statusColor;
String statusText;
IconData statusIcon;
if (sensorData.isFireDetected) {
statusColor = AppColors.textError;
statusText = 'BAHAYA - API TERDETEKSI';
statusIcon = Icons.local_fire_department;
} else if (sensorData.isWarning) {
statusColor = AppColors.primaryOrange;
statusText = 'WASPADA - KONDISI ABNORMAL';
statusIcon = Icons.warning;
} else {
statusColor = AppColors.accentGreen;
statusText = 'AMAN - KONDISI NORMAL';
statusIcon = Icons.check_circle;
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [statusColor, statusColor.withOpacity(0.8)],
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: statusColor.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
children: [
Icon(statusIcon, size: 48, color: AppColors.textLight),
const SizedBox(height: 12),
Text(
statusText,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textLight,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Terakhir Update: ${DateTime.now().toString().substring(0, 19)}',
style: TextStyle(
fontSize: 12,
color: AppColors.textLight.withOpacity(0.8),
),
),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
);
}
Widget _buildSensorCards(SensorData sensorData) {
return Column(
children: [
SensorCard(
title: 'Suhu',
value: sensorData.suhu.toStringAsFixed(1),
unit: '°C',
icon: Icons.thermostat,
statusColor:
sensorData.suhu > 35
? AppColors.textError
: sensorData.suhu > 25
? AppColors.accentGreen
: AppColors.primaryBlue,
status: sensorData.statusSuhu,
),
SensorCard(
title: 'Kelembapan',
value: sensorData.kelembapan.toString(),
unit: '%',
icon: Icons.water_drop,
statusColor:
sensorData.kelembapan > 70
? AppColors.primaryBlue
: sensorData.kelembapan > 40
? AppColors.accentGreen
: AppColors.primaryOrange,
status: sensorData.statusKelembapan,
),
SensorCard(
title: 'Gas',
value: sensorData.gasValue.toString(),
unit: 'ppm',
icon: Icons.air,
statusColor:
sensorData.gasValue > 300
? AppColors.textError
: sensorData.gasValue > 150
? AppColors.primaryOrange
: AppColors.accentGreen,
status: sensorData.statusGas,
),
SensorCard(
title: 'Deteksi Api',
value: sensorData.apiTerdeteksi ? 'TERDETEKSI' : 'AMAN',
unit: '',
icon: Icons.local_fire_department,
statusColor:
sensorData.apiTerdeteksi
? AppColors.textError
: AppColors.accentGreen,
status: sensorData.statusApi,
),
],
);
}
Widget _buildControlCards(SensorData sensorData) {
return Column(
children: [
ControlCard(
title: 'Buzzer',
description: 'Alarm suara peringatan',
icon: Icons.volume_up,
isActive: sensorData.buzzerActive,
activeColor: AppColors.primaryOrange,
onToggle: () => _toggleControl('buzzer', !sensorData.buzzerActive),
),
ControlCard(
title: 'Exhaust Fan',
description: 'Kipas pembuangan asap',
icon: Icons.air,
isActive: sensorData.exhaustActive,
activeColor: AppColors.primaryBlue,
onToggle: () => _toggleControl('exhaust', !sensorData.exhaustActive),
),
ControlCard(
title: 'Water Pump',
description: 'Pompa air pemadam',
icon: Icons.water,
isActive: sensorData.pumpActive,
activeColor: AppColors.accentGreen,
onToggle: () => _toggleControl('pump', !sensorData.pumpActive),
),
],
);
}
Widget _buildEmergencyActions() {
return Column(
children: [
const SizedBox(height: 16),
// Mode Darurat Button
CustomButton(
text: 'Mode Darurat',
onPressed: _activateEmergencyMode,
backgroundColor: AppColors.textError,
icon: Icons.emergency,
isLoading: _isLoading,
width: double.infinity,
),
const SizedBox(height: 8),
// Reset Semua Button
CustomButton(
text: 'Reset Semua',
onPressed: _resetAllControls,
backgroundColor: AppColors.secondaryGray,
icon: Icons.refresh,
isLoading: _isLoading,
width: double.infinity,
),
],
);
}
Future<void> _toggleControl(String controlType, bool value) async {
setState(() {
_isLoading = true;
});
try {
switch (controlType) {
case 'buzzer':
await _dbService.updateControl(buzzerActive: value);
break;
case 'exhaust':
await _dbService.updateControl(exhaustActive: value);
break;
case 'pump':
await _dbService.updateControl(pumpActive: value);
break;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Kontrol $controlType berhasil diupdate'),
backgroundColor: AppColors.accentGreen,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: AppColors.textError,
),
);
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _activateEmergencyMode() async {
setState(() {
_isLoading = true;
});
try {
await _dbService.activateEmergencyMode();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Mode darurat diaktifkan!'),
backgroundColor: AppColors.textError,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: AppColors.textError,
),
);
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _resetAllControls() async {
setState(() {
_isLoading = true;
});
try {
await _dbService.resetAllControls();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Semua kontrol berhasil direset'),
backgroundColor: AppColors.accentGreen,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: AppColors.textError,
),
);
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _handleLogout() async {
try {
await _authService.signOut();
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const LoginScreen()),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: AppColors.textError,
),
);
}
}
}

View File

@ -0,0 +1,411 @@
import 'package:flutter/material.dart';
import '../utils/app_colors.dart';
import '../widgets/custom_text_field.dart';
import '../widgets/custom_button.dart';
import '../services/auth_service.dart';
import '../services/firestore_service.dart';
import 'dashboard_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _authService = AuthService();
final _firestoreService = FirestoreService();
bool _isLoading = false;
late AnimationController _animationController;
late AnimationController _pulseAnimationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _pulseAnimation;
@override
void initState() {
super.initState();
// Initialize animation controllers
_animationController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_pulseAnimationController = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
// Initialize animations
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(parent: _animationController, curve: Curves.elasticOut),
);
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.elasticOut),
);
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.1).animate(
CurvedAnimation(
parent: _pulseAnimationController,
curve: Curves.easeInOut,
),
);
// Start animations
_animationController.forward();
_pulseAnimationController.repeat(reverse: true);
}
@override
void dispose() {
_animationController.dispose();
_pulseAnimationController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email tidak boleh kosong';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Format email tidak valid';
}
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password tidak boleh kosong';
}
if (value.length < 6) {
return 'Password minimal 6 karakter';
}
return null;
}
void _handleLogin() async {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});
try {
// Login dengan Firebase
UserCredential? result = await _authService.signInWithEmailAndPassword(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
if (result?.user != null) {
// Cek apakah user profile sudah ada di Firestore
var userProfile = await _firestoreService.getUserProfile(
result!.user!.uid,
);
if (userProfile == null) {
// Buat profile baru jika belum ada
await _firestoreService.createUserProfile(
uid: result.user!.uid,
email: result.user!.email!,
displayName: result.user!.displayName,
photoURL: result.user!.photoURL,
);
}
// Tampilkan pesan sukses
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login berhasil!'),
backgroundColor: AppColors.accentGreen,
),
);
// TODO: Navigate to home screen
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const DashboardScreen()),
);
}
} catch (e) {
// Tampilkan error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor: AppColors.textError,
),
);
} finally {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
body: Container(
width: screenWidth,
height: screenHeight,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColors.backgroundLight,
AppColors.backgroundWhite,
Color(0xFFFFF8F0),
],
),
),
child: SafeArea(
child: FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(
horizontal: screenWidth * 0.08,
vertical: 16,
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: screenHeight * 0.05),
// Logo and App Title
_buildHeader(),
SizedBox(height: screenHeight * 0.06),
// Login Form
_buildLoginForm(),
SizedBox(height: screenHeight * 0.04),
// Login Button
CustomButton(
text: 'Masuk',
onPressed: _handleLogin,
isLoading: _isLoading,
width: double.infinity,
height: 54,
icon: Icons.login,
),
SizedBox(height: screenHeight * 0.04),
// Footer
_buildFooter(),
],
),
),
),
),
),
),
),
),
);
}
Widget _buildHeader() {
return Column(
children: [
// Fire Detection Logo with animated pulse
AnimatedBuilder(
animation: _pulseAnimation,
builder: (context, child) {
return Transform.scale(
scale: _pulseAnimation.value,
child: Container(
width: 110,
height: 110,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: AppColors.primaryGradient,
boxShadow: [
BoxShadow(
color: AppColors.primaryRed.withOpacity(0.4),
blurRadius: 25,
offset: const Offset(0, 10),
),
BoxShadow(
color: AppColors.primaryOrange.withOpacity(0.3),
blurRadius: 40,
offset: const Offset(0, 15),
),
],
),
child: const Icon(
Icons.local_fire_department,
size: 55,
color: AppColors.textLight,
),
),
);
},
),
const SizedBox(height: 20),
// App Title
const Text(
'Fire Detection',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
letterSpacing: 1.2,
),
),
const SizedBox(height: 8),
// Subtitle
const Text(
'Sistem Deteksi Kebakaran Otomatis',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
fontWeight: FontWeight.w400,
),
),
],
);
}
Widget _buildLoginForm() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.backgroundWhite,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: AppColors.secondaryGray.withOpacity(0.08),
blurRadius: 25,
offset: const Offset(0, 10),
),
BoxShadow(
color: AppColors.primaryRed.withOpacity(0.05),
blurRadius: 30,
offset: const Offset(0, 20),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Masuk ke Akun Anda',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 6),
const Text(
'Silakan masukkan email dan password Anda',
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
),
const SizedBox(height: 24),
// Email Field
CustomTextField(
hintText: 'Masukkan email Anda',
labelText: 'Email',
prefixIcon: Icons.email_outlined,
controller: _emailController,
validator: _validateEmail,
keyboardType: TextInputType.emailAddress,
),
// Password Field
CustomTextField(
hintText: 'Masukkan password Anda',
labelText: 'Password',
prefixIcon: Icons.lock_outline,
controller: _passwordController,
validator: _validatePassword,
obscureText: true,
),
],
),
);
}
Widget _buildFooter() {
return Column(
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Text(
// 'Belum punya akun? ',
// style: TextStyle(color: AppColors.textSecondary, fontSize: 14),
// ),
// TextButton(
// onPressed: () {
// // Navigate to register page
// },
// child: const Text(
// 'Daftar Sekarang',
// style: TextStyle(
// color: AppColors.primaryRed,
// fontSize: 14,
// fontWeight: FontWeight.w600,
// ),
// ),
// ),
// ],
// ),
const SizedBox(height: 16),
// Version Info
const Text(
'Version 1.0.0',
style: TextStyle(color: AppColors.textSecondary, fontSize: 12),
),
const SizedBox(height: 4),
// Copyright
const Text(
'© 2025 Fire Detection System',
style: TextStyle(color: AppColors.textSecondary, fontSize: 12),
),
],
);
}
}

View File

View File

@ -0,0 +1,135 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Get current user
User? get currentUser => _auth.currentUser;
// Auth state changes stream
Stream<User?> get authStateChanges => _auth.authStateChanges();
// Sign in with email and password
Future<UserCredential?> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return result;
} on FirebaseAuthException catch (e) {
if (kDebugMode) {
print('Firebase Auth Error: ${e.message}');
}
throw _handleAuthException(e);
} catch (e) {
if (kDebugMode) {
print('General Auth Error: $e');
}
throw 'Terjadi kesalahan saat login';
}
}
// Register with email and password
Future<UserCredential?> registerWithEmailAndPassword({
required String email,
required String password,
}) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return result;
} on FirebaseAuthException catch (e) {
if (kDebugMode) {
print('Firebase Auth Error: ${e.message}');
}
throw _handleAuthException(e);
} catch (e) {
if (kDebugMode) {
print('General Auth Error: $e');
}
throw 'Terjadi kesalahan saat mendaftar';
}
}
// Sign out
Future<void> signOut() async {
try {
await _auth.signOut();
} catch (e) {
if (kDebugMode) {
print('Sign out error: $e');
}
throw 'Terjadi kesalahan saat logout';
}
}
// Reset password
Future<void> sendPasswordResetEmail({required String email}) async {
try {
await _auth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
if (kDebugMode) {
print('Password reset error: ${e.message}');
}
throw _handleAuthException(e);
} catch (e) {
if (kDebugMode) {
print('General password reset error: $e');
}
throw 'Terjadi kesalahan saat mengirim email reset password';
}
}
// Update user profile
Future<void> updateUserProfile({
String? displayName,
String? photoURL,
}) async {
try {
await currentUser?.updateDisplayName(displayName);
if (photoURL != null) {
await currentUser?.updatePhotoURL(photoURL);
}
} catch (e) {
if (kDebugMode) {
print('Update profile error: $e');
}
throw 'Terjadi kesalahan saat update profil';
}
}
// Handle Firebase Auth exceptions
String _handleAuthException(FirebaseAuthException e) {
switch (e.code) {
case 'weak-password':
return 'Password terlalu lemah';
case 'email-already-in-use':
return 'Email sudah digunakan';
case 'invalid-email':
return 'Email tidak valid';
case 'user-not-found':
return 'User tidak ditemukan';
case 'wrong-password':
return 'Password salah';
case 'user-disabled':
return 'Akun telah dinonaktifkan';
case 'too-many-requests':
return 'Terlalu banyak percobaan login. Coba lagi nanti';
case 'operation-not-allowed':
return 'Operasi tidak diizinkan';
case 'invalid-credential':
return 'Kredensial tidak valid';
case 'network-request-failed':
return 'Koneksi internet bermasalah';
default:
return e.message ?? 'Terjadi kesalahan yang tidak diketahui';
}
}
}

View File

@ -0,0 +1,209 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
class FirestoreService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
// Collections
static const String usersCollection = 'users';
static const String detectionsCollection = 'detections';
static const String alertsCollection = 'alerts';
static const String devicesCollection = 'devices';
// User operations
Future<void> createUserProfile({
required String uid,
required String email,
String? displayName,
String? photoURL,
}) async {
try {
await _firestore.collection(usersCollection).doc(uid).set({
'email': email,
'displayName': displayName ?? '',
'photoURL': photoURL ?? '',
'createdAt': FieldValue.serverTimestamp(),
'updatedAt': FieldValue.serverTimestamp(),
'role': 'user',
'isActive': true,
});
} catch (e) {
if (kDebugMode) {
print('Create user profile error: $e');
}
throw 'Gagal membuat profil pengguna';
}
}
Future<DocumentSnapshot?> getUserProfile(String uid) async {
try {
DocumentSnapshot doc =
await _firestore.collection(usersCollection).doc(uid).get();
return doc.exists ? doc : null;
} catch (e) {
if (kDebugMode) {
print('Get user profile error: $e');
}
throw 'Gagal mengambil profil pengguna';
}
}
Future<void> updateUserProfile({
required String uid,
Map<String, dynamic>? data,
}) async {
try {
if (data != null) {
data['updatedAt'] = FieldValue.serverTimestamp();
await _firestore.collection(usersCollection).doc(uid).update(data);
}
} catch (e) {
if (kDebugMode) {
print('Update user profile error: $e');
}
throw 'Gagal mengupdate profil pengguna';
}
}
// Fire detection operations
Future<void> saveDetectionData({
required String uid,
required Map<String, dynamic> detectionData,
}) async {
try {
await _firestore.collection(detectionsCollection).add({
'userId': uid,
'timestamp': FieldValue.serverTimestamp(),
'location': detectionData['location'] ?? '',
'confidence': detectionData['confidence'] ?? 0.0,
'imageUrl': detectionData['imageUrl'] ?? '',
'status': detectionData['status'] ?? 'detected',
'severity': detectionData['severity'] ?? 'medium',
'description': detectionData['description'] ?? '',
'resolved': false,
'createdAt': FieldValue.serverTimestamp(),
});
} catch (e) {
if (kDebugMode) {
print('Save detection error: $e');
}
throw 'Gagal menyimpan data deteksi';
}
}
Stream<QuerySnapshot> getDetectionHistory(String uid) {
return _firestore
.collection(detectionsCollection)
.where('userId', isEqualTo: uid)
.orderBy('timestamp', descending: true)
.snapshots();
}
// Alert operations
Future<void> createAlert({
required String uid,
required String detectionId,
required Map<String, dynamic> alertData,
}) async {
try {
await _firestore.collection(alertsCollection).add({
'userId': uid,
'detectionId': detectionId,
'title': alertData['title'] ?? 'Fire Detected',
'message': alertData['message'] ?? 'Kebakaran terdeteksi',
'severity': alertData['severity'] ?? 'high',
'location': alertData['location'] ?? '',
'timestamp': FieldValue.serverTimestamp(),
'isRead': false,
'isResolved': false,
'createdAt': FieldValue.serverTimestamp(),
});
} catch (e) {
if (kDebugMode) {
print('Create alert error: $e');
}
throw 'Gagal membuat alert';
}
}
Stream<QuerySnapshot> getAlerts(String uid) {
return _firestore
.collection(alertsCollection)
.where('userId', isEqualTo: uid)
.orderBy('timestamp', descending: true)
.snapshots();
}
Future<void> markAlertAsRead(String alertId) async {
try {
await _firestore.collection(alertsCollection).doc(alertId).update({
'isRead': true,
});
} catch (e) {
if (kDebugMode) {
print('Mark alert as read error: $e');
}
throw 'Gagal menandai alert sebagai dibaca';
}
}
// Device operations
Future<void> registerDevice({
required String uid,
required Map<String, dynamic> deviceData,
}) async {
try {
await _firestore.collection(devicesCollection).add({
'userId': uid,
'deviceName': deviceData['deviceName'] ?? '',
'deviceType': deviceData['deviceType'] ?? 'camera',
'location': deviceData['location'] ?? '',
'status': 'active',
'lastSeen': FieldValue.serverTimestamp(),
'createdAt': FieldValue.serverTimestamp(),
});
} catch (e) {
if (kDebugMode) {
print('Register device error: $e');
}
throw 'Gagal mendaftarkan perangkat';
}
}
Stream<QuerySnapshot> getUserDevices(String uid) {
return _firestore
.collection(devicesCollection)
.where('userId', isEqualTo: uid)
.snapshots();
}
// Generic operations
Future<void> deleteDocument(String collection, String docId) async {
try {
await _firestore.collection(collection).doc(docId).delete();
} catch (e) {
if (kDebugMode) {
print('Delete document error: $e');
}
throw 'Gagal menghapus dokumen';
}
}
Future<List<QueryDocumentSnapshot>> getDocuments({
required String collection,
Query? query,
}) async {
try {
QuerySnapshot snapshot =
query != null
? await query.get()
: await _firestore.collection(collection).get();
return snapshot.docs;
} catch (e) {
if (kDebugMode) {
print('Get documents error: $e');
}
throw 'Gagal mengambil dokumen';
}
}
}

View File

@ -0,0 +1,116 @@
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/foundation.dart';
import '../models/sensor_data_model.dart';
class RealtimeDatabaseService {
final FirebaseDatabase _database = FirebaseDatabase.instance;
// Get database reference
DatabaseReference get _sensorRef => _database.ref().child('sensor_data');
// Stream untuk mendapatkan data sensor secara real-time
Stream<SensorData> get sensorDataStream {
return _sensorRef.onValue.map((event) {
if (event.snapshot.value != null) {
final data = Map<String, dynamic>.from(event.snapshot.value as Map);
return SensorData.fromJson(data);
}
return SensorData(
apiTerdeteksi: false,
buzzerActive: false,
exhaustActive: false,
gasValue: 0,
kelembapan: 0,
pumpActive: false,
suhu: 0.0,
);
});
}
// Get data sensor satu kali
Future<SensorData> getSensorData() async {
try {
final snapshot = await _sensorRef.get();
if (snapshot.value != null) {
final data = Map<String, dynamic>.from(snapshot.value as Map);
return SensorData.fromJson(data);
}
return SensorData(
apiTerdeteksi: false,
buzzerActive: false,
exhaustActive: false,
gasValue: 0,
kelembapan: 0,
pumpActive: false,
suhu: 0.0,
);
} catch (e) {
if (kDebugMode) {
print('Error getting sensor data: $e');
}
throw 'Gagal mengambil data sensor';
}
}
// Update kontrol sistem (buzzer, exhaust, pump)
Future<void> updateControl({
bool? buzzerActive,
bool? exhaustActive,
bool? pumpActive,
}) async {
try {
Map<String, dynamic> updates = {};
if (buzzerActive != null) {
updates['buzzer_active'] = buzzerActive;
}
if (exhaustActive != null) {
updates['exhaust_active'] = exhaustActive;
}
if (pumpActive != null) {
updates['pump_active'] = pumpActive;
}
if (updates.isNotEmpty) {
await _sensorRef.update(updates);
}
} catch (e) {
if (kDebugMode) {
print('Error updating control: $e');
}
throw 'Gagal mengupdate kontrol sistem';
}
}
// Reset all controls
Future<void> resetAllControls() async {
try {
await _sensorRef.update({
'buzzer_active': false,
'exhaust_active': false,
'pump_active': false,
});
} catch (e) {
if (kDebugMode) {
print('Error resetting controls: $e');
}
throw 'Gagal mereset kontrol sistem';
}
}
// Activate emergency mode
Future<void> activateEmergencyMode() async {
try {
await _sensorRef.update({
'buzzer_active': true,
'exhaust_active': true,
'pump_active': true,
});
} catch (e) {
if (kDebugMode) {
print('Error activating emergency mode: $e');
}
throw 'Gagal mengaktifkan mode darurat';
}
}
}

49
lib/utils/app_colors.dart Normal file
View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
class AppColors {
// Primary colors - Fire theme dengan orange-red gradasi
static const Color primaryRed = Color(0xFFFF4444);
static const Color primaryOrange = Color(0xFFFF6B35);
static const Color primaryDeepOrange = Color(0xFFE63946);
// Secondary colors - Smoke and safety theme
static const Color secondaryGray = Color(0xFF6C757D);
static const Color secondaryLightGray = Color(0xFFADB5BD);
static const Color secondaryDarkGray = Color(0xFF495057);
// Background colors
static const Color backgroundLight = Color(0xFFF8F9FA);
static const Color backgroundWhite = Color(0xFFFFFFFF);
static const Color backgroundDark = Color(0xFF212529);
// Accent colors
static const Color accentBlue = Color(0xFF0077BE);
static const Color primaryBlue = Color(0xFF007BFF);
static const Color accentGreen = Color(0xFF28A745);
static const Color accentYellow = Color(0xFFFFD700);
// Text colors
static const Color textPrimary = Color(0xFF212529);
static const Color textSecondary = Color(0xFF6C757D);
static const Color textLight = Color(0xFFFFFFFF);
static const Color textError = Color(0xFFDC3545);
// Gradients
static const LinearGradient primaryGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [primaryOrange, primaryRed],
);
static const LinearGradient backgroundGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [backgroundLight, backgroundWhite],
);
static const LinearGradient smokeGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [secondaryLightGray, secondaryGray],
);
}

171
lib/utils/app_icons.dart Normal file
View File

@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
class AppIcons {
// Icon API sederhana menggunakan Flutter Icons
static const IconData apiIcon = Icons.api;
static const IconData fireIcon = Icons.local_fire_department;
static const IconData sensorIcon = Icons.sensors;
static const IconData dashboardIcon = Icons.dashboard;
static const IconData settingsIcon = Icons.settings;
static const IconData networkIcon = Icons.wifi;
static const IconData databaseIcon = Icons.storage;
// Custom API Icon Widget
static Widget buildApiIcon({
double size = 24,
Color? color,
bool showBadge = false,
}) {
return Stack(
alignment: Alignment.center,
children: [
Icon(Icons.api, size: size, color: color ?? Colors.blue),
if (showBadge)
Positioned(
top: 0,
right: 0,
child: Container(
width: size * 0.3,
height: size * 0.3,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
),
],
);
}
// Custom Fire Detection Icon
static Widget buildFireDetectionIcon({
double size = 24,
Color? color,
bool isActive = false,
}) {
return Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.local_fire_department,
size: size,
color: isActive ? Colors.red : (color ?? Colors.grey),
),
if (isActive)
Positioned(
top: 0,
right: 0,
child: Container(
width: size * 0.25,
height: size * 0.25,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Icon(Icons.warning, color: Colors.white, size: 8),
),
),
],
);
}
// Custom Sensor Icon
static Widget buildSensorIcon({
double size = 24,
Color? color,
bool isOnline = true,
}) {
return Stack(
alignment: Alignment.center,
children: [
Icon(Icons.sensors, size: size, color: color ?? Colors.blue),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: size * 0.25,
height: size * 0.25,
decoration: BoxDecoration(
color: isOnline ? Colors.green : Colors.red,
shape: BoxShape.circle,
),
),
),
],
);
}
// Logo/Brand Icon untuk App
static Widget buildAppLogo({
double size = 48,
Color? primaryColor,
Color? secondaryColor,
}) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
primaryColor ?? Colors.blue,
secondaryColor ?? Colors.blueAccent,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: (primaryColor ?? Colors.blue).withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Icon(Icons.api, size: size * 0.6, color: Colors.white),
);
}
// Icon untuk berbagai status
static IconData getStatusIcon(String status) {
switch (status.toLowerCase()) {
case 'online':
case 'active':
return Icons.check_circle;
case 'offline':
case 'inactive':
return Icons.offline_bolt;
case 'warning':
return Icons.warning;
case 'error':
case 'danger':
return Icons.error;
case 'loading':
return Icons.refresh;
default:
return Icons.help;
}
}
// Color untuk berbagai status
static Color getStatusColor(String status) {
switch (status.toLowerCase()) {
case 'online':
case 'active':
case 'normal':
return Colors.green;
case 'offline':
case 'inactive':
return Colors.grey;
case 'warning':
return Colors.orange;
case 'error':
case 'danger':
return Colors.red;
case 'loading':
return Colors.blue;
default:
return Colors.grey;
}
}
}

101
lib/utils/app_theme.dart Normal file
View File

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
class AppTheme {
static ThemeData get lightTheme {
return ThemeData(
primarySwatch: Colors.deepOrange,
primaryColor: AppColors.primaryRed,
scaffoldBackgroundColor: AppColors.backgroundLight,
fontFamily: 'Roboto',
// AppBar theme
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.primaryRed,
foregroundColor: AppColors.textLight,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.textLight,
),
),
// Elevated button theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryRed,
foregroundColor: AppColors.textLight,
elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 32),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
// Input decoration theme
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.backgroundWhite,
contentPadding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 20,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.secondaryLightGray),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.secondaryLightGray),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.primaryRed, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.textError),
),
hintStyle: TextStyle(color: AppColors.secondaryGray, fontSize: 16),
labelStyle: TextStyle(color: AppColors.textSecondary, fontSize: 16),
),
// Text theme
textTheme: const TextTheme(
headlineLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
headlineMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
headlineSmall: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
bodyLarge: TextStyle(fontSize: 16, color: AppColors.textPrimary),
bodyMedium: TextStyle(fontSize: 14, color: AppColors.textSecondary),
bodySmall: TextStyle(fontSize: 12, color: AppColors.textSecondary),
),
// Card theme
cardTheme: CardTheme(
color: AppColors.backgroundWhite,
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shadowColor: AppColors.secondaryGray.withOpacity(0.2),
),
// Icon theme
iconTheme: const IconThemeData(color: AppColors.primaryRed, size: 24),
);
}
}

View File

@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;
import 'dart:io';
class IconGenerator {
static Future<void> generateAppIcons() async {
// Sizes untuk berbagai platform
final sizes = [
16, 32, 48, 64, 128, 256, 512, 1024, // Berbagai ukuran
];
for (final size in sizes) {
await _generateIcon(size);
}
}
static Future<void> _generateIcon(int size) async {
// Create a recorder to record drawing commands
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
// Background
final backgroundPaint =
Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawRRect(
RRect.fromLTRBR(
0,
0,
size.toDouble(),
size.toDouble(),
Radius.circular(size * 0.1),
),
backgroundPaint,
);
// API Icon shape
final iconPaint =
Paint()
..color = Colors.white
..style = PaintingStyle.fill;
final iconSize = size * 0.6;
final iconOffset = size * 0.2;
// Draw simple API icon (circles and lines)
// Center circle
canvas.drawCircle(
Offset(size * 0.5, size * 0.5),
iconSize * 0.15,
iconPaint,
);
// Side circles
canvas.drawCircle(
Offset(iconOffset + iconSize * 0.2, size * 0.5),
iconSize * 0.1,
iconPaint,
);
canvas.drawCircle(
Offset(iconOffset + iconSize * 0.8, size * 0.5),
iconSize * 0.1,
iconPaint,
);
// Connecting lines
final linePaint =
Paint()
..color = Colors.white
..strokeWidth = iconSize * 0.05
..style = PaintingStyle.stroke;
canvas.drawLine(
Offset(iconOffset + iconSize * 0.3, size * 0.5),
Offset(iconOffset + iconSize * 0.35, size * 0.5),
linePaint,
);
canvas.drawLine(
Offset(iconOffset + iconSize * 0.65, size * 0.5),
Offset(iconOffset + iconSize * 0.7, size * 0.5),
linePaint,
);
// End recording
final picture = recorder.endRecording();
final image = await picture.toImage(size, size);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData != null) {
final file = File('assets/icons/app_icon_${size}x$size.png');
await file.writeAsBytes(byteData.buffer.asUint8List());
print('Generated icon: ${file.path}');
}
}
// Simple icon untuk development
static Widget buildSimpleApiIcon({double size = 24, Color? color}) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: color ?? Colors.blue,
borderRadius: BorderRadius.circular(size * 0.1),
),
child: Stack(
alignment: Alignment.center,
children: [
// Center dot
Container(
width: size * 0.15,
height: size * 0.15,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
// Left dot
Positioned(
left: size * 0.2,
child: Container(
width: size * 0.1,
height: size * 0.1,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
),
// Right dot
Positioned(
right: size * 0.2,
child: Container(
width: size * 0.1,
height: size * 0.1,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
),
// Connecting lines
Positioned(
left: size * 0.3,
child: Container(
width: size * 0.05,
height: size * 0.02,
color: Colors.white,
),
),
Positioned(
right: size * 0.3,
child: Container(
width: size * 0.05,
height: size * 0.02,
color: Colors.white,
),
),
],
),
);
}
}

View File

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import '../utils/app_colors.dart';
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool isLoading;
final bool isOutlined;
final IconData? icon;
final double? width;
final double height;
final Color? backgroundColor;
const CustomButton({
Key? key,
required this.text,
this.onPressed,
this.isLoading = false,
this.isOutlined = false,
this.icon,
this.width,
this.height = 56,
this.backgroundColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
margin: const EdgeInsets.only(bottom: 16),
child:
isOutlined
? OutlinedButton(
onPressed: isLoading ? null : onPressed,
style: OutlinedButton.styleFrom(
side: const BorderSide(color: AppColors.primaryRed, width: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _buildButtonContent(),
)
: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor ?? AppColors.primaryRed,
foregroundColor: AppColors.textLight,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _buildButtonContent(),
),
);
}
Widget _buildButtonContent() {
if (isLoading) {
return const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(AppColors.textLight),
),
);
}
if (icon != null) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 20),
const SizedBox(width: 8),
Text(
text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isOutlined ? AppColors.primaryRed : AppColors.textLight,
),
),
],
);
}
return Text(
text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isOutlined ? AppColors.primaryRed : AppColors.textLight,
),
);
}
}

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import '../utils/app_colors.dart';
class CustomTextField extends StatefulWidget {
final String hintText;
final String? labelText;
final IconData? prefixIcon;
final bool obscureText;
final TextEditingController? controller;
final String? Function(String?)? validator;
final TextInputType? keyboardType;
final bool enabled;
final VoidCallback? onSuffixIconPressed;
const CustomTextField({
Key? key,
required this.hintText,
this.labelText,
this.prefixIcon,
this.obscureText = false,
this.controller,
this.validator,
this.keyboardType,
this.enabled = true,
this.onSuffixIconPressed,
}) : super(key: key);
@override
State<CustomTextField> createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
bool _isObscured = true;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
child: TextFormField(
controller: widget.controller,
obscureText: widget.obscureText ? _isObscured : false,
validator: widget.validator,
keyboardType: widget.keyboardType,
enabled: widget.enabled,
style: const TextStyle(fontSize: 16, color: AppColors.textPrimary),
decoration: InputDecoration(
hintText: widget.hintText,
labelText: widget.labelText,
prefixIcon:
widget.prefixIcon != null
? Icon(widget.prefixIcon, color: AppColors.primaryRed)
: null,
suffixIcon:
widget.obscureText
? IconButton(
icon: Icon(
_isObscured ? Icons.visibility_off : Icons.visibility,
color: AppColors.secondaryGray,
),
onPressed: () {
setState(() {
_isObscured = !_isObscured;
});
},
)
: null,
),
),
);
}
}

View File

@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import '../utils/app_colors.dart';
class SensorCard extends StatelessWidget {
final String title;
final String value;
final String unit;
final IconData icon;
final Color statusColor;
final String status;
final VoidCallback? onTap;
const SensorCard({
Key? key,
required this.title,
required this.value,
required this.unit,
required this.icon,
required this.statusColor,
required this.status,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.backgroundWhite, AppColors.backgroundLight],
),
),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: statusColor, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.textSecondary,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
const SizedBox(width: 4),
Text(
unit,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.textSecondary,
),
),
],
),
],
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
status,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: statusColor,
),
),
),
],
),
),
),
);
}
}
class ControlCard extends StatelessWidget {
final String title;
final String description;
final IconData icon;
final bool isActive;
final VoidCallback onToggle;
final Color activeColor;
const ControlCard({
Key? key,
required this.title,
required this.description,
required this.icon,
required this.isActive,
required this.onToggle,
required this.activeColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.backgroundWhite, AppColors.backgroundLight],
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: (isActive ? activeColor : AppColors.secondaryGray)
.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: isActive ? activeColor : AppColors.secondaryGray,
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
description,
style: const TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
),
),
],
),
),
Switch(
value: isActive,
onChanged: (_) => onToggle(),
activeColor: activeColor,
inactiveTrackColor: AppColors.secondaryLightGray,
),
],
),
),
);
}
}

1
linux/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
flutter/ephemeral

128
linux/CMakeLists.txt Normal file
View File

@ -0,0 +1,128 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "debartis")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.debartis")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View File

@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View File

@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}

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