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