first commit
15
.gitignore
vendored
Normal 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
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
kasirapp
|
||||
6
.idea/AndroidProjectSystem.xml
generated
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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>
|
||||
45
.kotlin/errors/errors-1765439590219.log
Normal 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)
|
||||
|
||||
|
||||
45
.kotlin/errors/errors-1765747395531.log
Normal 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)
|
||||
|
||||
|
||||
4
.kotlin/errors/errors-1765978610358.log
Normal 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
|
||||
|
||||
58
.kotlin/errors/errors-1766255274355.log
Normal 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)
|
||||
|
||||
|
||||
58
.kotlin/errors/errors-1766255487235.log
Normal 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
@ -0,0 +1 @@
|
||||
/build
|
||||
53
app/build.gradle.kts
Normal 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
@ -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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
48
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
64
app/src/main/java/com/example/kasirapp/CartAdapter.kt
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
58
app/src/main/java/com/example/kasirapp/LoginActivity.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
402
app/src/main/java/com/example/kasirapp/MainActivity.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
144
app/src/main/java/com/example/kasirapp/ManageMenuActivity.kt
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/com/example/kasirapp/ProductAdapter.kt
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
187
app/src/main/java/com/example/kasirapp/SalesReportActivity.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
150
app/src/main/java/com/example/kasirapp/SalesTrafficActivity.kt
Normal 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()) ?: ""
|
||||
}
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
32
app/src/main/java/com/example/kasirapp/data/AppDatabase.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
app/src/main/java/com/example/kasirapp/data/Product.kt
Normal 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
|
||||
)
|
||||
29
app/src/main/java/com/example/kasirapp/data/ProductDao.kt
Normal 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?
|
||||
}
|
||||
16
app/src/main/java/com/example/kasirapp/data/Promo.kt
Normal 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
|
||||
)
|
||||
16
app/src/main/java/com/example/kasirapp/data/Transaction.kt
Normal 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
|
||||
)
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
@ -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>
|
||||
)
|
||||
BIN
app/src/main/res/drawable/ayam_bakar.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
6
app/src/main/res/drawable/bg_card.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/edit_text_background.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/es_jeruk.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
app/src/main/res/drawable/es_teh.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
10
app/src/main/res/drawable/ic_baseline_add_24.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/ic_baseline_remove_24.xml
Normal 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>
|
||||
74
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
||||
4
app/src/main/res/drawable/ic_qris_sample.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</selector>
|
||||
BIN
app/src/main/res/drawable/jus_alpukat.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
app/src/main/res/drawable/keripik.png
Normal file
|
After Width: | Height: | Size: 167 KiB |
BIN
app/src/main/res/drawable/kopi.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
app/src/main/res/drawable/mie_goreng.png
Normal file
|
After Width: | Height: | Size: 235 KiB |
BIN
app/src/main/res/drawable/nasi_goreng.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
app/src/main/res/drawable/qris_real.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
app/src/main/res/drawable/resto_indoor.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
app/src/main/res/drawable/resto_outdoor.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
app/src/main/res/drawable/roti_bakar.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
app/src/main/res/drawable/sate_ayam.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
60
app/src/main/res/layout/activity_admin_dashboard.xml
Normal 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>
|
||||
195
app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||
26
app/src/main/res/layout/activity_manage_menu.xml
Normal 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>
|
||||
23
app/src/main/res/layout/activity_payment_management.xml
Normal 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>
|
||||
23
app/src/main/res/layout/activity_receipt_preview.xml
Normal 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>
|
||||
79
app/src/main/res/layout/activity_sales_report.xml
Normal 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>
|
||||
43
app/src/main/res/layout/activity_sales_traffic.xml
Normal 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>
|
||||
89
app/src/main/res/layout/activity_transaction_detail.xml
Normal 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>
|
||||
33
app/src/main/res/layout/dialog_add_edit_menu.xml
Normal 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>
|
||||
26
app/src/main/res/layout/dialog_customer_info.xml
Normal 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>
|
||||
24
app/src/main/res/layout/dialog_qris.xml
Normal 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>
|
||||
84
app/src/main/res/layout/dialog_seating_choice.xml
Normal 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>
|
||||
99
app/src/main/res/layout/item_cart.xml
Normal 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>
|
||||
43
app/src/main/res/layout/item_menu.xml
Normal 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>
|
||||
59
app/src/main/res/layout/item_product_card.xml
Normal 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>
|
||||
28
app/src/main/res/layout/item_transaction.xml
Normal 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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
7
app/src/main/res/values-night/themes.xml
Normal 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>
|
||||
4
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="light_gray">#F5F5F5</color>
|
||||
</resources>
|
||||
23
app/src/main/res/values/strings.xml
Normal 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>
|
||||
9
app/src/main/res/values/themes.xml
Normal 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>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal 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>
|
||||