Menambahkan ulang Aplikasi Mobile sebagai folder biasa

This commit is contained in:
Fajar 2025-07-04 10:28:55 +07:00
parent b164d092ee
commit df0dde0d46
185 changed files with 9976 additions and 2 deletions

@ -1 +0,0 @@
Subproject commit 9c33fe273876c6cf926e2947ecd463a933406690

43
Aplikasi Mobile/.gitignore vendored Normal file
View File

@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
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
Aplikasi Mobile/.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: "5874a72aa4c779a02553007c47dacbefba2374dc"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
- platform: android
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
- platform: ios
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
- platform: linux
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
- platform: macos
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
- platform: web
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
- platform: windows
create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc
# 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'

3
Aplikasi Mobile/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

16
Aplikasi Mobile/README.md Normal file
View File

@ -0,0 +1,16 @@
# siparkir
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.

View File

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

13
Aplikasi Mobile/android/.gitignore vendored Normal file
View File

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

View File

@ -0,0 +1,49 @@
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"
}
android {
namespace = "com.example.siparkir"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.siparkir"
// 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.debug
}
}
}
flutter {
source = "../.."
}
dependencies {}
apply plugin: 'com.google.gms.google-services'

View File

@ -0,0 +1,30 @@
{
"project_info": {
"project_number": "856252653498",
"firebase_url": "https://sistemparkir-cc7d8-default-rtdb.firebaseio.com",
"project_id": "sistemparkir-cc7d8",
"storage_bucket": "sistemparkir-cc7d8.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:856252653498:android:588d0c0b82858243548783",
"android_client_info": {
"package_name": "com.example.siparkir"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyB1Rk4TT8FR3662n7TpPYiES53fRpuvx-U"
}
],
"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,46 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:label="siparkir"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package com.example.siparkir
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: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

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

View File

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

View File

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

View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '2.0.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.15'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

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

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@ -0,0 +1,25 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
}
include ":app"

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

34
Aplikasi Mobile/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.siparkir;
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.siparkir.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.siparkir.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.siparkir.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.siparkir;
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.siparkir;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

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

View File

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

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>Siparkir</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>siparkir</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

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

View File

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

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

View File

@ -0,0 +1,55 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
class CekKartuController extends GetxController {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final kartuList = RxList<Map<String, dynamic>>([]);
final isLoading = true.obs;
@override
void onInit() {
super.onInit();
ambilDanCekKartu();
}
Future<void> ambilDanCekKartu() async {
isLoading.value = true;
try {
final uid = _auth.currentUser?.uid;
if (uid == null) {
Get.snackbar("Error", "User belum login.");
return;
}
final userSnapshot = await _firestore.collection('users').doc(uid).get();
final email = userSnapshot.data()?['email'];
if (email == null) {
Get.snackbar("Error", "Email user tidak ditemukan.");
return;
}
final kartuQuery = await _firestore
.collection('kartu_parkir')
.where('email', isEqualTo: email)
.get();
if (kartuQuery.docs.isNotEmpty) {
kartuList.assignAll(
kartuQuery.docs.map((doc) => doc.data()).toList(),
);
} else {
kartuList.clear();
}
} catch (e) {
Get.snackbar("Error", "Gagal memuat data: $e");
} finally {
isLoading.value = false;
}
}
}

View File

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/cek_kartu_controller.dart';
import '../../../widgets/kartu_karyawan.dart'; // pastikan path widget sesuai
class CekKartuView extends GetView<CekKartuController> {
const CekKartuView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 253, 253, 253),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
const SizedBox(width: 8),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Kartu Tap Parkir Karyawan',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
),
),
],
),
const SizedBox(height: 30),
Obx(() {
if (controller.isLoading.value) {
return const CircularProgressIndicator();
}
if (controller.kartuList.isEmpty) {
return Text(
"Belum ada kartu yang terdaftar.",
style: GoogleFonts.poppins(fontSize: 14),
);
}
return Expanded(
child: ListView.builder(
itemCount: controller.kartuList.length,
itemBuilder: (context, index) {
final data = controller.kartuList[index];
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: KartuKaryawanWidget(
nama: data['nama'] ?? '-',
divisi: data['divisi'] ?? '-',
platNomor: data['plat_nomor'] ?? '-',
rfid: data['rfid'] ?? '-',
),
);
},
),
);
}),
],
),
),
),
);
}
}

View File

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

View File

@ -0,0 +1,77 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:firebase_auth/firebase_auth.dart';
class DaftarKartuController extends GetxController {
final namaController = TextEditingController();
final teleponController = TextEditingController();
final divisiController = TextEditingController();
final platNomorController = TextEditingController();
final emailController = TextEditingController();
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@override
void onInit() {
super.onInit();
getUserData();
}
void getUserData() async {
try {
final uid = FirebaseAuth.instance.currentUser?.uid;
if (uid != null) {
final snapshot = await _firestore.collection('users').doc(uid).get();
if (snapshot.exists) {
final data = snapshot.data();
namaController.text = data?['name'] ?? '';
teleponController.text = data?['noTelp'] ?? '';
divisiController.text = data?['devisi'] ?? '';
emailController.text = data?['email'] ?? '';
} else {
Get.snackbar("Error", "Data user tidak ditemukan di Firestore.");
}
} else {
Get.snackbar("Error", "User belum login.");
}
} catch (e) {
Get.snackbar("Error", "Gagal mengambil data user: $e");
}
}
void simpanData() async {
if (platNomorController.text.trim().isEmpty) {
Get.snackbar("Validasi", "Plat Nomor tidak boleh kosong.");
return;
}
try {
await _firestore.collection('daftar_kartu_parkir').add({
'nama': namaController.text,
'noTelp': teleponController.text,
'divisi': divisiController.text,
'plat_nomor': platNomorController.text,
'email': emailController.text,
'timestamp': FieldValue.serverTimestamp(),
});
Get.snackbar("Berhasil", "Data berhasil disimpan.");
clearForm();
} catch (e) {
Get.snackbar("Error", "Gagal menyimpan data: $e");
}
}
void clearForm() {
platNomorController.clear();
}
@override
void onClose() {
namaController.dispose();
teleponController.dispose();
divisiController.dispose();
platNomorController.dispose();
emailController.dispose();
super.onClose();
}
}

View File

@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/daftar_kartu_controller.dart';
class DaftarKartuView extends GetView<DaftarKartuController> {
const DaftarKartuView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 253, 253, 253),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
_buildHeader(),
const SizedBox(height: 20),
_buildFormCard(),
],
),
),
),
),
);
}
Widget _buildHeader() {
return Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
const SizedBox(width: 8),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Pendaftaran Kartu Tap Parkir Motor Karyawan',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
),
),
],
);
}
Widget _buildInputField(String label, TextEditingController controller, {bool readOnly = false}) {
return TextField(
controller: controller,
readOnly: readOnly,
decoration: InputDecoration(
labelText: label,
labelStyle: GoogleFonts.poppins(
color: Colors.black87,
fontSize: 14,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: Colors.white,
),
);
}
Widget _buildFormCard() {
return Card(
color: Colors.white,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
_buildInputField("Nama", controller.namaController, readOnly: true),
const SizedBox(height: 12),
_buildInputField("No Telepon", controller.teleponController, readOnly: true),
const SizedBox(height: 12),
_buildInputField("Divisi", controller.divisiController, readOnly: true),
const SizedBox(height: 12),
_buildInputField("Email", controller.emailController, readOnly: true),
const SizedBox(height: 12),
_buildInputField("Plat Nomor", controller.platNomorController),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
onPressed: controller.simpanData,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"Daftar",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
}
}

View File

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

View File

@ -0,0 +1,154 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class HistoryParkirController extends GetxController {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final riwayat = <Map<String, dynamic>>[].obs;
final riwayatFiltered = <Map<String, dynamic>>[].obs;
final isLoading = false.obs;
final searchQuery = ''.obs;
@override
void onInit() {
super.onInit();
ambilRiwayat();
}
Future<void> ambilRiwayat() async {
isLoading.value = true;
try {
final snapshot = await _firestore
.collection('history_parkir')
.orderBy('time', descending: true)
.get();
List<Map<String, dynamic>> hasil = [];
for (var doc in snapshot.docs) {
final data = doc.data();
final uid = data['uid'];
// Ambil info dari kartu_parkir berdasarkan UID
final kartuSnapshot = await _firestore
.collection('kartu_parkir')
.where('uid', isEqualTo: uid)
.limit(1)
.get();
String nama = '-';
String plat = '-';
if (kartuSnapshot.docs.isNotEmpty) {
final kartuData = kartuSnapshot.docs.first.data();
nama = kartuData['nama']?.toString() ?? '-';
plat = kartuData['plat_nomor']?.toString() ?? '-';
}
hasil.add({
'uid': uid?.toString() ?? '-',
'nama': nama,
'plat': plat,
'activity': data['activity']?.toString() ?? '-',
'time': data['time'],
});
}
riwayat.value = hasil;
riwayatFiltered.value = List.from(hasil);
} catch (e) {
Get.snackbar("Error", "Gagal memuat riwayat: $e");
} finally {
isLoading.value = false;
}
}
void searchKartu(String query) {
searchQuery.value = query;
if (query.isEmpty) {
riwayatFiltered.value = List.from(riwayat);
} else {
riwayatFiltered.value = riwayat.where((item) {
final nama = item['nama'].toLowerCase();
final plat = item['plat'].toLowerCase();
return nama.contains(query.toLowerCase()) || plat.contains(query.toLowerCase());
}).toList();
}
}
Future<void> hapusSemuaRiwayat() async {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Konfirmasi Hapus",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 15),
const Text(
"Apakah kamu yakin ingin menghapus semua riwayat parkir?",
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => Get.back(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text("Batal", style: TextStyle(color: Colors.white)),
),
ElevatedButton(
onPressed: () async {
Get.back(); // tutup dialog
isLoading.value = true;
try {
final snapshot = await _firestore.collection('history_parkir').get();
for (var doc in snapshot.docs) {
await doc.reference.delete();
}
riwayat.clear();
riwayatFiltered.clear();
Get.snackbar("Berhasil", "Semua data riwayat telah dihapus.");
} catch (e) {
Get.snackbar("Error", "Gagal menghapus data: $e");
} finally {
isLoading.value = false;
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text("Hapus", style: TextStyle(color: Colors.white)),
),
],
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,206 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import '../controllers/history_parkir_controller.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class HistoryParkirView extends GetView<HistoryParkirController> {
const HistoryParkirView({super.key});
Future<void> _downloadPdf() async {
final pdf = pw.Document();
final controller = Get.find<HistoryParkirController>();
pdf.addPage(
pw.Page(
build: (pw.Context context) {
return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text('Riwayat Parkir', style: pw.TextStyle(fontSize: 18, fontWeight: pw.FontWeight.bold)),
pw.SizedBox(height: 12),
pw.Table.fromTextArray(
headers: ['UID', 'Nama', 'Plat', 'Aktivitas', 'Waktu'],
data: controller.riwayatFiltered.map((item) {
final time = item['time'] is Timestamp
? DateFormat('dd/MM/yyyy HH:mm').format((item['time'] as Timestamp).toDate())
: item['time']?.toString() ?? '-';
return [
item['uid'] ?? '-',
item['nama'] ?? '-',
item['plat'] ?? '-',
item['activity'] ?? '-',
time,
];
}).toList(),
cellStyle: const pw.TextStyle(fontSize: 10),
headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold),
headerDecoration: const pw.BoxDecoration(color: PdfColors.grey300),
cellAlignment: pw.Alignment.centerLeft,
),
],
);
},
),
);
await Printing.layoutPdf(
onLayout: (PdfPageFormat format) async => pdf.save(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 253, 253, 253),
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
heroTag: "hapusSemua",
onPressed: controller.hapusSemuaRiwayat,
backgroundColor: Colors.red,
child: const Icon(Icons.delete, color: Colors.white),
),
const SizedBox(height: 12),
FloatingActionButton(
heroTag: "downloadPdf",
onPressed: _downloadPdf,
backgroundColor: Colors.black,
child: const Icon(Icons.picture_as_pdf, color: Colors.white),
),
],
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
const SizedBox(width: 8),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Riwayat Parkir',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
const SizedBox(height: 20),
// 🔍 Search Field
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: TextField(
onChanged: controller.searchKartu,
decoration: InputDecoration(
hintText: 'Cari nama atau plat nomor...',
filled: true,
fillColor: Colors.black12,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
prefixIcon: const Icon(Icons.search),
),
),
),
Obx(() {
if (controller.isLoading.value) {
return const CircularProgressIndicator();
}
if (controller.riwayatFiltered.isEmpty) {
return Text(
"Belum ada riwayat parkir.",
style: GoogleFonts.poppins(fontSize: 14),
);
}
return Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columnSpacing: 20,
headingRowColor: MaterialStateColor.resolveWith((states) => Colors.black87),
headingTextStyle: GoogleFonts.poppins(
color: Colors.white,
fontWeight: FontWeight.bold,
),
dataTextStyle: GoogleFonts.poppins(
fontSize: 13,
color: Colors.black87,
),
columns: const [
DataColumn(label: Text("UID")),
DataColumn(label: Text("Nama")),
DataColumn(label: Text("Plat")),
DataColumn(label: Text("Aktivitas")),
DataColumn(label: Text("Waktu")),
],
rows: List<DataRow>.generate(
controller.riwayatFiltered.length,
(index) {
final item = controller.riwayatFiltered[index];
final formattedTime = item['time'] is Timestamp
? DateFormat('dd/MM/yyyy . HH:mm').format(
(item['time'] as Timestamp).toDate(),
)
: item['time']?.toString() ?? '-';
final isEven = index % 2 == 0;
return DataRow(
color: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
return isEven
? const Color(0xFFF7F7F7)
: const Color(0xFFEFEFEF);
},
),
cells: [
DataCell(Text(item['uid'] ?? '-')),
DataCell(Text(item['nama'] ?? '-')),
DataCell(Text(item['plat'] ?? '-')),
DataCell(Text(item['activity'] ?? '-')),
DataCell(Text(formattedTime)),
],
);
},
),
),
),
);
}),
],
),
),
),
);
}
}

View File

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

View File

@ -0,0 +1,205 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../../../routes/app_pages.dart';
class HomeController extends GetxController {
final DatabaseReference _database = FirebaseDatabase.instance.ref();
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final GetStorage storage = GetStorage();
final FlutterLocalNotificationsPlugin _notifPlugin = FlutterLocalNotificationsPlugin();
var username = "".obs;
var slot = 0.obs;
var keluar = 0.obs;
var masuk = 0.obs;
var jumlahMotor = 0.obs;
var newsList = <Map<String, dynamic>>[].obs;
var isLoadingNews = false.obs;
static const int totalKapasitas = 10;
@override
void onInit() {
super.onInit();
fetchUserData();
fetchParkingInfo();
fetchNews();
}
void fetchUserData() async {
User? user = _auth.currentUser;
if (user != null) {
final userDoc = await _firestore.collection('users').doc(user.uid).get();
username.value = userDoc['name'] ?? 'User';
}
}
void fetchParkingInfo() {
_database.child("info_parkir").onValue.listen((event) async {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data != null) {
final sisaSlot = int.tryParse(data['slot'].toString()) ?? 0;
slot.value = sisaSlot;
jumlahMotor.value = totalKapasitas - sisaSlot;
final previousSlot = storage.read('lastSlot') ?? -1;
if (sisaSlot <= 3 && sisaSlot != previousSlot) {
final now = DateTime.now();
final formattedTime = DateFormat('d MMMM yyyy - HH:mm', 'id_ID').format(now);
final notifData = {
"judul": "Slot Hampir Penuh",
"deskripsi": "Slot parkir motor tersisa $sisaSlot unit lagi.",
"waktu": formattedTime,
};
final existing = storage.read<List>('notifikasi') ?? [];
await _showNotification(notifData['judul']!, notifData['deskripsi']!);
storage.write('notifikasi', [...existing, notifData]);
storage.write('lastSlot', sisaSlot);
}
}
});
}
Future<void> _showNotification(String title, String body) async {
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
'channel_id',
'Siparkir Notifications',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
);
const NotificationDetails notifDetails = NotificationDetails(android: androidDetails);
await _notifPlugin.show(
0,
title,
body,
notifDetails,
payload: 'slot_notif',
);
}
void navigateTo(String menu) {
switch (menu) {
case 'Daftar Kartu':
Get.toNamed(Routes.DAFTAR_KARTU);
break;
case 'Cek Kartu':
Get.toNamed(Routes.CEK_KARTU);
break;
case 'Notifikasi':
Get.toNamed(Routes.NOTIFIKASI);
break;
default:
Get.snackbar("Oops", "Menu tidak dikenali");
}
}
void fetchNews() async {
isLoadingNews.value = true;
try {
final response = await http.get(Uri.parse('https://api-berita-indonesia.vercel.app/antara/otomotif/'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
final posts = data['data']['posts'] as List;
final formattedPosts = posts.map<Map<String, dynamic>>((e) {
final pubDate = e['pubDate'] ?? '';
String formattedDate = pubDate;
try {
final parsedDate = DateTime.parse(pubDate);
formattedDate = DateFormat('d MMMM yyyy', 'id_ID').format(parsedDate);
} catch (_) {
// Gunakan format asli jika gagal parsing
}
return {
...Map<String, dynamic>.from(e),
'pubDate': formattedDate,
};
}).toList();
newsList.value = formattedPosts;
} else {
Get.snackbar("Error", "Gagal memuat berita");
}
} catch (e) {
Get.snackbar("Error", "Terjadi kesalahan saat memuat berita");
} finally {
isLoadingNews.value = false;
}
}
void logout() async {
await _auth.signOut();
storage.remove("isLoggedIn");
storage.remove("userEmail");
Get.offAllNamed(Routes.LOGIN);
}
void showLogoutDialog() {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Konfirmasi Keluar",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 15),
const Text("Apakah kamu yakin untuk keluar?"),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: Get.back,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text("Batal", style: TextStyle(color: Colors.white)),
),
ElevatedButton(
onPressed: logout,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text("Ya", style: TextStyle(color: Colors.white)),
),
],
)
],
),
),
),
);
}
}

View File

@ -0,0 +1,311 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/home_controller.dart';
class HomeView extends StatelessWidget {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
final HomeController controller = Get.put(HomeController());
return Scaffold(
backgroundColor: const Color(0xFFF5FAF2),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Greeting Box + Logout
Obx(
() => Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
"Halo, ${controller.username.value.split(' ').first}!",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Icons.logout, color: Colors.black87),
onPressed: controller.showLogoutDialog,
),
],
),
),
),
const SizedBox(height: 20),
// Menu card
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: _menuItem("Daftar Kartu", Icons.credit_card, () => controller.navigateTo("Daftar Kartu"))),
const SizedBox(width: 8),
Expanded(child: _menuItem("Cek Kartu", Icons.qr_code_scanner, () => controller.navigateTo("Cek Kartu"))),
const SizedBox(width: 8),
Expanded(child: _menuItem("Notifikasi", Icons.notifications, () => controller.navigateTo("Notifikasi"))),
],
),
const SizedBox(height: 24),
Obx(() {
final slot = controller.slot.value;
final jumlahMotor = controller.jumlahMotor.value;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"Ketersediaan Slot Parkir Motor",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
const SizedBox(height: 12),
Row(
children: [
_slotItemCard("$slot", "Sisa Slot"),
_slotItemCard("$jumlahMotor", "Jumlah Motor"),
],
),
],
),
);
}),
const SizedBox(height: 24),
// News section
Obx(() {
if (controller.isLoadingNews.value) {
return const Center(child: CircularProgressIndicator());
}
if (controller.newsList.isEmpty) {
return const Center(child: Text("Tidak ada berita tersedia"));
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Berita terkini",
style: GoogleFonts.poppins(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
SizedBox(
height: 180,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: controller.newsList.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final news = controller.newsList[index];
return Container(
width: 160,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
spreadRadius: 2,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
child: Image.network(
news['thumbnail'] ?? '',
height: 90,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container(
height: 90,
color: Colors.grey[300],
child: const Icon(Icons.broken_image),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
news['title'] ?? 'Judul tidak tersedia',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
news['pubDate'] ?? '',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.grey[600],
),
),
],
),
),
],
),
);
},
),
),
],
),
);
}),
],
),
),
),
);
}
Widget _menuItem(String label, IconData icon, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.circular(14),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
blurRadius: 4,
),
],
),
child: Icon(icon, size: 28, color: Colors.black87),
),
const SizedBox(height: 6),
Text(
label,
style: GoogleFonts.poppins(fontSize: 13),
),
],
),
);
}
Widget _slotItemCard(String value, String label) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Column(
children: [
Text(
value,
style: GoogleFonts.poppins(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
label,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
}

View File

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

View File

@ -0,0 +1,195 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../../../routes/app_pages.dart';
class HomeAdminController extends GetxController {
final DatabaseReference _database = FirebaseDatabase.instance.ref();
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final GetStorage storage = GetStorage();
final FlutterLocalNotificationsPlugin _notifPlugin = FlutterLocalNotificationsPlugin();
var username = "".obs;
var slot = 0.obs;
var keluar = 0.obs;
var masuk = 0.obs;
var newsList = <Map<String, dynamic>>[].obs;
var isLoadingNews = false.obs;
@override
void onInit() {
super.onInit();
_initializeNotification();
fetchParkingInfo();
fetchNews();
}
void _initializeNotification() {
const android = AndroidInitializationSettings('@mipmap/ic_launcher');
const initSettings = InitializationSettings(android: android);
_notifPlugin.initialize(initSettings);
}
void fetchParkingInfo() {
_database.child("info_parkir").onValue.listen((event) async {
try {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data != null) {
final sisaSlot = int.tryParse(data['slot'].toString()) ?? 0;
slot.value = sisaSlot;
keluar.value = int.tryParse(data['keluar'].toString()) ?? 0;
masuk.value = int.tryParse(data['masuk'].toString()) ?? 0;
final previousSlot = storage.read('lastSlot') ?? -1;
if (sisaSlot <= 3 && sisaSlot != previousSlot) {
final now = DateTime.now();
final formattedTime = DateFormat('d MMMM yyyy - HH:mm', 'id_ID').format(now);
final notifData = {
"judul": "Slot Hampir Penuh",
"deskripsi": "Slot parkir motor tersisa $sisaSlot unit lagi.",
"waktu": formattedTime,
};
List<dynamic> existing = storage.read('notifikasi') ?? [];
existing.add(notifData);
await _showNotification(notifData['judul']!, notifData['deskripsi']!);
storage.write('notifikasi', existing);
storage.write('lastSlot', sisaSlot);
}
}
} catch (e) {
Get.snackbar("Error", "Gagal mengambil data parkir.");
}
});
}
Future<void> _showNotification(String title, String body) async {
const androidDetails = AndroidNotificationDetails(
'channel_id',
'Siparkir Notifications',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
);
const platformDetails = NotificationDetails(android: androidDetails);
await _notifPlugin.show(0, title, body, platformDetails, payload: 'slot_notif');
}
void navigateTo(String menu) {
switch (menu) {
case 'Persetujuan Kartu':
Get.toNamed(Routes.PERSETUJUAN_KARTU);
break;
case 'Kartu Terdaftar':
Get.toNamed(Routes.KARTU_TERDAFTAR);
break;
case 'Riwayat Parkir':
Get.toNamed(Routes.HISTORY_PARKIR);
break;
case 'Notifikasi':
Get.toNamed(Routes.NOTIFIKASI);
break;
default:
Get.snackbar("Oops", "Menu tidak dikenali");
}
}
void fetchNews() async {
isLoadingNews.value = true;
try {
final response = await http.get(Uri.parse('https://api-berita-indonesia.vercel.app/antara/otomotif/'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
final posts = data['data']['posts'] as List;
final formattedPosts = posts.map<Map<String, dynamic>>((e) {
final pubDate = e['pubDate'] ?? '';
String formattedDate = pubDate;
try {
final parsedDate = DateTime.parse(pubDate);
formattedDate = DateFormat('d MMMM yyyy', 'id_ID').format(parsedDate);
} catch (_) {}
return {
...Map<String, dynamic>.from(e),
'pubDate': formattedDate,
};
}).toList();
newsList.value = formattedPosts;
} else {
Get.snackbar("Error", "Gagal memuat berita");
}
} catch (e) {
Get.snackbar("Error", "Terjadi kesalahan saat memuat berita");
} finally {
isLoadingNews.value = false;
}
}
void logout() async {
await _auth.signOut();
storage.remove("isLoggedIn");
storage.remove("userEmail");
Get.offAllNamed(Routes.LOGIN);
}
void showLogoutDialog() {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Konfirmasi Keluar",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
),
const SizedBox(height: 15),
const Text("Apakah kamu yakin untuk keluar?"),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => Get.back(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text("Batal", style: TextStyle(color: Colors.white)),
),
ElevatedButton(
onPressed: logout,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text("Ya", style: TextStyle(color: Colors.white)),
),
],
)
],
),
),
),
);
}
}

View File

@ -0,0 +1,321 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_admin_controller.dart';
import 'package:google_fonts/google_fonts.dart';
class HomeAdminView extends StatelessWidget {
const HomeAdminView({super.key});
@override
Widget build(BuildContext context) {
final HomeAdminController controller = Get.put(HomeAdminController());
return Scaffold(
backgroundColor: const Color(0xFFF5FAF2),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Greeting Box + Logout
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
"Halo, Admin!",
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Icons.logout, color: Colors.black87),
onPressed: controller.showLogoutDialog,
),
],
),
),
const SizedBox(height: 20),
// Admin Menu
// Admin Menu
// Admin Menu
// Admin Menu
Wrap(
spacing: 12,
runSpacing: 12,
alignment: WrapAlignment.spaceBetween,
children: [
_menuItem("Pendaftar", Icons.assignment_turned_in, () => controller.navigateTo("Persetujuan Kartu")),
_menuItem("Terdaftar", Icons.credit_card, () => controller.navigateTo("Kartu Terdaftar")),
_menuItem("Riwayat", Icons.history, () => controller.navigateTo("Riwayat Parkir")),
_menuItem("Notifikasi", Icons.notifications, () => controller.navigateTo("Notifikasi")),
],
),
const SizedBox(height: 24),
// Slot parkir - Wrapped with Obx
Obx(() => Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"Ketersediaan Slot Parkir Motor",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
const SizedBox(height: 12),
Row(
children: [
_slotItemCard("${controller.slot.value}", "Total Slot"),
_slotItemCard("${controller.masuk.value}", "Motor Masuk"),
_slotItemCard("${controller.keluar.value}", "Motor Keluar"),
],
),
],
),
)),
const SizedBox(height: 24),
// News section - Wrapped with Obx
Obx(() {
if (controller.isLoadingNews.value) {
return const Center(child: CircularProgressIndicator());
}
if (controller.newsList.isEmpty) {
return Center(
child: Text(
"Tidak ada berita tersedia",
style: GoogleFonts.poppins(fontSize: 14),
),
);
}
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Berita terkini",
style: GoogleFonts.poppins(
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
SizedBox(
height: 200,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: controller.newsList.length,
separatorBuilder: (_, __) => const SizedBox(width: 12),
itemBuilder: (context, index) {
final news = controller.newsList[index];
return Container(
width: 160,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
spreadRadius: 2,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
child: Image.network(
news['thumbnail'] ?? '',
height: 90,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container(
height: 90,
color: Colors.grey[300],
child: const Icon(Icons.broken_image),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
news['title'] ?? 'Judul tidak tersedia',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
news['pubDate'] ?? '',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.grey[600],
),
),
],
),
),
],
),
);
},
),
),
],
),
);
}),
],
),
),
),
);
}
Widget _menuItem(String label, IconData icon, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: SizedBox(
width: MediaQuery.of(Get.context!).size.width / 4 - 18,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.circular(14),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
blurRadius: 4,
),
],
),
child: Icon(icon, size: 26, color: Colors.black87),
),
const SizedBox(height: 6),
Text(
label,
textAlign: TextAlign.center,
style: GoogleFonts.poppins(fontSize: 12),
),
],
),
),
);
}
Widget _slotItemCard(String value, String label) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 6,
spreadRadius: 3,
),
],
),
child: Column(
children: [
Text(
value,
style: GoogleFonts.poppins(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
label,
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
}

View File

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

View File

@ -0,0 +1,100 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';
import 'package:firebase_database/firebase_database.dart';
class KartuTerdaftarController extends GetxController {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseDatabase _database = FirebaseDatabase.instance;
final daftarKartu = <Map<String, dynamic>>[].obs;
final daftarKartuFiltered = <Map<String, dynamic>>[].obs;
final isLoading = false.obs;
final searchQuery = ''.obs;
@override
void onInit() {
super.onInit();
ambilSemuaKartu();
}
Future<void> ambilSemuaKartu() async {
isLoading.value = true;
try {
final snapshot = await _firestore.collection('kartu_parkir').get();
daftarKartu.value = snapshot.docs.map((doc) {
final data = doc.data();
data['id'] = doc.id;
return data;
}).toList();
daftarKartuFiltered.value = List.from(daftarKartu);
} catch (e) {
Get.snackbar("Error", "Gagal memuat data: $e");
} finally {
isLoading.value = false;
}
}
void searchKartu(String query) {
searchQuery.value = query;
if (query.isEmpty) {
daftarKartuFiltered.value = List.from(daftarKartu);
} else {
daftarKartuFiltered.value = daftarKartu.where((kartu) =>
kartu['nama'].toLowerCase().contains(query.toLowerCase()) ||
kartu['plat_nomor'].toLowerCase().contains(query.toLowerCase())
).toList();
}
}
Future<void> hapusKartu(String id) async {
try {
final doc = await _firestore.collection('kartu_parkir').doc(id).get();
final uid = doc.data()?['uid'];
await _firestore.collection('kartu_parkir').doc(id).delete();
if (uid != null && uid.toString().isNotEmpty) {
await _database.ref('daftar_kartu/$uid').remove();
}
daftarKartu.removeWhere((item) => item['id'] == id);
daftarKartuFiltered.removeWhere((item) => item['id'] == id);
Get.snackbar("Berhasil", "Data berhasil dihapus");
} catch (e) {
Get.snackbar("Error", "Gagal menghapus data: $e");
}
}
Future<void> updateKartu(String id, Map<String, dynamic> dataBaru) async {
try {
final doc = await _firestore.collection('kartu_parkir').doc(id).get();
final uidLama = doc.data()?['uid'];
// Update di Firestore
await _firestore.collection('kartu_parkir').doc(id).update(dataBaru);
final uidBaru = dataBaru['uid'];
// Hapus UID lama di Realtime Database (kalau berbeda)
if (uidLama != null && uidLama != uidBaru) {
await _database.ref('daftar_kartu/$uidLama').remove();
}
// Tambahkan UID baru
if (uidBaru != null) {
await _database.ref('daftar_kartu/$uidBaru').set({
"status": false,
"value": true,
});
}
ambilSemuaKartu();
Get.snackbar("Berhasil", "Data berhasil diperbarui");
} catch (e) {
Get.snackbar("Error", "Gagal memperbarui data: $e");
}
}
}

View File

@ -0,0 +1,229 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../widgets/kartu_karyawan_widget.dart';
import '../controllers/kartu_terdaftar_controller.dart';
class KartuTerdaftarView extends GetView<KartuTerdaftarController> {
const KartuTerdaftarView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 253, 253, 253),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
const SizedBox(width: 8),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Daftar Kartu Terdaftar',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
),
),
],
),
const SizedBox(height: 20),
// 👉 Jumlah kartu
Obx(() => Align(
alignment: Alignment.centerLeft,
child: Text(
'Jumlah Kartu Terdaftar: ${controller.daftarKartu.length}',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
)),
const SizedBox(height: 8),
// 🔍 Search Field
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: TextField(
onChanged: controller.searchKartu,
decoration: InputDecoration(
hintText: 'Cari nama atau plat nomor...',
filled: true,
fillColor: Colors.black12,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
prefixIcon: const Icon(Icons.search),
),
),
),
Obx(() {
if (controller.isLoading.value) {
return const CircularProgressIndicator();
}
if (controller.daftarKartuFiltered.isEmpty) {
return Text(
"Belum ada kartu yang terdaftar.",
style: GoogleFonts.poppins(fontSize: 14),
);
}
return Expanded(
child: ListView.builder(
itemCount: controller.daftarKartuFiltered.length,
itemBuilder: (context, index) {
final data = controller.daftarKartuFiltered[index];
return KartuKaryawanWidget(
nama: data['nama'] ?? '-',
divisi: data['divisi'] ?? '-',
platNomor: data['plat_nomor'] ?? '-',
rfid: data['rfid'] ?? '-',
uid: data['uid'] ?? '-', // Tambahkan UID
onEdit: () => _showEditModal(context, controller, data),
onHapus: () => controller.hapusKartu(data['id']),
);
},
),
);
}),
],
),
),
),
);
}
void _showEditModal(BuildContext context, KartuTerdaftarController controller, Map<String, dynamic> data) {
final namaC = TextEditingController(text: data['nama']);
final emailC = TextEditingController(text: data['email']);
final noHpC = TextEditingController(text: data['noTelp']);
final divisiC = TextEditingController(text: data['divisi']);
final platNomorC = TextEditingController(text: data['plat_nomor']);
final uidC = TextEditingController(text: data['uid']);
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
backgroundColor: Colors.grey[700],
child: Padding(
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Edit Data Karyawan",
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 20),
_buildTextField("Nama", namaC),
const SizedBox(height: 10),
_buildTextField("Email", emailC),
const SizedBox(height: 10),
_buildTextField("No HP", noHpC),
const SizedBox(height: 10),
_buildTextField("Divisi", divisiC),
const SizedBox(height: 10),
_buildTextField("Plat Nomor", platNomorC),
const SizedBox(height: 10),
_buildTextField("UID", uidC),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
style: TextButton.styleFrom(foregroundColor: Colors.white),
child: Text("Batal", style: GoogleFonts.poppins()),
),
const SizedBox(width: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed: () {
if (namaC.text.isEmpty ||
emailC.text.isEmpty ||
noHpC.text.isEmpty ||
divisiC.text.isEmpty ||
platNomorC.text.isEmpty ||
uidC.text.isEmpty) {
Get.snackbar("Validasi", "Semua field harus diisi!");
return;
}
controller.updateKartu(data['id'], {
'nama': namaC.text,
'email': emailC.text,
'noTelp': noHpC.text,
'divisi': divisiC.text,
'plat_nomor': platNomorC.text,
'uid': uidC.text,
});
Navigator.pop(context);
},
child: Text("Simpan", style: GoogleFonts.poppins()),
),
],
)
],
),
),
),
),
);
}
Widget _buildTextField(String label, TextEditingController controller) {
return TextField(
controller: controller,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.white70),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white38),
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
),
);
}
}

View File

@ -0,0 +1,11 @@
import 'package:get/get.dart';
import '../controllers/login_controller.dart';
class LoginBinding extends Bindings {
@override
void dependencies() {
Get.put(LoginController(), permanent: true); // <- ini fix-nya
}
}

View File

@ -0,0 +1,189 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get_storage/get_storage.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:siparkir/app/routes/app_pages.dart';
class LoginController extends GetxController {
var emailController = TextEditingController();
var passwordController = TextEditingController();
var emailResetController = TextEditingController(); // Menambahkan deklarasi emailResetController
var isLoading = false.obs;
final FirebaseAuth _auth = FirebaseAuth.instance;
final GetStorage storage = GetStorage();
void login() async {
String email = emailController.text.trim();
String password = passwordController.text.trim();
if (email.isEmpty || password.isEmpty) {
showSnackbar("Error", "Email dan password tidak boleh kosong", Colors.redAccent);
return;
}
try {
isLoading.value = true;
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
isLoading.value = false;
showSnackbar("Success", "Login berhasil", Colors.black);
storage.write("isLoggedIn", true);
storage.write("userEmail", userCredential.user?.email);
Get.offAllNamed(Routes.HOME);
} on FirebaseAuthException catch (e) {
isLoading.value = false;
String errorMessage;
switch (e.code) {
case 'invalid-email':
errorMessage = "Format email tidak valid.";
break;
case 'user-not-found':
errorMessage = "Akun tidak ditemukan. Periksa kembali email Anda.";
break;
case 'wrong-password':
errorMessage = "Password salah. Coba lagi.";
break;
case 'too-many-requests':
errorMessage = "Terlalu banyak percobaan. Coba lagi nanti.";
break;
default:
errorMessage = "Terjadi kesalahan. Coba lagi nanti.";
}
showSnackbar("Login Gagal", errorMessage, Colors.redAccent);
} catch (e) {
isLoading.value = false;
showSnackbar("Error", "Terjadi kesalahan: ${e.toString()}", Colors.redAccent);
}
}
void resetPassword(String email) async {
if (email.isEmpty) {
showSnackbar("Error", "Email tidak boleh kosong", Colors.redAccent);
return;
}
try {
await _auth.sendPasswordResetEmail(email: email);
showSnackbar("Berhasil", "Tautan reset kata sandi telah dikirim ke email Anda.", Colors.black);
} on FirebaseAuthException catch (e) {
String errorMessage;
switch (e.code) {
case 'invalid-email':
errorMessage = "Format email tidak valid.";
break;
case 'user-not-found':
errorMessage = "Pengguna dengan email ini tidak ditemukan.";
break;
default:
errorMessage = "Terjadi kesalahan. Coba lagi nanti.";
}
showSnackbar("Gagal", errorMessage, Colors.redAccent);
} catch (e) {
showSnackbar("Error", "Terjadi kesalahan: ${e.toString()}", Colors.redAccent);
}
}
void showForgotPasswordDialog() {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Reset Kata Sandi",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
fontSize: 18,
color: Colors.black,
),
),
const SizedBox(height: 15),
Text(
"Masukkan email yang terdaftar untuk menerima tautan reset.",
style: GoogleFonts.poppins(),
textAlign: TextAlign.center,
),
const SizedBox(height: 15),
TextField(
controller: emailResetController,
decoration: InputDecoration(
labelText: "Email",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: Get.back,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
"Batal",
style: TextStyle(color: Colors.white),
),
),
ElevatedButton(
onPressed: () async {
final String email = emailResetController.text.trim();
Navigator.of(Get.context!).pop();
resetPassword(email);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
"Kirim",
style: TextStyle(color: Colors.white),
),
),
],
),
],
),
),
),
);
}
void showSnackbar(String title, String message, Color backgroundColor) {
Get.snackbar(
title,
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: backgroundColor,
colorText: Colors.white,
);
}
@override
void onClose() {
emailController.dispose();
passwordController.dispose();
emailResetController.dispose(); // Menambahkan dispose untuk emailResetController
super.onClose();
}
}

View File

@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/login_controller.dart';
class LoginView extends GetView<LoginController> {
const LoginView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 253, 253, 253),
body: SafeArea(
child: Stack(
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.admin_panel_settings, color: Colors.black, size: 32),
onPressed: () {
Get.toNamed('/login-admin'); // route ke halaman admin
},
),
),
Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildGreetingSection(),
const SizedBox(height: 20),
_buildLoginCard(context),
],
),
),
),
),
],
),
),
);
}
Widget _buildGreetingSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Selamat Datang!",
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
Text(
"Silakan masuk untuk melanjutkan",
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.black54,
),
),
],
);
}
Widget _buildLoginCard(BuildContext context) {
return Card(
color: Colors.white,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildInputField("Email", controller.emailController, false),
const SizedBox(height: 15),
_buildInputField("Kata Sandi", controller.passwordController, true),
const SizedBox(height: 0),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
// Panggil showForgotPasswordDialog yang ada di controller
controller.showForgotPasswordDialog();
},
child: Text(
"Lupa Kata Sandi?",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
),
),
_buildLoginButton(),
const SizedBox(height: 10),
_buildSignUpOption(),
],
),
),
);
}
Widget _buildSignUpOption() {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Belum punya akun? ",
style: GoogleFonts.poppins(fontSize: 14, color: Colors.black54),
),
TextButton(
onPressed: () => Get.toNamed('/signup'),
child: Text(
"Daftar",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
],
),
);
}
Widget _buildInputField(String label, TextEditingController controller, bool isObscure) {
return TextField(
controller: controller,
obscureText: isObscure,
decoration: InputDecoration(
labelText: label,
labelStyle: GoogleFonts.poppins(
color: Colors.black54,
fontSize: 14,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: Colors.white,
),
);
}
Widget _buildLoginButton() {
return SizedBox(
width: double.infinity,
child: Obx(() => ElevatedButton(
onPressed: controller.isLoading.value ? null : controller.login,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: controller.isLoading.value
? const CircularProgressIndicator(color: Colors.white)
: Text(
"Masuk",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
)),
);
}
}

View File

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

View File

@ -0,0 +1,78 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get_storage/get_storage.dart';
import 'package:siparkir/app/routes/app_pages.dart';
class LoginAdminController extends GetxController {
var emailController = TextEditingController();
var passwordController = TextEditingController();
var secretCodeController = TextEditingController();
var isLoading = false.obs;
final FirebaseAuth _auth = FirebaseAuth.instance;
final GetStorage storage = GetStorage();
final String secretCode = "ICON33";
final String allowedAdminEmail = "admin@gmail.com"; // <- tambahkan di sini
void loginAdmin() async {
String email = emailController.text.trim();
String password = passwordController.text.trim();
String code = secretCodeController.text.trim();
if (email.isEmpty || password.isEmpty || code.isEmpty) {
showSnackbar("Error", "Semua kolom harus diisi", Colors.redAccent);
return;
}
if (email != allowedAdminEmail) {
showSnackbar("Email Tidak Diizinkan", "Hanya email admin yang boleh login", Colors.redAccent);
return;
}
if (code != secretCode) {
showSnackbar("Kode Rahasia Salah", "Kode tidak sesuai", Colors.redAccent);
return;
}
try {
isLoading.value = true;
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
isLoading.value = false;
showSnackbar("Success", "Login admin berhasil", Colors.black);
storage.write("isLoggedIn", true);
storage.write("userEmail", userCredential.user?.email);
storage.write("isAdmin", true);
Get.offAllNamed(Routes.HOME_ADMIN); // arahkan ke home admin
} on FirebaseAuthException catch (e) {
isLoading.value = false;
showSnackbar("Login Gagal", e.message ?? "Terjadi kesalahan", Colors.redAccent);
}
}
void showSnackbar(String title, String message, Color backgroundColor) {
Get.snackbar(
title,
message,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: backgroundColor,
colorText: Colors.white,
);
}
@override
void onClose() {
emailController.dispose();
passwordController.dispose();
secretCodeController.dispose();
super.onClose();
}
}

View File

@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controllers/login_admin_controller.dart';
class LoginAdminView extends GetView<LoginAdminController> {
const LoginAdminView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromARGB(255, 253, 253, 253),
body: SafeArea(
child: Stack(
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.person, color: Colors.black, size: 32),
onPressed: () {
Get.toNamed('/login'); // Kosongkan jika tidak ingin diarahkan kemana-mana
},
),
),
Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildGreetingSection(),
const SizedBox(height: 20),
_buildLoginCard(),
],
),
),
),
),
],
),
),
);
}
Widget _buildGreetingSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Selamat Datang Admin!",
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
Text(
"Silakan masuk sebagai admin",
style: GoogleFonts.poppins(
fontSize: 16,
color: Colors.black54,
),
),
],
);
}
Widget _buildLoginCard() {
return Card(
color: Colors.white,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildInputField("Email", controller.emailController, false),
const SizedBox(height: 15),
_buildInputField("Kata Sandi", controller.passwordController, true),
const SizedBox(height: 15),
_buildInputField("Kode Rahasia", controller.secretCodeController, true),
const SizedBox(height: 20),
_buildLoginButton(),
],
),
),
);
}
Widget _buildInputField(String label, TextEditingController controller, bool isObscure) {
return TextField(
controller: controller,
obscureText: isObscure,
decoration: InputDecoration(
labelText: label,
labelStyle: GoogleFonts.poppins(
color: Colors.black54,
fontSize: 14,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: Colors.white,
),
);
}
Widget _buildLoginButton() {
return SizedBox(
width: double.infinity,
child: Obx(() => ElevatedButton(
onPressed: controller.isLoading.value ? null : controller.loginAdmin,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: controller.isLoading.value
? const CircularProgressIndicator(color: Colors.white)
: Text(
"Masuk",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
)),
);
}
}

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