first commit

This commit is contained in:
Fadhlul Wafi 2026-01-15 15:58:24 +07:00
commit 0fb0f7c548
111 changed files with 3507 additions and 0 deletions

15
.gitignore vendored Normal file
View File

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

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
kasirapp

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

18
.idea/deploymentTargetSelector.xml generated Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-09T07:43:45.340244700Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=y5zxozrgyhcafun7" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

18
.idea/deviceManager.xml generated Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
<option name="groupByAttributes">
<list>
<option value="Type" />
</list>
</option>
</component>
</project>

19
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

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

View File

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

View File

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

View File

@ -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.<init>(IncrementalContextBase.kt:103)
at com.google.devtools.ksp.IncrementalContext.<init>(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)

View File

@ -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.<init>(IncrementalContextBase.kt:103)
at com.google.devtools.ksp.IncrementalContext.<init>(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)

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

53
app/build.gradle.kts Normal file
View File

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

21
app/proguard-rules.pro vendored Normal file
View File

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

View File

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

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Kasirapp">
<activity android:name=".ReceiptPreviewActivity" />
<activity
android:name=".LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" />
<activity android:name=".AdminDashboardActivity" />
<activity android:name=".ManageMenuActivity" />
<activity android:name=".SalesReportActivity" />
<activity android:name=".PaymentManagementActivity" />
<activity android:name=".SalesTrafficActivity" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

View File

@ -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<Button>(R.id.btnManageMenu)
val btnSalesReport = findViewById<Button>(R.id.btnSalesReport)
val btnSalesTraffic = findViewById<Button>(R.id.btnSalesTraffic)
val btnPaymentManagement = findViewById<Button>(R.id.btnPaymentManagement)
val btnLogoutAdmin = findViewById<Button>(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()
}
}
}

View File

@ -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<MainActivity.CartItem>,
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<CartAdapter.CartViewHolder>() {
fun updateData(newItems: List<MainActivity.CartItem>) {
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) }
}
}
}

View File

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

View File

@ -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<Product>()
private val cart = mutableListOf<CartItem>()
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<TextView>(R.id.subtitle_text).text = "Selamat datang, ${userRole.replaceFirstChar { it.uppercase() }}!"
findViewById<Button>(R.id.btn_logout).setOnClickListener { logout() }
findViewById<Button>(R.id.btn_dine_in).setOnClickListener { showSeatingChoiceDialog() }
findViewById<Button>(R.id.btn_takeaway).setOnClickListener {
currentOrderMode = OrderMode.TAKEAWAY
Toast.makeText(this, "Mode Takeaway Dipilih", Toast.LENGTH_SHORT).show()
}
findViewById<Button>(R.id.btn_clear_cart).setOnClickListener { clearCart() }
findViewById<Button>(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<CardView>(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<CardView>(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<EditText>(R.id.inputCustomerName)
val tableSpinner = dialogView.findViewById<Spinner>(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<TextView>(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)
}
}

View File

@ -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<Product>()
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<View>(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<EditText>(R.id.et_menu_name)
val etMenuPrice = dialogView.findViewById<EditText>(R.id.et_menu_price)
val etMenuStock = dialogView.findViewById<EditText>(R.id.et_menu_stock)
val spinnerCategory = dialogView.findViewById<Spinner>(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<Product>,
private val onEdit: (Product) -> Unit,
private val onDelete: (Product) -> Unit
) :
RecyclerView.Adapter<MenuAdapter.MenuViewHolder>() {
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) }
}
}
}
}

View File

@ -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<ListView>(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
}
}

View File

@ -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<Product>,
private val onAddClick: (Product) -> Unit
) : RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() {
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<Product>) {
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) }
}
}
}

View File

@ -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<TextView>(R.id.tv_receipt)
val btnPrintPdf = findViewById<Button>(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()
}
}
}

View File

@ -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<TransactionWithItems>) {
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<Long, Long> {
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<TransactionWithItems>, private val context: Context) :
RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder>() {
fun updateData(newTransactions: List<TransactionWithItems>) {
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)
}
}
}
}
}

View File

@ -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<Transaction>, filter: FilterOption) {
val salesData = getSalesDataForChart(transactions, filter)
val entries = ArrayList<Entry>()
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<Transaction>, filter: FilterOption): Map<String, Int> {
val salesByDay = LinkedHashMap<String, Int>()
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<Long, Long> {
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<String>) : ValueFormatter() {
override fun getFormattedValue(value: Float):
String = dates.getOrNull(value.toInt()) ?: ""
}
}

View File

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

View File

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

View File

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

View File

@ -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<Product>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(product: Product)
@Insert
suspend fun insertAll(products: List<Product>)
@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?
}

View File

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

View File

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

View File

@ -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<TransactionItem>)
@RoomTransaction
@Query("SELECT * FROM transactions ORDER BY timestamp DESC")
suspend fun getTransactionsWithItems(): List<TransactionWithItems>
@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<TransactionWithItems>
// 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<Transaction>
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white"/>
<stroke android:width="1dp" android:color="#EEEEEE"/>
<corners android:radius="8dp"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="#CCCCCC" />
<corners android:radius="8dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13H5v-2h14v2z"/>
</vector>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".AdminDashboardActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Admin Dashboard"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btnManageMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kelola Menu"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnSalesReport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Laporan Penjualan"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnSalesTraffic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Traffic Penjualan"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnPaymentManagement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Manajemen Pembayaran"/>
</LinearLayout>
<Button
android:id="@+id/btnLogoutAdmin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:backgroundTint="@android:color/holo_red_light"
android:text="Logout" />
</RelativeLayout>

View File

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- Header -->
<RelativeLayout
android:id="@+id/header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:padding="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🛒 KASIR APP"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/subtitle_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Selamat datang, Kasir!"
android:textColor="@android:color/white" />
</LinearLayout>
<Button
android:id="@+id/btn_logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="LOGOUT"
app:backgroundTint="@android:color/holo_red_light" />
</RelativeLayout>
<!-- Search Bar -->
<EditText
android:id="@+id/et_search_menu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:hint="Cari menu..."
android:inputType="text"
android:padding="12dp"
android:background="@drawable/edit_text_background"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Cart Section (This will always be at the bottom) -->
<LinearLayout
android:id="@+id/cart_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#F5F5F5"
android:orientation="vertical"
android:padding="16dp"
android:elevation="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🛒 KERANJANG"
android:textAppearance="@android:style/TextAppearance.Large"
android:textColor="@android:color/black"
android:textStyle="bold"/>
<TextView
android:id="@+id/empty_cart_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:text="Keranjang masih kosong" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_cart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="120dp"
app:layout_constrainedHeight="true"
tools:listitem="@layout/item_cart"
tools:itemCount="2" />
<!-- Bottom Buttons and Total -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_dine_in"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_weight="1"
app:backgroundTint="@android:color/holo_blue_dark"
android:text="DINE-IN" />
<Button
android:id="@+id/btn_takeaway"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
app:backgroundTint="@android:color/darker_gray"
android:text="TAKEAWAY" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TOTAL:"
android:textAppearance="@android:style/TextAppearance.Large"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/total_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:text="Rp 0"
android:textAppearance="@android:style/TextAppearance.Large"
android:textColor="@android:color/holo_green_dark" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_clear_cart"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
app:backgroundTint="@android:color/holo_red_light"
android:text="HAPUS SEMUA" />
<Button
android:id="@+id/btn_checkout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
app:backgroundTint="@android:color/holo_green_dark"
android:text="BAYAR" />
</LinearLayout>
</LinearLayout>
<!-- Product List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_products"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="16dp"
app:layout_constraintTop_toBottomOf="@id/et_search_menu"
app:layout_constraintBottom_toTopOf="@id/cart_section"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ManageMenuActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
tools:listitem="@layout/item_menu" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@android:drawable/ic_input_add"
app:tint="@android:color/white"
android:contentDescription="Tambah Menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".PaymentManagementActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Manajemen Pembayaran"
android:textSize="24sp"
android:textStyle="bold" />
<ListView
android:id="@+id/lv_payment_methods"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
</LinearLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".ReceiptPreviewActivity">
<TextView
android:id="@+id/tv_receipt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:text="Struk akan ditampilkan di sini" />
<Button
android:id="@+id/btn_print_pdf"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="Cetak ke PDF" />
</RelativeLayout>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".SalesReportActivity">
<LinearLayout
android:id="@+id/header_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentTop="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Laporan Penjualan"
android:textSize="24sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filter:"/>
<Spinner
android:id="@+id/spinner_date_filter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<TextView
android:id="@+id/tv_total_sales"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Total Penjualan: Rp 0"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_total_transactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Total Transaksi: 0"
android:textSize="18sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_transactions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/header_layout"
android:layout_marginTop="16dp"
tools:listitem="@layout/item_transaction" />
<TextView
android:id="@+id/tv_no_transactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Belum ada transaksi."
android:textSize="18sp"
android:layout_centerInParent="true"
android:visibility="gone"
tools:visibility="visible"/>
</RelativeLayout>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".SalesTrafficActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Traffic Penjualan"
android:textSize="24sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filter:"/>
<Spinner
android:id="@+id/spinner_date_filter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/line_chart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".TransactionDetailActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Detail Transaksi"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_detail_transaction_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="16sp"
tools:text="ID: 123456789" />
<TextView
android:id="@+id/tv_detail_customer_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
tools:text="Nama: Pelanggan" />
<TextView
android:id="@+id/tv_detail_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
tools:text="Tanggal: 01/01/2023 12:00" />
<TextView
android:id="@+id/tv_detail_payment_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
tools:text="Metode: Cash" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--------------------"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Item Dibeli:"
android:textStyle="bold"/>
<LinearLayout
android:id="@+id/ll_transaction_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--------------------"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/tv_detail_total"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textSize="18sp"
android:textStyle="bold"
tools:text="TOTAL: Rp 50.000" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/et_menu_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nama Menu" />
<EditText
android:id="@+id/et_menu_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Harga"
android:inputType="number" />
<EditText
android:id="@+id/et_menu_stock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Stok"
android:inputType="number" />
<Spinner
android:id="@+id/spinner_menu_category"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/inputCustomerName"
android:hint="@string/nama_customer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no" />
<TextView
android:text="@string/pilih_nomor_meja"
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/spinnerTable"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="24dp">
<ImageView
android:id="@+id/qrisImage"
android:layout_width="401dp"
android:layout_height="497dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/qris_real" />
<TextView
android:id="@+id/qrisTotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="18sp" />
</LinearLayout>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pilih Area Tempat Duduk"
android:textSize="20sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:id="@+id/card_indoor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@drawable/resto_indoor"
android:scaleType="centerCrop"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Indoor"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_outdoor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@drawable/resto_outdoor"
android:scaleType="centerCrop"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Outdoor"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_cart_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:textAppearance="?attr/textAppearanceListItem"
tools:text="Nama Item" />
<TextView
android:id="@+id/tv_cart_item_total_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
tools:text="Rp 20.000" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageButton
android:id="@+id/btn_decrease_quantity"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Kurangi jumlah"
android:src="@drawable/ic_baseline_remove_24"
app:tint="?attr/colorPrimary" />
<TextView
android:id="@+id/tv_cart_item_quantity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textSize="16sp"
tools:text="1" />
<ImageButton
android:id="@+id/btn_increase_quantity"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Tambah jumlah"
android:src="@drawable/ic_baseline_add_24"
app:tint="?attr/colorPrimary" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageButton
android:id="@+id/btn_remove_from_cart"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Hapus item"
android:src="@android:drawable/ic_menu_delete"
app:tint="@android:color/holo_red_light" />
</LinearLayout>
<TextView
android:id="@+id/tv_cart_item_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp"
android:visibility="gone"
tools:text="Catatan: Pedas" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
</LinearLayout>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_menu_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama Menu"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_menu_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp 10.000" />
</LinearLayout>
<Button
android:id="@+id/btn_edit_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Edit" />
<Button
android:id="@+id/btn_delete_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Hapus" />
</LinearLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_product_image"
android:layout_width="match_parent"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher_background" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/tv_product_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama Produk"
android:textAppearance="?attr/textAppearanceListItem"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_product_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp 0"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_product_stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stok: 0"
android:textSize="12sp" />
<Button
android:id="@+id/btn_add_to_cart"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Tambah" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tv_transaction_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ID Transaksi: 123456789"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_transaction_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total: Rp 50.000" />
<TextView
android:id="@+id/tv_transaction_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tanggal: 2024-05-20" />
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Kasirapp" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="light_gray">#F5F5F5</color>
</resources>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">KasirApp</string>
<string name="mode_takeaway">Mode: Takeaway</string>
<string name="stock_format">Stok: %d</string>
<string name="add">Tambah</string>
<string name="sold_out">Habis</string>
<string name="note_format">Catatan: %s</string>
<string name="area_indoor">Area: Indoor</string>
<string name="area_outdoor">Area: Outdoor</string>
<string name="stock_not_enough">Stok tidak cukup</string>
<string name="cart_is_empty">Keranjang kosong!</string>
<string name="customer_data">Data Pemesan</string>
<string name="next">Lanjut</string>
<string name="name_cannot_be_empty">Nama tidak boleh kosong</string>
<string name="cancel">Batal</string>
<string name="choose_payment_method">Pilih Metode Pembayaran</string>
<string name="note_for">Catatan untuk %s</string>
<string name="save">Simpan</string>
<string name="receipt_header">STRUK PEMBAYARAN</string>
<string name="nama_customer">Nama Customer</string>
<string name="pilih_nomor_meja">Pilih Nomor Meja</string>
</resources>

View File

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Kasirapp" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.Kasirapp" parent="Base.Theme.Kasirapp" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

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