commit 0fb0f7c5480d51fade9ed6d806908d0064842722
Author: Fadhlul Wafi <202310715188@mhs.ubharajaya.aac.id>
Date: Thu Jan 15 15:58:24 2026 +0700
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..1db0e60
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+kasirapp
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..4c2dbaa
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..ac6e7a6
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b2c751a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.kotlin/errors/errors-1765439590219.log b/.kotlin/errors/errors-1765439590219.log
new file mode 100644
index 0000000..88b88b0
--- /dev/null
+++ b/.kotlin/errors/errors-1765439590219.log
@@ -0,0 +1,45 @@
+kotlin version: 2.0.21
+error message: Failed connecting to the daemon in 4 retries
+
+error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
+java.lang.RuntimeException: Could not connect to Kotlin compile daemon
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
+ at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
+ at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
+ at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
+ at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
+ at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
+ at org.gradle.internal.Factories$1.create(Factories.java:31)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
+ at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+
+
diff --git a/.kotlin/errors/errors-1765747395531.log b/.kotlin/errors/errors-1765747395531.log
new file mode 100644
index 0000000..88b88b0
--- /dev/null
+++ b/.kotlin/errors/errors-1765747395531.log
@@ -0,0 +1,45 @@
+kotlin version: 2.0.21
+error message: Failed connecting to the daemon in 4 retries
+
+error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
+java.lang.RuntimeException: Could not connect to Kotlin compile daemon
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
+ at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76)
+ at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
+ at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
+ at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
+ at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
+ at org.gradle.internal.Factories$1.create(Factories.java:31)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
+ at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+
+
diff --git a/.kotlin/errors/errors-1765978610358.log b/.kotlin/errors/errors-1765978610358.log
new file mode 100644
index 0000000..1219b50
--- /dev/null
+++ b/.kotlin/errors/errors-1765978610358.log
@@ -0,0 +1,4 @@
+kotlin version: 2.0.21
+error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
+ 1. Kotlin compile daemon is ready
+
diff --git a/.kotlin/errors/errors-1766255274355.log b/.kotlin/errors/errors-1766255274355.log
new file mode 100644
index 0000000..ccc8943
--- /dev/null
+++ b/.kotlin/errors/errors-1766255274355.log
@@ -0,0 +1,58 @@
+kotlin version: 2.0.21
+error message: java.lang.IncompatibleClassChangeError: class com.google.devtools.ksp.common.PersistentMap cannot inherit from final class org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap
+ at java.base/java.lang.ClassLoader.defineClass1(Native Method)
+ at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
+ at java.base/java.security.SecureClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.base/java.net.URLClassLoader.findClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.defineClass1(Native Method)
+ at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
+ at java.base/java.security.SecureClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.base/java.net.URLClassLoader.findClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at com.google.devtools.ksp.common.IncrementalContextBase.(IncrementalContextBase.kt:103)
+ at com.google.devtools.ksp.IncrementalContext.(IncrementalContext.kt:64)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:192)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:189)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:189)
+ at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
+ at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
+ at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
+ at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
+ at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
+ at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
+ at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
+ at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
+ at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1555)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
+ at java.base/java.lang.reflect.Method.invoke(Unknown Source)
+ at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+
+
diff --git a/.kotlin/errors/errors-1766255487235.log b/.kotlin/errors/errors-1766255487235.log
new file mode 100644
index 0000000..ccc8943
--- /dev/null
+++ b/.kotlin/errors/errors-1766255487235.log
@@ -0,0 +1,58 @@
+kotlin version: 2.0.21
+error message: java.lang.IncompatibleClassChangeError: class com.google.devtools.ksp.common.PersistentMap cannot inherit from final class org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap
+ at java.base/java.lang.ClassLoader.defineClass1(Native Method)
+ at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
+ at java.base/java.security.SecureClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.base/java.net.URLClassLoader.findClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.defineClass1(Native Method)
+ at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
+ at java.base/java.security.SecureClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader.defineClass(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.net.URLClassLoader$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.base/java.net.URLClassLoader.findClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
+ at com.google.devtools.ksp.common.IncrementalContextBase.(IncrementalContextBase.kt:103)
+ at com.google.devtools.ksp.IncrementalContext.(IncrementalContext.kt:64)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:192)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:189)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414)
+ at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:189)
+ at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
+ at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
+ at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
+ at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
+ at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
+ at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
+ at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
+ at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
+ at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
+ at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1555)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
+ at java.base/java.lang.reflect.Method.invoke(Unknown Source)
+ at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..84d9a71
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,53 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.ksp)
+}
+
+android {
+ namespace = "com.example.kasirapp"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "com.example.kasirapp"
+ minSdk = 24
+ targetSdk = 36
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ implementation(libs.androidx.activity)
+ implementation(libs.androidx.constraintlayout)
+ implementation(libs.mpandroidchart)
+ implementation(libs.androidx.room.runtime)
+ implementation(libs.androidx.room.ktx)
+ ksp(libs.androidx.room.compiler)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/example/kasirapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/kasirapp/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..95c5ed5
--- /dev/null
+++ b/app/src/androidTest/java/com/example/kasirapp/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.kasirapp
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.kasirapp", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8c01225
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..c8dfaba
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/example/kasirapp/AdminDashboardActivity.kt b/app/src/main/java/com/example/kasirapp/AdminDashboardActivity.kt
new file mode 100644
index 0000000..8e31839
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/AdminDashboardActivity.kt
@@ -0,0 +1,48 @@
+package com.example.kasirapp
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Button
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+
+class AdminDashboardActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_admin_dashboard)
+
+ val btnManageMenu = findViewById(R.id.btnManageMenu)
+ val btnSalesReport = findViewById(R.id.btnSalesReport)
+ val btnSalesTraffic = findViewById(R.id.btnSalesTraffic)
+ val btnPaymentManagement = findViewById(R.id.btnPaymentManagement)
+ val btnLogoutAdmin = findViewById(R.id.btnLogoutAdmin)
+
+ btnManageMenu.setOnClickListener {
+ val intent = Intent(this, ManageMenuActivity::class.java)
+ startActivity(intent)
+ }
+
+ btnSalesReport.setOnClickListener {
+ val intent = Intent(this, SalesReportActivity::class.java)
+ startActivity(intent)
+ }
+
+ btnSalesTraffic.setOnClickListener {
+ val intent = Intent(this, SalesTrafficActivity::class.java)
+ startActivity(intent)
+ }
+
+ btnPaymentManagement.setOnClickListener {
+ val intent = Intent(this, PaymentManagementActivity::class.java)
+ startActivity(intent)
+ }
+
+ btnLogoutAdmin.setOnClickListener {
+ val intent = Intent(this, LoginActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ startActivity(intent)
+ finish()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kasirapp/CartAdapter.kt b/app/src/main/java/com/example/kasirapp/CartAdapter.kt
new file mode 100644
index 0000000..0f5ef8e
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/CartAdapter.kt
@@ -0,0 +1,64 @@
+package com.example.kasirapp
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import java.text.NumberFormat
+import java.util.Locale
+
+class CartAdapter(
+ private var cartItems: List,
+ private val onIncrease: (MainActivity.CartItem) -> Unit,
+ private val onDecrease: (MainActivity.CartItem) -> Unit,
+ private val onRemove: (MainActivity.CartItem) -> Unit,
+ private val onNoteClick: (MainActivity.CartItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ fun updateData(newItems: List) {
+ this.cartItems = newItems
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_cart, parent, false)
+ return CartViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
+ holder.bind(cartItems[position])
+ }
+
+ override fun getItemCount(): Int = cartItems.size
+
+ inner class CartViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val name: TextView = itemView.findViewById(R.id.tv_cart_item_name)
+ private val price: TextView = itemView.findViewById(R.id.tv_cart_item_total_price)
+ private val quantity: TextView = itemView.findViewById(R.id.tv_cart_item_quantity)
+ private val note: TextView = itemView.findViewById(R.id.tv_cart_item_note)
+ private val increaseBtn: ImageButton = itemView.findViewById(R.id.btn_increase_quantity)
+ private val decreaseBtn: ImageButton = itemView.findViewById(R.id.btn_decrease_quantity)
+ private val removeBtn: ImageButton = itemView.findViewById(R.id.btn_remove_from_cart)
+ private val currencyFormat = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
+
+ fun bind(item: MainActivity.CartItem) {
+ name.text = item.product.name
+ quantity.text = item.quantity.toString()
+ price.text = currencyFormat.format((item.product.price * item.quantity).toLong())
+
+ if (item.note.isNullOrBlank()) {
+ note.visibility = View.GONE
+ } else {
+ note.visibility = View.VISIBLE
+ note.text = "Catatan: ${item.note}"
+ }
+
+ increaseBtn.setOnClickListener { onIncrease(item) }
+ decreaseBtn.setOnClickListener { onDecrease(item) }
+ removeBtn.setOnClickListener { onRemove(item) }
+ itemView.setOnClickListener { onNoteClick(item) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kasirapp/LoginActivity.kt b/app/src/main/java/com/example/kasirapp/LoginActivity.kt
new file mode 100644
index 0000000..4364d46
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/LoginActivity.kt
@@ -0,0 +1,58 @@
+package com.example.kasirapp
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.*
+import androidx.appcompat.app.AppCompatActivity
+
+class LoginActivity : AppCompatActivity() {
+
+ private val adminPin = "1234"
+ private val kasirPin = "0000"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val layout = LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ setPadding(64, 128, 64, 64)
+ }
+
+ val title = TextView(this).apply {
+ text = "Login Kasir/Admin"
+ textSize = 24f
+ }
+
+ val pinInput = EditText(this).apply {
+ hint = "Masukkan PIN"
+ inputType = android.text.InputType.TYPE_CLASS_NUMBER or android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD
+ }
+
+ val loginButton = Button(this).apply {
+ text = "Login"
+ setOnClickListener {
+ val pin = pinInput.text.toString()
+ val role = when (pin) {
+ adminPin -> "admin"
+ kasirPin -> "kasir"
+ else -> null
+ }
+
+ if (role != null) {
+ val intent = Intent(this@LoginActivity, MainActivity::class.java)
+ intent.putExtra("role", role)
+ startActivity(intent)
+ finish()
+ } else {
+ Toast.makeText(this@LoginActivity, "PIN salah", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ layout.addView(title)
+ layout.addView(pinInput)
+ layout.addView(loginButton)
+
+ setContentView(layout)
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/MainActivity.kt b/app/src/main/java/com/example/kasirapp/MainActivity.kt
new file mode 100644
index 0000000..5181149
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/MainActivity.kt
@@ -0,0 +1,402 @@
+package com.example.kasirapp
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ArrayAdapter
+import android.widget.Button
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.cardview.widget.CardView
+import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.kasirapp.data.AppDatabase
+import com.example.kasirapp.data.Product
+import com.example.kasirapp.data.Transaction
+import com.example.kasirapp.data.TransactionItem
+import kotlinx.coroutines.launch
+import java.text.NumberFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+class MainActivity : AppCompatActivity() {
+
+ // region Data Classes & Enums
+ data class CartItem(val product: Product, var quantity: Int, var note: String? = null)
+
+ enum class OrderMode { DINE_IN_INDOOR, DINE_IN_OUTDOOR, TAKEAWAY }
+ // endregion
+
+ // region State & Variables
+ private var fullProductList = listOf()
+ private val cart = mutableListOf()
+ private val db by lazy { AppDatabase.getDatabase(this) }
+ private lateinit var productAdapter: ProductAdapter
+ private lateinit var cartAdapter: CartAdapter
+
+ private lateinit var rvProducts: RecyclerView
+ private lateinit var rvCart: RecyclerView
+ private lateinit var totalText: TextView
+ private lateinit var emptyCartText: TextView
+ private lateinit var searchEditText: EditText
+
+ private var customerName: String = ""
+ private var tableNumber: Int = 0
+ private var currentOrderMode: OrderMode = OrderMode.DINE_IN_INDOOR
+ private lateinit var userRole: String
+ // endregion
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ userRole = intent.getStringExtra("role") ?: "kasir"
+
+ if (userRole == "admin") {
+ startActivity(Intent(this, AdminDashboardActivity::class.java))
+ finish()
+ return
+ }
+
+ setContentView(R.layout.activity_main)
+
+ initializeViews()
+ setupRecyclerViews()
+ setupSearch()
+
+ findViewById(R.id.subtitle_text).text = "Selamat datang, ${userRole.replaceFirstChar { it.uppercase() }}!"
+
+ findViewById(R.id.btn_logout).setOnClickListener { logout() }
+ findViewById(R.id.btn_dine_in).setOnClickListener { showSeatingChoiceDialog() }
+ findViewById(R.id.btn_takeaway).setOnClickListener {
+ currentOrderMode = OrderMode.TAKEAWAY
+ Toast.makeText(this, "Mode Takeaway Dipilih", Toast.LENGTH_SHORT).show()
+ }
+ findViewById(R.id.btn_clear_cart).setOnClickListener { clearCart() }
+ findViewById(R.id.btn_checkout).setOnClickListener { checkout() }
+
+ loadProducts()
+ updateCartDisplay()
+ }
+
+ private fun initializeViews() {
+ rvProducts = findViewById(R.id.rv_products)
+ rvCart = findViewById(R.id.rv_cart)
+ emptyCartText = findViewById(R.id.empty_cart_text)
+ totalText = findViewById(R.id.total_text)
+ searchEditText = findViewById(R.id.et_search_menu)
+ }
+
+ private fun setupRecyclerViews() {
+ // Products RecyclerView
+ productAdapter = ProductAdapter(emptyList()) { product ->
+ addToCart(product)
+ }
+ rvProducts.adapter = productAdapter
+ rvProducts.layoutManager = GridLayoutManager(this, 2)
+
+ // Cart RecyclerView
+ cartAdapter = CartAdapter(cart,
+ onIncrease = { item -> increaseQuantity(item) },
+ onDecrease = { item -> decreaseQuantity(item) },
+ onRemove = { item -> removeFromCart(item) },
+ onNoteClick = { item -> showNoteDialog(item) }
+ )
+ rvCart.adapter = cartAdapter
+ rvCart.layoutManager = LinearLayoutManager(this)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (userRole == "kasir") {
+ loadProducts()
+ }
+ }
+
+ private fun setupSearch() {
+ searchEditText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ filterProducts(s.toString())
+ }
+
+ override fun afterTextChanged(s: Editable?) {}
+ })
+ }
+
+ private fun filterProducts(query: String) {
+ val filteredList = if (query.isBlank()) {
+ fullProductList
+ } else {
+ fullProductList.filter { it.name.contains(query, ignoreCase = true) }
+ }
+ productAdapter.updateProducts(filteredList)
+ }
+
+ private fun logout() {
+ AlertDialog.Builder(this)
+ .setTitle("Logout")
+ .setMessage("Apakah Anda yakin ingin logout?")
+ .setPositiveButton("Ya") { _, _ ->
+ val intent = Intent(this, LoginActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ startActivity(intent)
+ finish()
+ }
+ .setNegativeButton("Batal", null)
+ .show()
+ }
+
+ private fun showSeatingChoiceDialog() {
+ val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_seating_choice, null)
+ val dialog = AlertDialog.Builder(this).setView(dialogView).create()
+ dialogView.findViewById(R.id.card_indoor).setOnClickListener {
+ currentOrderMode = OrderMode.DINE_IN_INDOOR
+ Toast.makeText(this, "Area Dalam Ruangan (Indoor) Dipilih", Toast.LENGTH_SHORT).show()
+ dialog.dismiss()
+ }
+ dialogView.findViewById(R.id.card_outdoor).setOnClickListener {
+ currentOrderMode = OrderMode.DINE_IN_OUTDOOR
+ Toast.makeText(this, "Area Luar Ruangan (Outdoor) Dipilih", Toast.LENGTH_SHORT).show()
+ dialog.dismiss()
+ }
+ dialog.show()
+ }
+
+ private fun addToCart(product: Product) {
+ val existingItem = cart.find { it.product.id == product.id }
+ if (product.stock <= (existingItem?.quantity ?: 0)) {
+ Toast.makeText(this, "Stok tidak mencukupi", Toast.LENGTH_SHORT).show()
+ return
+ }
+ if (existingItem != null) {
+ existingItem.quantity++
+ } else {
+ cart.add(CartItem(product, 1))
+ }
+ updateCartDisplay()
+ }
+
+ private fun increaseQuantity(item: CartItem) {
+ if (item.product.stock > item.quantity) {
+ item.quantity++
+ updateCartDisplay()
+ }
+ }
+
+ private fun decreaseQuantity(item: CartItem) {
+ if (item.quantity > 1) {
+ item.quantity--
+ updateCartDisplay()
+ } else {
+ removeFromCart(item)
+ }
+ }
+
+ private fun removeFromCart(item: CartItem) {
+ cart.remove(item)
+ updateCartDisplay()
+ }
+
+ private fun clearCart() {
+ AlertDialog.Builder(this)
+ .setTitle("Hapus Keranjang")
+ .setMessage("Yakin ingin mengosongkan keranjang?")
+ .setPositiveButton("Ya") { _, _ ->
+ cart.clear()
+ updateCartDisplay()
+ }
+ .setNegativeButton("Batal", null)
+ .show()
+ }
+
+ private fun updateCartDisplay() {
+ cartAdapter.updateData(ArrayList(cart))
+ rvCart.isVisible = cart.isNotEmpty()
+ emptyCartText.isVisible = cart.isEmpty()
+ updateTotal()
+ }
+
+ private fun checkout() {
+ if (cart.isEmpty()) {
+ Toast.makeText(this, "Keranjang masih kosong", Toast.LENGTH_SHORT).show()
+ return
+ }
+ showCustomerInfoDialog()
+ }
+
+ private fun showCustomerInfoDialog() {
+ val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_customer_info, null)
+ val nameInput = dialogView.findViewById(R.id.inputCustomerName)
+ val tableSpinner = dialogView.findViewById(R.id.spinnerTable)
+
+ if (currentOrderMode == OrderMode.TAKEAWAY) {
+ tableSpinner.visibility = View.GONE
+ } else {
+ tableSpinner.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, (1..20).map { "Meja $it" })
+ }
+
+ AlertDialog.Builder(this).setTitle("Data Pelanggan").setView(dialogView)
+ .setPositiveButton("Lanjut") { _, _ ->
+ customerName = nameInput.text.toString()
+ if (customerName.isBlank()) {
+ Toast.makeText(this, "Nama tidak boleh kosong", Toast.LENGTH_SHORT).show()
+ return@setPositiveButton
+ }
+ if (currentOrderMode != OrderMode.TAKEAWAY) {
+ tableNumber = tableSpinner.selectedItem.toString().filter { it.isDigit() }.toInt()
+ }
+ showPaymentMethodDialog()
+ }
+ .setNegativeButton("Batal", null).show()
+ }
+
+ private fun showPaymentMethodDialog() {
+ val total = cart.sumOf { (it.product.price * it.quantity).toLong() }.toInt()
+ val paymentMethods = arrayOf("Cash", "QRIS")
+ AlertDialog.Builder(this)
+ .setTitle("Pilih Metode Pembayaran")
+ .setItems(paymentMethods) { _, which ->
+ when (paymentMethods[which]) {
+ "Cash" -> goToReceiptPreview("Cash", total)
+ "QRIS" -> showQrisDialog(total)
+ }
+ }
+ .setNegativeButton("Batal", null)
+ .show()
+ }
+
+ private fun showQrisDialog(total: Int) {
+ val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_qris, null)
+ val totalTextView = dialogView.findViewById(R.id.qrisTotal)
+ totalTextView.text = "Total: ${formatCurrency(total)}"
+
+ val qrisDialog = AlertDialog.Builder(this)
+ .setTitle("Scan QRIS untuk Pembayaran")
+ .setView(dialogView)
+ .setPositiveButton("Konfirmasi Pembayaran") { dialog, _ ->
+ dialog.dismiss()
+ Toast.makeText(this, "Pembayaran Dikonfirmasi", Toast.LENGTH_SHORT).show()
+ goToReceiptPreview("QRIS", total)
+ }
+ .setNegativeButton("Ubah Metode") { dialog, _ ->
+ dialog.dismiss()
+ showPaymentMethodDialog()
+ }
+ .setOnCancelListener {
+ showPaymentMethodDialog()
+ }
+ .create()
+
+ qrisDialog.show()
+ }
+
+ private fun goToReceiptPreview(method: String, total: Int) {
+ lifecycleScope.launch {
+ processTransaction(method, total)
+ loadProducts()
+
+ val intent = Intent(this@MainActivity, ReceiptPreviewActivity::class.java).apply {
+ putExtra("receipt_text", buildReceiptText(method, total))
+ }
+ startActivity(intent)
+
+ cart.clear()
+ updateCartDisplay()
+ }
+ }
+
+ private fun showNoteDialog(item: CartItem) {
+ val input = EditText(this).apply { setText(item.note) }
+ AlertDialog.Builder(this).setTitle("Catatan untuk ${item.product.name}").setView(input)
+ .setPositiveButton("Simpan") { _, _ ->
+ item.note = input.text.toString().ifBlank { null }
+ updateCartDisplay()
+ }
+ .setNegativeButton("Batal", null).show()
+ }
+
+ private fun loadProducts() {
+ lifecycleScope.launch {
+ var products = db.productDao().getAllProducts()
+ if (products.isEmpty()) {
+ insertInitialProducts()
+ products = db.productDao().getAllProducts()
+ }
+ fullProductList = products
+ filterProducts(searchEditText.text.toString()) // Apply current filter
+ }
+ }
+
+ private suspend fun insertInitialProducts() {
+ val initialProducts = listOf(
+ Product(name = "Nasi Goreng", price = 15000, category = "Makanan", imageResName = "nasi_goreng", stock = 10),
+ Product(name = "Mie Goreng", price = 12000, category = "Makanan", imageResName = "mie_goreng", stock = 8),
+ Product(name = "Ayam Bakar", price = 18000, category = "Makanan", imageResName = "ayam_bakar", stock = 5),
+ Product(name = "Sate Ayam", price = 20000, category = "Makanan", imageResName = "sate_ayam", stock = 7),
+ Product(name = "Es Teh Manis", price = 5000, category = "Minuman", imageResName = "es_teh", stock = 20),
+ Product(name = "Es Jeruk", price = 7000, category = "Minuman", imageResName = "es_jeruk", stock = 15),
+ Product(name = "Jus Alpukat", price = 12000, category = "Minuman", imageResName = "jus_alpukat", stock = 10),
+ Product(name = "Kopi", price = 8000, category = "Minuman", imageResName = "kopi", stock = 12),
+ Product(name = "Keripik", price = 10000, category = "Snack", imageResName = "keripik", stock = 25),
+ Product(name = "Roti Bakar", price = 8000, category = "Snack", imageResName = "roti_bakar", stock = 18)
+ )
+ db.productDao().insertAll(initialProducts)
+ }
+
+ private suspend fun processTransaction(paymentMethod: String, total: Int) {
+ val newTransaction = Transaction(
+ customerName = customerName,
+ tableNumber = if (currentOrderMode != OrderMode.TAKEAWAY) tableNumber else null,
+ orderMode = currentOrderMode.name,
+ total = total,
+ paymentMethod = paymentMethod,
+ timestamp = System.currentTimeMillis()
+ )
+ val transactionId = db.transactionDao().insertTransaction(newTransaction)
+
+ val transactionItems = cart.map { cartItem ->
+ val updatedProduct = cartItem.product.copy(stock = cartItem.product.stock - cartItem.quantity)
+ db.productDao().updateProduct(updatedProduct)
+
+ TransactionItem(
+ transactionId = transactionId,
+ productId = cartItem.product.id,
+ productName = cartItem.product.name,
+ quantity = cartItem.quantity,
+ price = cartItem.product.price
+ )
+ }
+ db.transactionDao().insertTransactionItems(transactionItems)
+ }
+
+ private fun buildReceiptText(method: String, total: Int): String {
+ val timeStr = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault()).format(Date())
+ return buildString {
+ append("Struk Pembayaran")
+ append("\n--------------------\n")
+ append("Nama: $customerName\n")
+ if (currentOrderMode != OrderMode.TAKEAWAY) append("Meja: $tableNumber (${currentOrderMode.name.substringAfter("DINE_IN_")})\n")
+ append("Metode: $method\n")
+ append("Waktu: $timeStr\n--------------------\n")
+ cart.forEach { append("${it.product.name} x${it.quantity} = ${formatCurrency(it.product.price * it.quantity)}\n") }
+ append("--------------------\nTOTAL: ${formatCurrency(total)}\n\nTerima kasih!\n")
+ }
+ }
+
+ private fun formatCurrency(amount: Int): String = NumberFormat.getCurrencyInstance(Locale("id", "ID")).format(amount.toLong())
+ private fun updateTotal() {
+ val total = cart.sumOf { (it.product.price * it.quantity).toLong() }.toInt()
+ totalText.text = formatCurrency(total)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kasirapp/ManageMenuActivity.kt b/app/src/main/java/com/example/kasirapp/ManageMenuActivity.kt
new file mode 100644
index 0000000..8c8eca0
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/ManageMenuActivity.kt
@@ -0,0 +1,144 @@
+package com.example.kasirapp
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.kasirapp.data.AppDatabase
+import com.example.kasirapp.data.Product
+import kotlinx.coroutines.launch
+
+class ManageMenuActivity : AppCompatActivity() {
+
+ private lateinit var rvMenu: RecyclerView
+ private lateinit var menuAdapter: MenuAdapter
+ private var productList = mutableListOf()
+ private val db by lazy { AppDatabase.getDatabase(this) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_manage_menu)
+
+ rvMenu = findViewById(R.id.rv_menu)
+ rvMenu.layoutManager = LinearLayoutManager(this)
+ menuAdapter = MenuAdapter(productList, this::showAddEditDialog, this::deleteProduct)
+ rvMenu.adapter = menuAdapter
+
+ findViewById(R.id.fab_add_menu).setOnClickListener {
+ showAddEditDialog(null)
+ }
+
+ loadProductsFromDb()
+ }
+
+ private fun loadProductsFromDb() {
+ lifecycleScope.launch {
+ val products = db.productDao().getAllProducts()
+ productList.clear()
+ productList.addAll(products)
+ menuAdapter.notifyDataSetChanged()
+ }
+ }
+
+ private fun showAddEditDialog(product: Product?) {
+ val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_add_edit_menu, null)
+ val etMenuName = dialogView.findViewById(R.id.et_menu_name)
+ val etMenuPrice = dialogView.findViewById(R.id.et_menu_price)
+ val etMenuStock = dialogView.findViewById(R.id.et_menu_stock)
+ val spinnerCategory = dialogView.findViewById(R.id.spinner_menu_category)
+
+ val categories = arrayOf("Makanan", "Minuman", "Snack")
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, categories)
+ spinnerCategory.adapter = adapter
+
+ product?.let {
+ etMenuName.setText(it.name)
+ etMenuPrice.setText(it.price.toString())
+ etMenuStock.setText(it.stock.toString())
+ spinnerCategory.setSelection(categories.indexOf(it.category).coerceAtLeast(0))
+ }
+
+ AlertDialog.Builder(this)
+ .setTitle(if (product == null) "Tambah Menu" else "Edit Menu")
+ .setView(dialogView)
+ .setPositiveButton("Simpan") { _, _ ->
+ val name = etMenuName.text.toString()
+ val price = etMenuPrice.text.toString().toIntOrNull() ?: 0
+ val stock = etMenuStock.text.toString().toIntOrNull() ?: 0
+ val category = spinnerCategory.selectedItem.toString()
+
+ if (name.isNotBlank()) {
+ lifecycleScope.launch {
+ if (product == null) {
+ // Add new product
+ val newProduct = Product(name = name, price = price, category = category, stock = stock, imageResName = "ic_launcher_background")
+ db.productDao().insert(newProduct)
+ } else {
+ // Edit existing product
+ val updatedProduct = product.copy(name = name, price = price, stock = stock, category = category)
+ db.productDao().updateProduct(updatedProduct)
+ }
+ // Refresh list from DB
+ loadProductsFromDb()
+ }
+ }
+ }
+ .setNegativeButton("Batal", null)
+ .show()
+ }
+
+ private fun deleteProduct(product: Product) {
+ AlertDialog.Builder(this)
+ .setTitle("Hapus Menu")
+ .setMessage("Yakin ingin menghapus item \"${product.name}\"?")
+ .setPositiveButton("Hapus") { _, _ ->
+ lifecycleScope.launch {
+ db.productDao().delete(product)
+ loadProductsFromDb() // Refresh list
+ }
+ }
+ .setNegativeButton("Batal", null)
+ .show()
+ }
+
+ class MenuAdapter(
+ private val menuList: List,
+ private val onEdit: (Product) -> Unit,
+ private val onDelete: (Product) -> Unit
+ ) :
+ RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MenuViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_menu, parent, false)
+ return MenuViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: MenuViewHolder, position: Int) {
+ val menuItem = menuList[position]
+ holder.bind(menuItem, onEdit, onDelete)
+ }
+
+ override fun getItemCount(): Int = menuList.size
+
+ class MenuViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val tvMenuName: TextView = itemView.findViewById(R.id.tv_menu_name)
+ private val tvMenuPrice: TextView = itemView.findViewById(R.id.tv_menu_price)
+ private val btnEdit: Button = itemView.findViewById(R.id.btn_edit_menu)
+ private val btnDelete: Button = itemView.findViewById(R.id.btn_delete_menu)
+
+ fun bind(product: Product, onEdit: (Product) -> Unit, onDelete: (Product) -> Unit) {
+ tvMenuName.text = product.name
+ tvMenuPrice.text = "Rp ${product.price}"
+
+ btnEdit.setOnClickListener { onEdit(product) }
+ btnDelete.setOnClickListener { onDelete(product) }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/PaymentManagementActivity.kt b/app/src/main/java/com/example/kasirapp/PaymentManagementActivity.kt
new file mode 100644
index 0000000..78bb6ca
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/PaymentManagementActivity.kt
@@ -0,0 +1,21 @@
+package com.example.kasirapp
+
+import android.os.Bundle
+import android.widget.ArrayAdapter
+import android.widget.ListView
+import androidx.appcompat.app.AppCompatActivity
+
+class PaymentManagementActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_payment_management)
+
+ val lvPaymentMethods = findViewById(R.id.lv_payment_methods)
+
+ val paymentMethods = arrayOf("Cash", "QRIS")
+
+ val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, paymentMethods)
+ lvPaymentMethods.adapter = adapter
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kasirapp/ProductAdapter.kt b/app/src/main/java/com/example/kasirapp/ProductAdapter.kt
new file mode 100644
index 0000000..6a9622b
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/ProductAdapter.kt
@@ -0,0 +1,57 @@
+package com.example.kasirapp
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.example.kasirapp.data.Product
+
+class ProductAdapter(
+ private var productList: List,
+ private val onAddClick: (Product) -> Unit
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_product_card, parent, false)
+ return ProductViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
+ holder.bind(productList[position])
+ }
+
+ override fun getItemCount(): Int = productList.size
+
+ fun updateProducts(newProducts: List) {
+ this.productList = newProducts
+ notifyDataSetChanged()
+ }
+
+ inner class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val nameTextView: TextView = itemView.findViewById(R.id.tv_product_name)
+ private val priceTextView: TextView = itemView.findViewById(R.id.tv_product_price)
+ private val stockTextView: TextView = itemView.findViewById(R.id.tv_product_stock)
+ private val imageView: ImageView = itemView.findViewById(R.id.iv_product_image)
+ private val addButton: Button = itemView.findViewById(R.id.btn_add_to_cart)
+
+ fun bind(product: Product) {
+ nameTextView.text = product.name
+ priceTextView.text = "Rp ${product.price}"
+ stockTextView.text = "Stok: ${product.stock}"
+
+ val imageResId = itemView.context.resources.getIdentifier(product.imageResName, "drawable", itemView.context.packageName)
+ if (imageResId != 0) {
+ imageView.setImageResource(imageResId)
+ } else {
+ imageView.setImageResource(R.drawable.ic_launcher_background) // Fallback image
+ }
+
+ addButton.isEnabled = product.stock > 0
+ addButton.text = if (product.stock > 0) "Tambah" else "Habis"
+ addButton.setOnClickListener { onAddClick(product) }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/ReceiptPreviewActivity.kt b/app/src/main/java/com/example/kasirapp/ReceiptPreviewActivity.kt
new file mode 100644
index 0000000..5d4f852
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/ReceiptPreviewActivity.kt
@@ -0,0 +1,80 @@
+package com.example.kasirapp
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.graphics.Paint
+import android.graphics.pdf.PdfDocument
+import android.os.Bundle
+import android.os.Environment
+import android.widget.Button
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.FileProvider
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+
+class ReceiptPreviewActivity : AppCompatActivity() {
+
+ private lateinit var receiptText: String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_receipt_preview)
+
+ val tvReceipt = findViewById(R.id.tv_receipt)
+ val btnPrintPdf = findViewById(R.id.btn_print_pdf)
+
+ receiptText = intent.getStringExtra("receipt_text") ?: ""
+ tvReceipt.text = receiptText
+
+ btnPrintPdf.setOnClickListener {
+ createPdf(receiptText)
+ }
+ }
+
+ private fun createPdf(text: String) {
+ val document = PdfDocument()
+ val pageInfo = PdfDocument.PageInfo.Builder(300, 600, 1).create()
+ val page = document.startPage(pageInfo)
+ val canvas = page.canvas
+ val paint = Paint()
+ paint.textSize = 8f
+
+ var y = 20f
+ for (line in text.split("\n")) {
+ canvas.drawText(line, 10f, y, paint)
+ y += paint.descent() - paint.ascent()
+ }
+
+ document.finishPage(page)
+
+ val fileName = "struk_${System.currentTimeMillis()}.pdf"
+ val file = File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName)
+
+ try {
+ document.writeTo(FileOutputStream(file))
+ Toast.makeText(this, "PDF berhasil dibuat: ${file.absolutePath}", Toast.LENGTH_LONG).show()
+ openPdf(file)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ Toast.makeText(this, "Gagal membuat PDF: ${e.message}", Toast.LENGTH_SHORT).show()
+ } finally {
+ document.close()
+ }
+ }
+
+ private fun openPdf(file: File) {
+ val uri = FileProvider.getUriForFile(this, "${applicationContext.packageName}.provider", file)
+ val intent = Intent(Intent.ACTION_VIEW)
+ intent.setDataAndType(uri, "application/pdf")
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+
+ try {
+ startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(this, "Tidak ada aplikasi untuk membuka PDF", Toast.LENGTH_SHORT).show()
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/SalesReportActivity.kt b/app/src/main/java/com/example/kasirapp/SalesReportActivity.kt
new file mode 100644
index 0000000..43274d5
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/SalesReportActivity.kt
@@ -0,0 +1,187 @@
+package com.example.kasirapp
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.kasirapp.data.AppDatabase
+import com.example.kasirapp.data.TransactionWithItems
+import kotlinx.coroutines.launch
+import java.text.NumberFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+class SalesReportActivity : AppCompatActivity() {
+
+ private lateinit var rvTransactions: RecyclerView
+ private lateinit var transactionAdapter: TransactionAdapter
+ private lateinit var tvTotalSales: TextView
+ private lateinit var tvTotalTransactions: TextView
+ private lateinit var tvNoTransactions: TextView
+ private lateinit var spinnerDateFilter: Spinner
+
+ private val db by lazy { AppDatabase.getDatabase(this) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_sales_report)
+
+ initializeViews()
+ setupFilterSpinner()
+
+ rvTransactions.layoutManager = LinearLayoutManager(this)
+ transactionAdapter = TransactionAdapter(emptyList(), this)
+ rvTransactions.adapter = transactionAdapter
+
+ // Initial load with default filter (All)
+ loadTransactionReport(FilterOption.ALL)
+ }
+
+ private fun initializeViews() {
+ rvTransactions = findViewById(R.id.rv_transactions)
+ tvTotalSales = findViewById(R.id.tv_total_sales)
+ tvTotalTransactions = findViewById(R.id.tv_total_transactions)
+ tvNoTransactions = findViewById(R.id.tv_no_transactions)
+ spinnerDateFilter = findViewById(R.id.spinner_date_filter)
+ }
+
+ private fun setupFilterSpinner() {
+ val filterOptions = FilterOption.values().map { it.displayName }
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, filterOptions)
+ spinnerDateFilter.adapter = adapter
+
+ spinnerDateFilter.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ val selectedFilter = FilterOption.values()[position]
+ loadTransactionReport(selectedFilter)
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+ }
+
+ private fun loadTransactionReport(filter: FilterOption) {
+ lifecycleScope.launch {
+ val (startDate, endDate) = filter.getStartAndEndTimestamps()
+
+ val transactions = if (filter == FilterOption.ALL) {
+ db.transactionDao().getTransactionsWithItems()
+ } else {
+ db.transactionDao().getTransactionsWithItemsInRange(startDate, endDate)
+ }
+
+ updateUI(transactions)
+ }
+ }
+
+ private fun updateUI(transactions: List) {
+ if (transactions.isEmpty()) {
+ tvNoTransactions.visibility = View.VISIBLE
+ rvTransactions.visibility = View.GONE
+ tvTotalSales.text = "Total Penjualan: ${formatCurrency(0)}"
+ tvTotalTransactions.text = "Total Transaksi: 0"
+ } else {
+ tvNoTransactions.visibility = View.GONE
+ rvTransactions.visibility = View.VISIBLE
+
+ val totalSales = transactions.sumOf { it.transaction.total }
+ val totalTransactions = transactions.size
+
+ tvTotalSales.text = "Total Penjualan: ${formatCurrency(totalSales)}"
+ tvTotalTransactions.text = "Total Transaksi: $totalTransactions"
+
+ transactionAdapter.updateData(transactions)
+ }
+ }
+
+ private fun formatCurrency(amount: Int): String {
+ return NumberFormat.getCurrencyInstance(Locale("id", "ID")).format(amount.toLong())
+ }
+
+ enum class FilterOption(val displayName: String) {
+ TODAY("Hari Ini"),
+ LAST_7_DAYS("7 Hari Terakhir"),
+ THIS_MONTH("Bulan Ini"),
+ ALL("Semua");
+
+ fun getStartAndEndTimestamps(): Pair {
+ val calendar = Calendar.getInstance()
+ val endDate = calendar.timeInMillis
+
+ when (this) {
+ TODAY -> {
+ calendar.set(Calendar.HOUR_OF_DAY, 0)
+ calendar.set(Calendar.MINUTE, 0)
+ calendar.set(Calendar.SECOND, 0)
+ calendar.set(Calendar.MILLISECOND, 0)
+ }
+ LAST_7_DAYS -> {
+ calendar.add(Calendar.DAY_OF_YEAR, -6) // Include today, so go back 6 days
+ calendar.set(Calendar.HOUR_OF_DAY, 0)
+ calendar.set(Calendar.MINUTE, 0)
+ }
+ THIS_MONTH -> {
+ calendar.set(Calendar.DAY_OF_MONTH, 1)
+ calendar.set(Calendar.HOUR_OF_DAY, 0)
+ calendar.set(Calendar.MINUTE, 0)
+ }
+ ALL -> {
+ return Pair(0, Long.MAX_VALUE)
+ }
+ }
+ val startDate = calendar.timeInMillis
+ return Pair(startDate, endDate)
+ }
+ }
+
+ class TransactionAdapter(private var transactionList: List, private val context: Context) :
+ RecyclerView.Adapter() {
+
+ fun updateData(newTransactions: List) {
+ transactionList = newTransactions
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
+ return TransactionViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
+ holder.bind(transactionList[position], context)
+ }
+
+ override fun getItemCount(): Int = transactionList.size
+
+ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val tvTransactionId: TextView = itemView.findViewById(R.id.tv_transaction_id)
+ private val tvTransactionTotal: TextView = itemView.findViewById(R.id.tv_transaction_total)
+ private val tvTransactionDate: TextView = itemView.findViewById(R.id.tv_transaction_date)
+
+ fun bind(transactionWithItems: TransactionWithItems, context: Context) {
+ val transaction = transactionWithItems.transaction
+ tvTransactionId.text = "ID Transaksi: ${transaction.id}"
+ tvTransactionTotal.text = "Total: ${NumberFormat.getCurrencyInstance(Locale("id", "ID")).format(transaction.total.toLong())}"
+ val sdf = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault())
+ tvTransactionDate.text = "Tanggal: ${sdf.format(Date(transaction.timestamp))}"
+
+ itemView.setOnClickListener {
+ val intent = Intent(context, TransactionDetailActivity::class.java).apply {
+ putExtra("TRANSACTION_ID", transaction.id)
+ }
+ context.startActivity(intent)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/SalesTrafficActivity.kt b/app/src/main/java/com/example/kasirapp/SalesTrafficActivity.kt
new file mode 100644
index 0000000..b71c19b
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/SalesTrafficActivity.kt
@@ -0,0 +1,150 @@
+package com.example.kasirapp
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.example.kasirapp.data.AppDatabase
+import com.example.kasirapp.data.Transaction
+import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+import com.github.mikephil.charting.formatter.ValueFormatter
+import kotlinx.coroutines.launch
+import java.text.SimpleDateFormat
+import java.util.*
+
+class SalesTrafficActivity : AppCompatActivity() {
+
+ private lateinit var lineChart: LineChart
+ private lateinit var spinnerDateFilter: Spinner
+ private val db by lazy { AppDatabase.getDatabase(this) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_sales_traffic)
+
+ lineChart = findViewById(R.id.line_chart)
+ spinnerDateFilter = findViewById(R.id.spinner_date_filter)
+
+ setupFilterSpinner()
+ loadTransactionsAndSetupChart(FilterOption.LAST_7_DAYS) // Default to last 7 days
+ }
+
+ private fun setupFilterSpinner() {
+ val filterOptions = FilterOption.values().map { it.displayName }
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, filterOptions)
+ spinnerDateFilter.adapter = adapter
+ spinnerDateFilter.setSelection(FilterOption.LAST_7_DAYS.ordinal) // Set default selection
+
+ spinnerDateFilter.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ val selectedFilter = FilterOption.values()[position]
+ loadTransactionsAndSetupChart(selectedFilter)
+ }
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+ }
+
+ private fun loadTransactionsAndSetupChart(filter: FilterOption) {
+ lifecycleScope.launch {
+ val (startDate, endDate) = filter.getStartAndEndTimestamps()
+ val transactions = db.transactionDao().getAllTransactions().filter {
+ it.timestamp in startDate..endDate
+ }
+ setupLineChart(transactions, filter)
+ }
+ }
+
+ private fun setupLineChart(transactions: List, filter: FilterOption) {
+ val salesData = getSalesDataForChart(transactions, filter)
+
+ val entries = ArrayList()
+ salesData.values.forEachIndexed { index, sales ->
+ entries.add(Entry(index.toFloat(), sales.toFloat()))
+ }
+
+ val dataSet = LineDataSet(entries, "Penjualan").apply {
+ color = Color.BLUE
+ valueTextColor = Color.BLACK
+ lineWidth = 2f
+ setCircleColor(Color.BLUE)
+ circleRadius = 4f
+ }
+
+ val lineData = LineData(dataSet)
+ lineChart.data = lineData
+
+ lineChart.description.text = filter.displayName
+ lineChart.xAxis.valueFormatter = DateAxisFormatter(salesData.keys.toList())
+ lineChart.invalidate() // Refresh chart
+ }
+
+ private fun getSalesDataForChart(transactions: List, filter: FilterOption): Map {
+ val salesByDay = LinkedHashMap()
+ val (startDate, _) = filter.getStartAndEndTimestamps()
+ val calendar = Calendar.getInstance().apply { timeInMillis = startDate }
+ val endCalendar = Calendar.getInstance()
+
+ val sdf = if (filter == FilterOption.THIS_MONTH) {
+ SimpleDateFormat("dd", Locale.getDefault())
+ } else {
+ SimpleDateFormat("dd/MM", Locale.getDefault())
+ }
+
+ // Initialize map with all days/dates in the range
+ while (calendar.timeInMillis <= endCalendar.timeInMillis) {
+ salesByDay[sdf.format(calendar.time)] = 0
+ calendar.add(Calendar.DAY_OF_YEAR, 1)
+ }
+
+ // Populate with transaction data
+ transactions.forEach { transaction ->
+ val dateString = sdf.format(Date(transaction.timestamp))
+ if (salesByDay.containsKey(dateString)) {
+ salesByDay[dateString] = (salesByDay[dateString] ?: 0) + transaction.total
+ }
+ }
+
+ return salesByDay
+ }
+
+ enum class FilterOption(val displayName: String) {
+ TODAY("Hari Ini"),
+ LAST_7_DAYS("7 Hari Terakhir"),
+ THIS_MONTH("Bulan Ini"),
+ ALL("Semua"); // Although 'All' is less meaningful for a traffic chart, we keep it for consistency
+
+ fun getStartAndEndTimestamps(): Pair {
+ val calendar = Calendar.getInstance()
+ val endDate = calendar.timeInMillis
+
+ when (this) {
+ TODAY -> {
+ calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0)
+ }
+ LAST_7_DAYS -> {
+ calendar.add(Calendar.DAY_OF_YEAR, -6)
+ calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0)
+ }
+ THIS_MONTH -> {
+ calendar.set(Calendar.DAY_OF_MONTH, 1)
+ calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0)
+ }
+ ALL -> return Pair(0, Long.MAX_VALUE)
+ }
+ val startDate = calendar.timeInMillis
+ return Pair(startDate, endDate)
+ }
+ }
+
+ class DateAxisFormatter(private val dates: List) : ValueFormatter() {
+ override fun getFormattedValue(value: Float):
+ String = dates.getOrNull(value.toInt()) ?: ""
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/TransactionDetailActivity.kt b/app/src/main/java/com/example/kasirapp/TransactionDetailActivity.kt
new file mode 100644
index 0000000..0e31d26
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/TransactionDetailActivity.kt
@@ -0,0 +1,84 @@
+package com.example.kasirapp
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.example.kasirapp.data.AppDatabase
+import com.example.kasirapp.data.TransactionWithItems
+import kotlinx.coroutines.launch
+import java.text.NumberFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+class TransactionDetailActivity : AppCompatActivity() {
+
+ private val db by lazy { AppDatabase.getDatabase(this) }
+ private var transactionId: Long = -1
+
+ private lateinit var tvTransactionId: TextView
+ private lateinit var tvCustomerName: TextView
+ private lateinit var tvDate: TextView
+ private lateinit var tvPaymentMethod: TextView
+ private lateinit var tvTotal: TextView
+ private lateinit var llTransactionItems: LinearLayout
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_transaction_detail)
+
+ transactionId = intent.getLongExtra("TRANSACTION_ID", -1)
+ if (transactionId == -1L) {
+ finish() // Close activity if no ID is provided
+ return
+ }
+
+ initializeViews()
+ loadTransactionDetails()
+ }
+
+ private fun initializeViews() {
+ tvTransactionId = findViewById(R.id.tv_detail_transaction_id)
+ tvCustomerName = findViewById(R.id.tv_detail_customer_name)
+ tvDate = findViewById(R.id.tv_detail_date)
+ tvPaymentMethod = findViewById(R.id.tv_detail_payment_method)
+ tvTotal = findViewById(R.id.tv_detail_total)
+ llTransactionItems = findViewById(R.id.ll_transaction_items)
+ }
+
+ private fun loadTransactionDetails() {
+ lifecycleScope.launch {
+ // This assumes you will add a function in your DAO to get a single transaction by ID
+ val transactionWithItems = db.transactionDao().getTransactionWithItemsById(transactionId)
+ transactionWithItems?.let { displayTransaction(it) }
+ }
+ }
+
+ private fun displayTransaction(trx: TransactionWithItems) {
+ val transaction = trx.transaction
+ tvTransactionId.text = "ID: ${transaction.id}"
+ tvCustomerName.text = "Nama: ${transaction.customerName}"
+ tvPaymentMethod.text = "Metode: ${transaction.paymentMethod}"
+ tvTotal.text = "TOTAL: ${formatCurrency(transaction.total)}"
+
+ val sdf = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault())
+ tvDate.text = "Tanggal: ${sdf.format(Date(transaction.timestamp))}"
+
+ llTransactionItems.removeAllViews()
+ trx.items.forEach { item ->
+ val itemText = "- ${item.productName} x${item.quantity} | ${formatCurrency(item.price * item.quantity)}"
+ val textView = TextView(this).apply {
+ text = itemText
+ textSize = 16f
+ setPadding(0, 4, 0, 4)
+ }
+ llTransactionItems.addView(textView)
+ }
+ }
+
+ private fun formatCurrency(amount: Int): String {
+ return NumberFormat.getCurrencyInstance(Locale("id", "ID")).format(amount.toLong())
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/data/AppDatabase.kt b/app/src/main/java/com/example/kasirapp/data/AppDatabase.kt
new file mode 100644
index 0000000..46332b4
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/AppDatabase.kt
@@ -0,0 +1,32 @@
+package com.example.kasirapp.data
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+
+@Database(entities = [Product::class, Transaction::class, TransactionItem::class], version = 2, exportSchema = false)
+abstract class AppDatabase : RoomDatabase() {
+
+ abstract fun productDao(): ProductDao
+ abstract fun transactionDao(): TransactionDao
+
+ companion object {
+ @Volatile
+ private var INSTANCE: AppDatabase? = null
+
+ fun getDatabase(context: Context): AppDatabase {
+ return INSTANCE ?: synchronized(this) {
+ val instance = Room.databaseBuilder(
+ context.applicationContext,
+ AppDatabase::class.java,
+ "kasir_database"
+ )
+ .fallbackToDestructiveMigration() // Recreates the DB on version change
+ .build()
+ INSTANCE = instance
+ instance
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/kasirapp/data/Product.kt b/app/src/main/java/com/example/kasirapp/data/Product.kt
new file mode 100644
index 0000000..98d8b9e
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/Product.kt
@@ -0,0 +1,15 @@
+package com.example.kasirapp.data
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "products")
+data class Product(
+ @PrimaryKey(autoGenerate = true)
+ val id: Int = 0,
+ val name: String,
+ val price: Int,
+ val category: String,
+ val imageResName: String,
+ var stock: Int
+)
diff --git a/app/src/main/java/com/example/kasirapp/data/ProductDao.kt b/app/src/main/java/com/example/kasirapp/data/ProductDao.kt
new file mode 100644
index 0000000..02ac35c
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/ProductDao.kt
@@ -0,0 +1,29 @@
+package com.example.kasirapp.data
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+
+@Dao
+interface ProductDao {
+ @Query("SELECT * FROM products ORDER BY category, name")
+ suspend fun getAllProducts(): List
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(product: Product)
+
+ @Insert
+ suspend fun insertAll(products: List)
+
+ @Update
+ suspend fun updateProduct(product: Product)
+
+ @Delete
+ suspend fun delete(product: Product)
+
+ @Query("SELECT * FROM products WHERE id = :productId")
+ suspend fun getProductById(productId: Int): Product?
+}
diff --git a/app/src/main/java/com/example/kasirapp/data/Promo.kt b/app/src/main/java/com/example/kasirapp/data/Promo.kt
new file mode 100644
index 0000000..8119364
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/Promo.kt
@@ -0,0 +1,16 @@
+package com.example.kasirapp.data
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "promos")
+data class Promo(
+ @PrimaryKey(autoGenerate = true)
+ val id: Int = 0,
+ val code: String, // e.g., "HEMAT10"
+ val description: String, // e.g., "Diskon 10%"
+ val discountPercentage: Int? = null, // 10 for 10%
+ val discountFixedAmount: Int? = null, // e.g., 10000 for Rp 10.000
+ val minPurchaseAmount: Int? = null, // Minimum purchase to be eligible
+ val isActive: Boolean = true
+)
diff --git a/app/src/main/java/com/example/kasirapp/data/Transaction.kt b/app/src/main/java/com/example/kasirapp/data/Transaction.kt
new file mode 100644
index 0000000..1b6b30f
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/Transaction.kt
@@ -0,0 +1,16 @@
+package com.example.kasirapp.data
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "transactions")
+data class Transaction(
+ @PrimaryKey(autoGenerate = true)
+ val id: Long = 0,
+ val customerName: String,
+ val tableNumber: Int?,
+ val orderMode: String,
+ val total: Int,
+ val paymentMethod: String,
+ val timestamp: Long
+)
diff --git a/app/src/main/java/com/example/kasirapp/data/TransactionDao.kt b/app/src/main/java/com/example/kasirapp/data/TransactionDao.kt
new file mode 100644
index 0000000..af958fe
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/TransactionDao.kt
@@ -0,0 +1,31 @@
+package com.example.kasirapp.data
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Transaction as RoomTransaction // Alias to avoid conflict with our Transaction class
+
+@Dao
+interface TransactionDao {
+ @Insert
+ suspend fun insertTransaction(transaction: Transaction): Long // Returns the new transaction's ID
+
+ @Insert
+ suspend fun insertTransactionItems(items: List)
+
+ @RoomTransaction
+ @Query("SELECT * FROM transactions ORDER BY timestamp DESC")
+ suspend fun getTransactionsWithItems(): List
+
+ @RoomTransaction
+ @Query("SELECT * FROM transactions WHERE id = :id")
+ suspend fun getTransactionWithItemsById(id: Long): TransactionWithItems?
+
+ @RoomTransaction
+ @Query("SELECT * FROM transactions WHERE timestamp BETWEEN :startDate AND :endDate ORDER BY timestamp DESC")
+ suspend fun getTransactionsWithItemsInRange(startDate: Long, endDate: Long): List
+
+ // We keep this function in case we need to get just the transaction objects
+ @Query("SELECT * FROM transactions ORDER BY timestamp DESC")
+ suspend fun getAllTransactions(): List
+}
diff --git a/app/src/main/java/com/example/kasirapp/data/TransactionItem.kt b/app/src/main/java/com/example/kasirapp/data/TransactionItem.kt
new file mode 100644
index 0000000..faded97
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/TransactionItem.kt
@@ -0,0 +1,24 @@
+package com.example.kasirapp.data
+
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.PrimaryKey
+
+@Entity(
+ tableName = "transaction_items",
+ foreignKeys = [ForeignKey(
+ entity = Transaction::class,
+ parentColumns = ["id"],
+ childColumns = ["transactionId"],
+ onDelete = ForeignKey.CASCADE // Jika transaksi dihapus, itemnya juga terhapus
+ )]
+)
+data class TransactionItem(
+ @PrimaryKey(autoGenerate = true)
+ val id: Long = 0,
+ val transactionId: Long, // Menghubungkan ke Transaction(id)
+ val productId: Int,
+ val productName: String, // Menyimpan nama produk saat transaksi
+ val quantity: Int,
+ val price: Int // Menyimpan harga per item saat transaksi
+)
diff --git a/app/src/main/java/com/example/kasirapp/data/TransactionWithItems.kt b/app/src/main/java/com/example/kasirapp/data/TransactionWithItems.kt
new file mode 100644
index 0000000..29358ab
--- /dev/null
+++ b/app/src/main/java/com/example/kasirapp/data/TransactionWithItems.kt
@@ -0,0 +1,13 @@
+package com.example.kasirapp.data
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+data class TransactionWithItems(
+ @Embedded val transaction: Transaction,
+ @Relation(
+ parentColumn = "id",
+ entityColumn = "transactionId"
+ )
+ val items: List
+)
diff --git a/app/src/main/res/drawable/ayam_bakar.png b/app/src/main/res/drawable/ayam_bakar.png
new file mode 100644
index 0000000..c0bd19e
Binary files /dev/null and b/app/src/main/res/drawable/ayam_bakar.png differ
diff --git a/app/src/main/res/drawable/bg_card.xml b/app/src/main/res/drawable/bg_card.xml
new file mode 100644
index 0000000..345d062
--- /dev/null
+++ b/app/src/main/res/drawable/bg_card.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/edit_text_background.xml b/app/src/main/res/drawable/edit_text_background.xml
new file mode 100644
index 0000000..0cff365
--- /dev/null
+++ b/app/src/main/res/drawable/edit_text_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/es_jeruk.png b/app/src/main/res/drawable/es_jeruk.png
new file mode 100644
index 0000000..04fa569
Binary files /dev/null and b/app/src/main/res/drawable/es_jeruk.png differ
diff --git a/app/src/main/res/drawable/es_teh.png b/app/src/main/res/drawable/es_teh.png
new file mode 100644
index 0000000..f7aa8be
Binary files /dev/null and b/app/src/main/res/drawable/es_teh.png differ
diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml
new file mode 100644
index 0000000..eb23254
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_add_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_remove_24.xml b/app/src/main/res/drawable/ic_baseline_remove_24.xml
new file mode 100644
index 0000000..791a2f8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_remove_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..ca3826a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_qris_sample.xml b/app/src/main/res/drawable/ic_qris_sample.xml
new file mode 100644
index 0000000..a8b409b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_qris_sample.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/jus_alpukat.png b/app/src/main/res/drawable/jus_alpukat.png
new file mode 100644
index 0000000..4b01884
Binary files /dev/null and b/app/src/main/res/drawable/jus_alpukat.png differ
diff --git a/app/src/main/res/drawable/keripik.png b/app/src/main/res/drawable/keripik.png
new file mode 100644
index 0000000..77be21a
Binary files /dev/null and b/app/src/main/res/drawable/keripik.png differ
diff --git a/app/src/main/res/drawable/kopi.png b/app/src/main/res/drawable/kopi.png
new file mode 100644
index 0000000..1f9936c
Binary files /dev/null and b/app/src/main/res/drawable/kopi.png differ
diff --git a/app/src/main/res/drawable/mie_goreng.png b/app/src/main/res/drawable/mie_goreng.png
new file mode 100644
index 0000000..228871f
Binary files /dev/null and b/app/src/main/res/drawable/mie_goreng.png differ
diff --git a/app/src/main/res/drawable/nasi_goreng.png b/app/src/main/res/drawable/nasi_goreng.png
new file mode 100644
index 0000000..b9ae0f7
Binary files /dev/null and b/app/src/main/res/drawable/nasi_goreng.png differ
diff --git a/app/src/main/res/drawable/qris_real.png b/app/src/main/res/drawable/qris_real.png
new file mode 100644
index 0000000..dcabc83
Binary files /dev/null and b/app/src/main/res/drawable/qris_real.png differ
diff --git a/app/src/main/res/drawable/resto_indoor.png b/app/src/main/res/drawable/resto_indoor.png
new file mode 100644
index 0000000..8d64d40
Binary files /dev/null and b/app/src/main/res/drawable/resto_indoor.png differ
diff --git a/app/src/main/res/drawable/resto_outdoor.png b/app/src/main/res/drawable/resto_outdoor.png
new file mode 100644
index 0000000..db50dfa
Binary files /dev/null and b/app/src/main/res/drawable/resto_outdoor.png differ
diff --git a/app/src/main/res/drawable/roti_bakar.png b/app/src/main/res/drawable/roti_bakar.png
new file mode 100644
index 0000000..9b3bf0e
Binary files /dev/null and b/app/src/main/res/drawable/roti_bakar.png differ
diff --git a/app/src/main/res/drawable/sate_ayam.png b/app/src/main/res/drawable/sate_ayam.png
new file mode 100644
index 0000000..241a0f1
Binary files /dev/null and b/app/src/main/res/drawable/sate_ayam.png differ
diff --git a/app/src/main/res/layout/activity_admin_dashboard.xml b/app/src/main/res/layout/activity_admin_dashboard.xml
new file mode 100644
index 0000000..ab904b2
--- /dev/null
+++ b/app/src/main/res/layout/activity_admin_dashboard.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..04d313e
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_manage_menu.xml b/app/src/main/res/layout/activity_manage_menu.xml
new file mode 100644
index 0000000..b3ebbbd
--- /dev/null
+++ b/app/src/main/res/layout/activity_manage_menu.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_payment_management.xml b/app/src/main/res/layout/activity_payment_management.xml
new file mode 100644
index 0000000..32b54c5
--- /dev/null
+++ b/app/src/main/res/layout/activity_payment_management.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_receipt_preview.xml b/app/src/main/res/layout/activity_receipt_preview.xml
new file mode 100644
index 0000000..3d14f08
--- /dev/null
+++ b/app/src/main/res/layout/activity_receipt_preview.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sales_report.xml b/app/src/main/res/layout/activity_sales_report.xml
new file mode 100644
index 0000000..3129fec
--- /dev/null
+++ b/app/src/main/res/layout/activity_sales_report.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_sales_traffic.xml b/app/src/main/res/layout/activity_sales_traffic.xml
new file mode 100644
index 0000000..e53892e
--- /dev/null
+++ b/app/src/main/res/layout/activity_sales_traffic.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_transaction_detail.xml b/app/src/main/res/layout/activity_transaction_detail.xml
new file mode 100644
index 0000000..5c34ffc
--- /dev/null
+++ b/app/src/main/res/layout/activity_transaction_detail.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_add_edit_menu.xml b/app/src/main/res/layout/dialog_add_edit_menu.xml
new file mode 100644
index 0000000..f29188b
--- /dev/null
+++ b/app/src/main/res/layout/dialog_add_edit_menu.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_customer_info.xml b/app/src/main/res/layout/dialog_customer_info.xml
new file mode 100644
index 0000000..46c7bad
--- /dev/null
+++ b/app/src/main/res/layout/dialog_customer_info.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_qris.xml b/app/src/main/res/layout/dialog_qris.xml
new file mode 100644
index 0000000..8ccee5c
--- /dev/null
+++ b/app/src/main/res/layout/dialog_qris.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_seating_choice.xml b/app/src/main/res/layout/dialog_seating_choice.xml
new file mode 100644
index 0000000..0da0d6d
--- /dev/null
+++ b/app/src/main/res/layout/dialog_seating_choice.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_cart.xml b/app/src/main/res/layout/item_cart.xml
new file mode 100644
index 0000000..32383c4
--- /dev/null
+++ b/app/src/main/res/layout/item_cart.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_menu.xml b/app/src/main/res/layout/item_menu.xml
new file mode 100644
index 0000000..2dc13a4
--- /dev/null
+++ b/app/src/main/res/layout/item_menu.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_product_card.xml b/app/src/main/res/layout/item_product_card.xml
new file mode 100644
index 0000000..a49659a
--- /dev/null
+++ b/app/src/main/res/layout/item_product_card.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_transaction.xml b/app/src/main/res/layout/item_transaction.xml
new file mode 100644
index 0000000..135abe2
--- /dev/null
+++ b/app/src/main/res/layout/item_transaction.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..73eac9e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..cae6ef9
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..180fc65
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..3ded27c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..851a68d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..644413a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..9247462
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..1cdf46a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..29d3e9a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..b91390e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..b943ad1
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..d9f9e01
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..165ef8f
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..456423b
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..463c3b9
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..cc04648
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..dc969b8
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #F5F5F5
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2b7984e
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,23 @@
+
+
+ KasirApp
+ Mode: Takeaway
+ Stok: %d
+ Tambah
+ Habis
+ Catatan: %s
+ Area: Indoor
+ Area: Outdoor
+ Stok tidak cukup
+ Keranjang kosong!
+ Data Pemesan
+ Lanjut
+ Nama tidak boleh kosong
+ Batal
+ Pilih Metode Pembayaran
+ Catatan untuk %s
+ Simpan
+ STRUK PEMBAYARAN
+ Nama Customer
+ Pilih Nomor Meja
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..f3da9ab
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..c4a74f9
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/test/java/com/example/kasirapp/ExampleUnitTest.kt b/app/src/test/java/com/example/kasirapp/ExampleUnitTest.kt
new file mode 100644
index 0000000..bd63c08
--- /dev/null
+++ b/app/src/test/java/com/example/kasirapp/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.kasirapp
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..922f551
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..20e2a01
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..0fb7229
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,35 @@
+[versions]
+agp = "8.13.2"
+kotlin = "2.0.0"
+coreKtx = "1.17.0"
+junit = "4.13.2"
+junitVersion = "1.3.0"
+espressoCore = "3.7.0"
+appcompat = "1.7.1"
+material = "1.13.0"
+activity = "1.11.0"
+constraintlayout = "2.2.1"
+mpandroidchart = "v3.1.0"
+ksp = "2.0.0-1.0.22"
+room = "2.6.1"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+mpandroidchart = { group = "com.github.PhilJay", name = "MPAndroidChart", version.ref = "mpandroidchart" }
+androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
+androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
+androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
+
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3dc7209
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 11 14:48:40 ICT 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..0f4dc76
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,24 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven { url = uri("https://jitpack.io") }
+ }
+}
+
+rootProject.name = "kasirapp"
+include(":app")