mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
Merge branch 'master' into gracia
# Conflicts: # app/build.gradle.kts # app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt # app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
This commit is contained in:
3
.idea/gradle.xml
generated
3
.idea/gradle.xml
generated
@ -6,13 +6,14 @@
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="openjdk-24" />
|
||||
<option name="gradleJvm" value="jbr-21" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -4,7 +4,7 @@
|
||||
<option name="optimizeImportsOnTheFly" value="true" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="24" project-jdk-type="JavaSDK">
|
||||
<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">
|
||||
|
549
.idea/other.xml
generated
549
.idea/other.xml
generated
@ -1,549 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="direct_access_persist.xml">
|
||||
<option name="deviceSelectionList">
|
||||
<list>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="27" />
|
||||
<option name="brand" value="DOCOMO" />
|
||||
<option name="codename" value="F01L" />
|
||||
<option name="id" value="F01L" />
|
||||
<option name="manufacturer" value="FUJITSU" />
|
||||
<option name="name" value="F-01L" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1280" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OnePlus" />
|
||||
<option name="codename" value="OP5552L1" />
|
||||
<option name="id" value="OP5552L1" />
|
||||
<option name="manufacturer" value="OnePlus" />
|
||||
<option name="name" value="CPH2415" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2412" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="OPPO" />
|
||||
<option name="codename" value="OP573DL1" />
|
||||
<option name="id" value="OP573DL1" />
|
||||
<option name="manufacturer" value="OPPO" />
|
||||
<option name="name" value="CPH2557" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="28" />
|
||||
<option name="brand" value="DOCOMO" />
|
||||
<option name="codename" value="SH-01L" />
|
||||
<option name="id" value="SH-01L" />
|
||||
<option name="manufacturer" value="SHARP" />
|
||||
<option name="name" value="AQUOS sense2 SH-01L" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2160" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="Lenovo" />
|
||||
<option name="codename" value="TB370FU" />
|
||||
<option name="id" value="TB370FU" />
|
||||
<option name="manufacturer" value="Lenovo" />
|
||||
<option name="name" value="Tab P12" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1840" />
|
||||
<option name="screenY" value="2944" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a15" />
|
||||
<option name="id" value="a15" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A15" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a35x" />
|
||||
<option name="id" value="a35x" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="A35" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="31" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="a51" />
|
||||
<option name="id" value="a51" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy A51" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="akita" />
|
||||
<option name="id" value="akita" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="arcfox" />
|
||||
<option name="id" value="arcfox" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="razr plus 2024" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="1272" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="austin" />
|
||||
<option name="id" value="austin" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g 5G (2022)" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="b0q" />
|
||||
<option name="id" value="b0q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S22 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3088" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="32" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="bluejay" />
|
||||
<option name="id" value="bluejay" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 6a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="caiman" />
|
||||
<option name="id" value="caiman" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="960" />
|
||||
<option name="screenY" value="2142" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="comet" />
|
||||
<option name="id" value="comet" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro Fold" />
|
||||
<option name="screenDensity" value="390" />
|
||||
<option name="screenX" value="2076" />
|
||||
<option name="screenY" value="2152" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="29" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="crownqlteue" />
|
||||
<option name="id" value="crownqlteue" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Note9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2220" />
|
||||
<option name="screenY" value="1080" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="dm2q" />
|
||||
<option name="id" value="dm2q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="S23 Plus" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="dm3q" />
|
||||
<option name="id" value="dm3q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S23 Ultra" />
|
||||
<option name="screenDensity" value="600" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3088" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e1q" />
|
||||
<option name="id" value="e1q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="e3q" />
|
||||
<option name="id" value="e3q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S24 Ultra" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="3120" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="eos" />
|
||||
<option name="id" value="eos" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Eos" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="384" />
|
||||
<option name="screenY" value="384" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix" />
|
||||
<option name="id" value="felix" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix" />
|
||||
<option name="id" value="felix" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="felix_camera" />
|
||||
<option name="id" value="felix_camera" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="2208" />
|
||||
<option name="screenY" value="1840" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="fogona" />
|
||||
<option name="id" value="fogona" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2024" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="g0q" />
|
||||
<option name="id" value="g0q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-S906U1" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gta9pwifi" />
|
||||
<option name="id" value="gta9pwifi" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-X210" />
|
||||
<option name="screenDensity" value="240" />
|
||||
<option name="screenX" value="1200" />
|
||||
<option name="screenY" value="1920" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts7xllite" />
|
||||
<option name="id" value="gts7xllite" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-T738U" />
|
||||
<option name="screenDensity" value="340" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts8uwifi" />
|
||||
<option name="id" value="gts8uwifi" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S8 Ultra" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="1848" />
|
||||
<option name="screenY" value="2960" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts8wifi" />
|
||||
<option name="id" value="gts8wifi" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S8" />
|
||||
<option name="screenDensity" value="274" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="gts9fe" />
|
||||
<option name="id" value="gts9fe" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Tab S9 FE 5G" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="1440" />
|
||||
<option name="screenY" value="2304" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="husky" />
|
||||
<option name="id" value="husky" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8 Pro" />
|
||||
<option name="screenDensity" value="390" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="java" />
|
||||
<option name="id" value="java" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="G20" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="komodo" />
|
||||
<option name="id" value="komodo" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9 Pro XL" />
|
||||
<option name="screenDensity" value="360" />
|
||||
<option name="screenX" value="1008" />
|
||||
<option name="screenY" value="2244" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="lynx" />
|
||||
<option name="id" value="lynx" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 7a" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="motorola" />
|
||||
<option name="codename" value="maui" />
|
||||
<option name="id" value="maui" />
|
||||
<option name="manufacturer" value="Motorola" />
|
||||
<option name="name" value="moto g play - 2023" />
|
||||
<option name="screenDensity" value="280" />
|
||||
<option name="screenX" value="720" />
|
||||
<option name="screenY" value="1600" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="o1q" />
|
||||
<option name="id" value="o1q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S21" />
|
||||
<option name="screenDensity" value="421" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="31" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="oriole" />
|
||||
<option name="id" value="oriole" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 6" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="panther" />
|
||||
<option name="id" value="panther" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 7" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q5q" />
|
||||
<option name="id" value="q5q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Z Fold5" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1812" />
|
||||
<option name="screenY" value="2176" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q6q" />
|
||||
<option name="id" value="q6q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Z Fold6" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1856" />
|
||||
<option name="screenY" value="2160" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="r11" />
|
||||
<option name="id" value="r11" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Watch" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="384" />
|
||||
<option name="screenY" value="384" />
|
||||
<option name="type" value="WEAR_OS" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="r11q" />
|
||||
<option name="id" value="r11q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="SM-S711U" />
|
||||
<option name="screenDensity" value="450" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="30" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="redfin" />
|
||||
<option name="id" value="redfin" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 5" />
|
||||
<option name="screenDensity" value="440" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="shiba" />
|
||||
<option name="id" value="shiba" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 8" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="t2q" />
|
||||
<option name="id" value="t2q" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S21 Plus" />
|
||||
<option name="screenDensity" value="394" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="33" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tangorpro" />
|
||||
<option name="id" value="tangorpro" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel Tablet" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="1600" />
|
||||
<option name="screenY" value="2560" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="34" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="35" />
|
||||
<option name="brand" value="google" />
|
||||
<option name="codename" value="tokay" />
|
||||
<option name="id" value="tokay" />
|
||||
<option name="manufacturer" value="Google" />
|
||||
<option name="name" value="Pixel 9" />
|
||||
<option name="screenDensity" value="420" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2424" />
|
||||
</PersistentDeviceSelectionData>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -1,5 +1,4 @@
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
|
||||
import java.util.Properties
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.jetbrains.kotlin.android)
|
||||
@ -9,6 +8,14 @@ plugins {
|
||||
// id("com.google.dagger.hilt.android")
|
||||
}
|
||||
|
||||
val localProperties = Properties().apply {
|
||||
val localPropertiesFile = rootProject.file("local.properties")
|
||||
if (localPropertiesFile.exists()) {
|
||||
load(localPropertiesFile.inputStream())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
namespace = "com.alya.ecommerce_serang"
|
||||
compileSdk = 35
|
||||
@ -21,11 +28,19 @@ android {
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField(
|
||||
"String",
|
||||
"BASE_URL",
|
||||
"\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\""
|
||||
)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
buildConfigField("String", "BASE_URL", "\"http://192.168.20.238:3000/\"")
|
||||
buildConfigField("String",
|
||||
"BASE_URL",
|
||||
"\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\"")
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
@ -33,7 +48,9 @@ android {
|
||||
)
|
||||
}
|
||||
debug {
|
||||
buildConfigField("String", "BASE_URL", "\"http://192.168.20.238:3000/\"")
|
||||
buildConfigField("String",
|
||||
"BASE_URL",
|
||||
"\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\"")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -17,6 +19,24 @@
|
||||
android:theme="@style/Theme.Ecommerce_serang"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".data.api.response.cart.CartActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.address.EditAddressActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.address.AddAddressActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.address.AddressActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.ShippingActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.CheckoutActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.profile.mystore.profile.payment_info.DetailPaymentInfoActivity"
|
||||
android:exported="false" />
|
||||
|
@ -0,0 +1,11 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CartItem (
|
||||
@SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@SerializedName("quantity")
|
||||
val quantity: Int
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
|
||||
data class CheckoutData(
|
||||
val orderRequest: Any, // Can be OrderRequest or OrderRequestBuy
|
||||
val productName: String? = "",
|
||||
val productImageUrl: String = "",
|
||||
val productPrice: Double = 0.0,
|
||||
val sellerName: String = "",
|
||||
val sellerImageUrl: String? = null,
|
||||
val sellerId: Int = 0,
|
||||
val quantity: Int = 1,
|
||||
val isBuyNow: Boolean = false,
|
||||
val cartItems: List<CartItemsItem> = emptyList()
|
||||
)
|
@ -0,0 +1,19 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CourierCostRequest(
|
||||
@SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@SerializedName("items")
|
||||
val itemCost: CostProduct
|
||||
)
|
||||
|
||||
data class CostProduct (
|
||||
@SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@SerializedName("quantity")
|
||||
val quantity: Int
|
||||
)
|
@ -0,0 +1,41 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateAddressRequest (
|
||||
@SerializedName("latitude")
|
||||
val lat: Double,
|
||||
|
||||
@SerializedName("longitude")
|
||||
val long: Double,
|
||||
|
||||
@SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@SerializedName("subdistrict")
|
||||
val subDistrict: String,
|
||||
|
||||
@SerializedName("city_id")
|
||||
val cityId: Int,
|
||||
|
||||
@SerializedName("province_id")
|
||||
val provId: Int,
|
||||
@SerializedName("postal_code")
|
||||
val postCode: String? = null,
|
||||
|
||||
@SerializedName("detail")
|
||||
val detailAddress: String? = null,
|
||||
|
||||
@SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@SerializedName("recipient")
|
||||
val recipient: String,
|
||||
|
||||
@SerializedName("phone")
|
||||
val phone: String,
|
||||
|
||||
@SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean
|
||||
|
||||
)
|
@ -0,0 +1,29 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderRequest (
|
||||
@SerializedName("address_id")
|
||||
val addressId : Int,
|
||||
|
||||
@SerializedName("payment_method_id")
|
||||
val paymentMethodId : Int,
|
||||
|
||||
@SerializedName("ship_price")
|
||||
val shipPrice : Int,
|
||||
|
||||
@SerializedName("ship_name")
|
||||
val shipName : String,
|
||||
|
||||
@SerializedName("ship_service")
|
||||
val shipService : String,
|
||||
|
||||
@SerializedName("is_negotiable")
|
||||
val isNego: Boolean,
|
||||
|
||||
@SerializedName("cart_items_id")
|
||||
val cartItemId: List<Int>,
|
||||
|
||||
@SerializedName("ship_etd")
|
||||
val shipEtd: String
|
||||
)
|
@ -0,0 +1,33 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderRequestBuy (
|
||||
@SerializedName("address_id")
|
||||
val addressId : Int,
|
||||
|
||||
@SerializedName("payment_method_id")
|
||||
val paymentMethodId : Int,
|
||||
|
||||
@SerializedName("ship_price")
|
||||
val shipPrice : Int,
|
||||
|
||||
@SerializedName("ship_name")
|
||||
val shipName : String,
|
||||
|
||||
@SerializedName("ship_service")
|
||||
val shipService : String,
|
||||
|
||||
@SerializedName("is_negotiable")
|
||||
val isNego: Boolean,
|
||||
|
||||
@SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@SerializedName("quantity")
|
||||
val quantity : Int,
|
||||
|
||||
|
||||
@SerializedName("ship_etd")
|
||||
val shipEtd: String
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UpdateCart (
|
||||
@SerializedName("cart_item_id")
|
||||
val cartItemId: Int,
|
||||
|
||||
@SerializedName("quantity")
|
||||
val quantity: Int
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.response.product.Product
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateProductResponse(
|
||||
|
@ -1,33 +0,0 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DetailStoreProductResponse(
|
||||
|
||||
@field:SerializedName("store")
|
||||
val store: StoreProduct,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class StoreProduct(
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String,
|
||||
|
||||
@field:SerializedName("store_type")
|
||||
val storeType: String,
|
||||
|
||||
@field:SerializedName("store_location")
|
||||
val storeLocation: String,
|
||||
|
||||
@field:SerializedName("store_image")
|
||||
val storeImage: Any,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
@ -1,40 +0,0 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.Store
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class StoreResponse(
|
||||
|
||||
@field:SerializedName("shipping")
|
||||
val shipping: List<ShippingItem>,
|
||||
|
||||
@field:SerializedName("payment")
|
||||
val payment: List<PaymentItem>,
|
||||
|
||||
@field:SerializedName("store")
|
||||
val store: Store,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class ShippingItem(
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String
|
||||
)
|
||||
|
||||
data class PaymentItem(
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: String,
|
||||
|
||||
@field:SerializedName("bank_num")
|
||||
val bankNum: String,
|
||||
|
||||
@field:SerializedName("bank_name")
|
||||
val bankName: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.auth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.auth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.auth
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.cart
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AddCartResponse(
|
||||
|
||||
@field:SerializedName("data")
|
||||
val data: Data,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class Data(
|
||||
|
||||
@field:SerializedName("cart_id")
|
||||
val cartId: Int,
|
||||
|
||||
@field:SerializedName("quantity")
|
||||
val quantity: Int,
|
||||
|
||||
@field:SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.cart
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.alya.ecommerce_serang.R
|
||||
|
||||
class CartActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.activity_cart)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.cart
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ListCartResponse(
|
||||
|
||||
@field:SerializedName("data")
|
||||
val data: List<DataItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class DataItem(
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
|
||||
@field:SerializedName("cart_items")
|
||||
val cartItems: List<CartItemsItem>,
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("most_recent_item")
|
||||
val mostRecentItem: String
|
||||
)
|
||||
|
||||
data class CartItemsItem(
|
||||
|
||||
@field:SerializedName("quantity")
|
||||
val quantity: Int,
|
||||
|
||||
@field:SerializedName("price")
|
||||
val price: Int,
|
||||
|
||||
@field:SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("cart_item_id")
|
||||
val cartItemId: Int,
|
||||
|
||||
@field:SerializedName("product_name")
|
||||
val productName: String
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.cart
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UpdateCartResponse(
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
@ -0,0 +1,33 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CourierCostResponse(
|
||||
|
||||
@field:SerializedName("courierCosts")
|
||||
val courierCosts: List<CourierCostsItem>
|
||||
)
|
||||
|
||||
data class CourierCostsItem(
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("services")
|
||||
val services: List<ServicesItem>
|
||||
)
|
||||
|
||||
data class ServicesItem(
|
||||
|
||||
@field:SerializedName("cost")
|
||||
val cost: Int,
|
||||
|
||||
@field:SerializedName("etd")
|
||||
val etd: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String
|
||||
)
|
@ -0,0 +1,105 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateOrderResponse(
|
||||
|
||||
@field:SerializedName("shipping")
|
||||
val shipping: Shipping,
|
||||
|
||||
@field:SerializedName("order_item")
|
||||
val orderItem: List<OrderItemItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String,
|
||||
|
||||
@field:SerializedName("order")
|
||||
val order: Order
|
||||
)
|
||||
|
||||
data class Shipping(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: Int? = null,
|
||||
|
||||
@field:SerializedName("etd")
|
||||
val etd: String,
|
||||
|
||||
@field:SerializedName("price")
|
||||
val price: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("name")
|
||||
val name: String,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
||||
|
||||
data class OrderItemItem(
|
||||
|
||||
@field:SerializedName("quantity")
|
||||
val quantity: Int,
|
||||
|
||||
@field:SerializedName("price")
|
||||
val price: String,
|
||||
|
||||
@field:SerializedName("subtotal")
|
||||
val subtotal: String,
|
||||
|
||||
@field:SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int
|
||||
)
|
||||
|
||||
data class Order(
|
||||
|
||||
@field:SerializedName("payment_method_id")
|
||||
val paymentMethodId: Int,
|
||||
|
||||
@field:SerializedName("auto_completed_at")
|
||||
val autoCompletedAt: String? = null,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("is_negotiable")
|
||||
val isNegotiable: Boolean,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: String? = null,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ListCityResponse(
|
||||
|
||||
@field:SerializedName("cities")
|
||||
val cities: List<CitiesItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class CitiesItem(
|
||||
|
||||
@field:SerializedName("city_name")
|
||||
val cityName: String,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: String
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ListProvinceResponse(
|
||||
|
||||
@field:SerializedName("provinces")
|
||||
val provinces: List<ProvincesItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class ProvincesItem(
|
||||
|
||||
@field:SerializedName("province")
|
||||
val province: String,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: String
|
||||
)
|
@ -0,0 +1,129 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderDetailResponse(
|
||||
|
||||
@field:SerializedName("orders")
|
||||
val orders: Orders,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class OrderItemsItem(
|
||||
|
||||
@field:SerializedName("review_id")
|
||||
val reviewId: Int? = null,
|
||||
|
||||
@field:SerializedName("quantity")
|
||||
val quantity: Int,
|
||||
|
||||
@field:SerializedName("price")
|
||||
val price: Int,
|
||||
|
||||
@field:SerializedName("subtotal")
|
||||
val subtotal: Int,
|
||||
|
||||
@field:SerializedName("product_image")
|
||||
val productImage: String? = null,
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("product_price")
|
||||
val productPrice: Int,
|
||||
|
||||
@field:SerializedName("product_name")
|
||||
val productName: String
|
||||
)
|
||||
|
||||
data class Orders(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: String,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("voucher_code")
|
||||
val voucherCode: String? = null,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
|
||||
@field:SerializedName("etd")
|
||||
val etd: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("cancel_date")
|
||||
val cancelDate: String,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("shipment_status")
|
||||
val shipmentStatus: String,
|
||||
|
||||
@field:SerializedName("order_items")
|
||||
val orderItems: List<OrderItemsItem>,
|
||||
|
||||
@field:SerializedName("auto_completed_at")
|
||||
val autoCompletedAt: String,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean,
|
||||
|
||||
@field:SerializedName("voucher_name")
|
||||
val voucherName: String? = null,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("payment_method_id")
|
||||
val paymentMethodId: Int,
|
||||
|
||||
@field:SerializedName("cancel_reason")
|
||||
val cancelReason: String,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("shipment_price")
|
||||
val shipmentPrice: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: Int? = null,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
@ -0,0 +1,91 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderListResponse(
|
||||
|
||||
@field:SerializedName("orders")
|
||||
val orders: List<OrdersItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class OrdersItem(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: String,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("voucher_code")
|
||||
val voucherCode: String? = null,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("shipment_status")
|
||||
val shipmentStatus: String,
|
||||
|
||||
@field:SerializedName("order_items")
|
||||
val orderItems: List<OrderItemsItem>,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean,
|
||||
|
||||
@field:SerializedName("voucher_name")
|
||||
val voucherName: String? = null,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("payment_method_id")
|
||||
val paymentMethodId: Int,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("shipment_price")
|
||||
val shipmentPrice: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: Int? = null,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.google.gson.annotations.SerializedName
|
@ -1,13 +1,13 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AllStoreResponse(
|
||||
|
||||
@field:SerializedName("store")
|
||||
@field:SerializedName("store")
|
||||
val store: AllStore,
|
||||
|
||||
@field:SerializedName("message")
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.google.gson.annotations.SerializedName
|
@ -0,0 +1,63 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DetailStoreProductResponse(
|
||||
|
||||
@field:SerializedName("store")
|
||||
val store: StoreProduct,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class PaymentInfoItem(
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: String,
|
||||
|
||||
@field:SerializedName("bank_num")
|
||||
val bankNum: String,
|
||||
|
||||
@field:SerializedName("name")
|
||||
val name: String
|
||||
)
|
||||
|
||||
data class StoreProduct(
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
|
||||
@field:SerializedName("shipping_service")
|
||||
val shippingService: List<ShippingServiceItem>,
|
||||
|
||||
@field:SerializedName("store_rating")
|
||||
val storeRating: String,
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String,
|
||||
|
||||
@field:SerializedName("store_type")
|
||||
val storeType: String,
|
||||
|
||||
@field:SerializedName("payment_info")
|
||||
val paymentInfo: List<PaymentInfoItem>,
|
||||
|
||||
@field:SerializedName("store_location")
|
||||
val storeLocation: String,
|
||||
|
||||
@field:SerializedName("store_image")
|
||||
val storeImage: String,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
||||
|
||||
data class ShippingServiceItem(
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String
|
||||
)
|
@ -1,13 +1,13 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ProductResponse(
|
||||
|
||||
@field:SerializedName("product")
|
||||
@field:SerializedName("product")
|
||||
val product: Product,
|
||||
|
||||
@field:SerializedName("message")
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
@ -1,13 +1,13 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ReviewProductResponse(
|
||||
|
||||
@field:SerializedName("reviews")
|
||||
@field:SerializedName("reviews")
|
||||
val reviews: List<ReviewsItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
@ -0,0 +1,139 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.product
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.Store
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class StoreResponse(
|
||||
|
||||
@field:SerializedName("shipping")
|
||||
val shipping: List<ShippingItem>,
|
||||
|
||||
@field:SerializedName("payment")
|
||||
val payment: List<PaymentItem>,
|
||||
|
||||
@field:SerializedName("store")
|
||||
val store: Store,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class Store(
|
||||
|
||||
@field:SerializedName("approval_reason")
|
||||
val approvalReason: String,
|
||||
|
||||
@field:SerializedName("store_status")
|
||||
val storeStatus: String,
|
||||
|
||||
@field:SerializedName("sppirt")
|
||||
val sppirt: String,
|
||||
|
||||
@field:SerializedName("user_name")
|
||||
val userName: String,
|
||||
|
||||
@field:SerializedName("nib")
|
||||
val nib: String,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("store_type_id")
|
||||
val storeTypeId: Int,
|
||||
|
||||
@field:SerializedName("balance")
|
||||
val balance: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("user_phone")
|
||||
val userPhone: String,
|
||||
|
||||
@field:SerializedName("halal")
|
||||
val halal: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("email")
|
||||
val email: String,
|
||||
|
||||
@field:SerializedName("store_image")
|
||||
val storeImage: String? = null,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean,
|
||||
|
||||
@field:SerializedName("ktp")
|
||||
val ktp: String,
|
||||
|
||||
@field:SerializedName("approval_status")
|
||||
val approvalStatus: String,
|
||||
|
||||
@field:SerializedName("npwp")
|
||||
val npwp: String,
|
||||
|
||||
@field:SerializedName("store_type")
|
||||
val storeType: String,
|
||||
|
||||
@field:SerializedName("is_on_leave")
|
||||
val isOnLeave: Boolean,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("phone")
|
||||
val phone: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("recipient")
|
||||
val recipient: String,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("store_description")
|
||||
val storeDescription: String,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
||||
|
||||
data class ShippingItem(
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String
|
||||
)
|
||||
|
||||
data class PaymentItem(
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: String,
|
||||
|
||||
@field:SerializedName("bank_num")
|
||||
val bankNum: String,
|
||||
|
||||
@field:SerializedName("bank_name")
|
||||
val bankName: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int
|
||||
)
|
@ -0,0 +1,54 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.profile
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AddressResponse(
|
||||
|
||||
@field:SerializedName("addresses")
|
||||
val addresses: List<AddressesItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class AddressesItem(
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("phone")
|
||||
val phone: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("recipient")
|
||||
val recipient: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.profile
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CreateAddressResponse(
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
package com.alya.ecommerce_serang.data.api.response.profile
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.google.gson.annotations.SerializedName
|
@ -1,21 +1,36 @@
|
||||
package com.alya.ecommerce_serang.data.api.retrofit
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.CategoryResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.DetailStoreProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ProfileResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.RegisterResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||
import com.alya.ecommerce_serang.data.api.response.ViewStoreProductsResponse
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.ListCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.UpdateCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.AllProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
@ -25,6 +40,7 @@ import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface ApiService {
|
||||
@ -96,4 +112,30 @@ interface ApiService {
|
||||
@Part halal: MultipartBody.Part?
|
||||
): Response<Unit>
|
||||
|
||||
@GET("cart_item")
|
||||
suspend fun getCart (): Response<ListCartResponse>
|
||||
|
||||
@POST("cart/add")
|
||||
suspend fun addCart(
|
||||
@Body cartRequest: CartItem
|
||||
): Response<AddCartResponse>
|
||||
|
||||
@PUT("cart/update")
|
||||
suspend fun updateCart(
|
||||
@Body updateCart: UpdateCart
|
||||
): Response<UpdateCartResponse>
|
||||
|
||||
@POST("couriercost")
|
||||
suspend fun countCourierCost(
|
||||
@Body courierCost : CourierCostRequest
|
||||
): Response<CourierCostResponse>
|
||||
|
||||
@GET("cities/{id}")
|
||||
suspend fun getCityProvId(
|
||||
@Path("id") provId : Int
|
||||
): Response<ListCityResponse>
|
||||
|
||||
@GET("provinces")
|
||||
suspend fun getListProv(
|
||||
): Response<ListProvinceResponse>
|
||||
}
|
@ -2,7 +2,7 @@ package com.alya.ecommerce_serang.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.alya.ecommerce_serang.data.api.dto.Store
|
||||
import com.alya.ecommerce_serang.data.api.response.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
@ -0,0 +1,210 @@
|
||||
package com.alya.ecommerce_serang.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.DataItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import retrofit2.Response
|
||||
|
||||
class OrderRepository(private val apiService: ApiService) {
|
||||
|
||||
suspend fun fetchProductDetail(productId: Int): ProductResponse? {
|
||||
return try {
|
||||
val response = apiService.getDetailProduct(productId)
|
||||
if (response.isSuccessful) {
|
||||
val productResponse = response.body()
|
||||
Log.d("Order Repository", "Product detail fetched successfully: ${productResponse?.product?.productName}")
|
||||
productResponse
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching product detail. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception fetching product", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createOrder(orderRequest: OrderRequest): Response<CreateOrderResponse> {
|
||||
return try {
|
||||
Log.d("Order Repository", "Creating order. Request details: $orderRequest")
|
||||
val response = apiService.postOrder(orderRequest)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Log.d("Order Repository", "Order created successfully. Response: ${response.body()}")
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Order creation failed. Code: ${response.code()}, Error: $errorBody")
|
||||
}
|
||||
|
||||
response
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception creating order", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createOrderBuyNow(orderRequestBuy: OrderRequestBuy): Response<CreateOrderResponse> {
|
||||
return try {
|
||||
Log.d("Order Repository", "Creating buy now order. Request details: $orderRequestBuy")
|
||||
val response = apiService.postOrderBuyNow(orderRequestBuy)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Log.d("Order Repository", "Buy now order created successfully. Response: ${response.body()}")
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Buy now order creation failed. Code: ${response.code()}, Error: $errorBody")
|
||||
}
|
||||
|
||||
response
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception creating buy now order", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStore(): StoreResponse? {
|
||||
return try {
|
||||
val response = apiService.getStore()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val storeResponse = response.body()
|
||||
Log.d("Order Repository", "Store information fetched successfully. Store count: ${storeResponse?.store?.storeName}")
|
||||
storeResponse
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching store. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception getting store", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAddress(): AddressResponse? {
|
||||
return try {
|
||||
val response = apiService.getAddress()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val addressResponse = response.body()
|
||||
Log.d("Order Repository", "Address information fetched successfully. Address count: ${addressResponse?.addresses?.size}")
|
||||
addressResponse
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching addresses. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception getting addresses", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCountCourierCost(courierCost: CourierCostRequest): Result<CourierCostResponse> {
|
||||
return try {
|
||||
Log.d("Order Repository", "Calculating courier cost. Request: $courierCost")
|
||||
val response = apiService.countCourierCost(courierCost)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let { courierCostResponse ->
|
||||
Log.d("Order Repository", "Courier cost calculation successful. Courier costs: ${courierCostResponse.courierCosts.size}")
|
||||
Result.Success(courierCostResponse)
|
||||
} ?: run {
|
||||
Result.Error(Exception("Failed to get courier cost: Empty response"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error calculating courier cost. Code: ${response.code()}, Error: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception calculating courier cost", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCart(): Result<List<DataItem>> {
|
||||
return try {
|
||||
val response = apiService.getCart()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val cartData = response.body()?.data
|
||||
if (!cartData.isNullOrEmpty()) {
|
||||
Result.Success(cartData)
|
||||
} else {
|
||||
Log.e("Order Repository", "Cart data is empty")
|
||||
Result.Error(Exception("Cart is empty"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching cart: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception fetching cart", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchStoreDetail(storeId: Int): Result<StoreProduct?> {
|
||||
return try {
|
||||
val response = apiService.getDetailStore(storeId)
|
||||
if (response.isSuccessful) {
|
||||
val store = response.body()?.store
|
||||
if (store != null) {
|
||||
Result.Success(store)
|
||||
} else {
|
||||
Result.Error(Exception("Store details not found"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching store: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception fetching store details", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||
return try {
|
||||
val response = apiService.createAddress(createAddressRequest)
|
||||
if (response.isSuccessful){
|
||||
response.body()?.let {
|
||||
Result.Success(it)
|
||||
} ?: Result.Error(Exception("Add Address failed"))
|
||||
} else {
|
||||
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
|
||||
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getListProvinces(): ListProvinceResponse? {
|
||||
val response = apiService.getListProv()
|
||||
return if (response.isSuccessful) response.body() else null
|
||||
}
|
||||
|
||||
suspend fun getListCities(provId : Int): ListCityResponse?{
|
||||
val response = apiService.getCityProvId(provId)
|
||||
return if (response.isSuccessful) response.body() else null
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
package com.alya.ecommerce_serang.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -23,26 +26,37 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
if (response.isSuccessful) {
|
||||
// Return a Result.Success with the list of products
|
||||
|
||||
Result.Success(response.body()?.products ?: emptyList())
|
||||
val products = response.body()?.products ?: emptyList()
|
||||
Log.d(TAG, "Products fetched successfully. Total products: ${products.size}")
|
||||
|
||||
// Optional: Log some product details
|
||||
products.take(3).forEach { product ->
|
||||
Log.d(TAG, "Sample Product - ID: ${product.id}, Name: ${product.name}, Price: ${product.price}")
|
||||
}
|
||||
|
||||
Result.Success(products)
|
||||
} else {
|
||||
// Return a Result.Error with a custom Exception
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e(TAG, "Failed to fetch products. Code: ${response.code()}, Error: $errorBody")
|
||||
Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Return a Result.Error with the exception caught
|
||||
Log.e(TAG, "Exception while fetching products", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchProductDetail(productId: Int): ProductResponse? {
|
||||
return try {
|
||||
Log.d(TAG, "Fetching product detail for ID: $productId")
|
||||
val response = apiService.getDetailProduct(productId)
|
||||
if (response.isSuccessful) {
|
||||
response.body()
|
||||
val productResponse = response.body()
|
||||
Log.d(TAG, "Product detail fetched successfully. Product: ${productResponse?.product?.productName}")
|
||||
productResponse
|
||||
} else {
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e(TAG, "Error fetching product detail. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -58,14 +72,14 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val categories = response.body()?.category ?: emptyList()
|
||||
Log.d("Categories", "Fetched categories: $categories")
|
||||
Log.d("ProductRepository", "Fetched categories: $categories")
|
||||
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
|
||||
Result.Success(categories)
|
||||
} else {
|
||||
Result.Error(Exception("Failed to fetch categories. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Categories", "Error fetching categories", e)
|
||||
Log.e("ProductRepository", "Error fetching categories", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
@ -84,6 +98,42 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addToCart(request: CartItem): Result<AddCartResponse> {
|
||||
return try {
|
||||
val response = apiService.addCart(request)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Result.Success(it)
|
||||
} ?: Result.Error(Exception("Add Cart failed"))
|
||||
} else {
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown Error"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchStoreDetail(storeId: Int): Result<StoreProduct?> {
|
||||
return try {
|
||||
val response = apiService.getDetailStore(storeId)
|
||||
if (response.isSuccessful) {
|
||||
val store = response.body()?.store
|
||||
if (store != null) {
|
||||
Result.Success(store)
|
||||
} else {
|
||||
Result.Error(Throwable("Empty response body"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("ProductRepository", "Error: $errorMsg")
|
||||
Result.Error(Throwable(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchMyStoreProducts(): List<ProductsItem> {
|
||||
return try {
|
||||
val response = apiService.getStoreProduct()
|
||||
@ -152,6 +202,10 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ProductRepository"
|
||||
}
|
||||
}
|
||||
|
||||
// suspend fun fetchStoreDetail(storeId: Int): Store? {
|
||||
|
@ -4,18 +4,15 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.response.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
|
||||
class UserRepository(private val apiService: ApiService) {
|
||||
|
||||
//post data without message/response
|
||||
suspend fun requestOtpRep(email: String): OtpResponse {
|
||||
|
||||
// fun requestOtpRep(email: String): Result<String> {
|
||||
|
||||
return apiService.getOTP(OtpRequest(email))
|
||||
|
||||
}
|
||||
|
||||
suspend fun registerUser(request: RegisterRequest): String {
|
||||
|
@ -61,6 +61,7 @@ class HorizontalProductAdapter(
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
products = newProducts
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun updateLimitedProducts(newProducts: List<ProductsItem>) {
|
||||
|
@ -0,0 +1,79 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderSellerBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CartCheckoutAdapter(private val checkoutData: CheckoutData) :
|
||||
RecyclerView.Adapter<CartCheckoutAdapter.SellerViewHolder>() {
|
||||
|
||||
class SellerViewHolder(val binding: ItemOrderSellerBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
|
||||
val binding = ItemOrderSellerBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return SellerViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1 // Only one seller
|
||||
|
||||
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set seller name
|
||||
tvStoreName.text = checkoutData.sellerName
|
||||
|
||||
// Set up products RecyclerView with multiple items
|
||||
rvSellerOrderProduct.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = MultiCartItemsAdapter(checkoutData.cartItems)
|
||||
isNestedScrollingEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultiCartItemsAdapter(private val cartItems: List<CartItemsItem>) :
|
||||
RecyclerView.Adapter<MultiCartItemsAdapter.CartItemViewHolder>() {
|
||||
|
||||
class CartItemViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
|
||||
val binding = ItemOrderProductBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return CartItemViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = cartItems.size
|
||||
|
||||
override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
|
||||
val item = cartItems[position]
|
||||
|
||||
with(holder.binding) {
|
||||
// Set cart item details
|
||||
tvProductName.text = item.productName
|
||||
tvProductQuantity.text = "${item.quantity} buah"
|
||||
tvProductPrice.text = formatCurrency(item.price.toDouble())
|
||||
|
||||
// Load placeholder image
|
||||
Glide.with(ivProduct.context)
|
||||
.load(R.drawable.placeholder_image)
|
||||
.into(ivProduct)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
}
|
@ -0,0 +1,357 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.product.PaymentInfoItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding
|
||||
import com.alya.ecommerce_serang.ui.order.address.AddressActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CheckoutActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityCheckoutBinding
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private var paymentAdapter: PaymentMethodAdapter? = null
|
||||
|
||||
private val viewModel: CheckoutViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
CheckoutViewModel(orderRepository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityCheckoutBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
|
||||
// Setup UI components
|
||||
setupToolbar()
|
||||
setupObservers()
|
||||
setupClickListeners()
|
||||
processIntentData()
|
||||
}
|
||||
|
||||
private fun processIntentData() {
|
||||
// Determine if this is Buy Now or Cart checkout
|
||||
val isBuyNow = intent.hasExtra(EXTRA_PRODUCT_ID) && !intent.hasExtra(EXTRA_CART_ITEM_IDS)
|
||||
|
||||
if (isBuyNow) {
|
||||
// Process Buy Now flow
|
||||
viewModel.initializeBuyNow(
|
||||
storeId = intent.getIntExtra(EXTRA_STORE_ID, 0),
|
||||
storeName = intent.getStringExtra(EXTRA_STORE_NAME),
|
||||
productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0),
|
||||
productName = intent.getStringExtra(EXTRA_PRODUCT_NAME),
|
||||
productImage = intent.getStringExtra(EXTRA_PRODUCT_IMAGE),
|
||||
quantity = intent.getIntExtra(EXTRA_QUANTITY, 1),
|
||||
price = intent.getDoubleExtra(EXTRA_PRICE, 0.0)
|
||||
)
|
||||
} else {
|
||||
// Process Cart checkout flow
|
||||
val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList()
|
||||
if (cartItemIds.isNotEmpty()) {
|
||||
viewModel.initializeFromCart(cartItemIds)
|
||||
} else {
|
||||
Toast.makeText(this, "Error: No cart items specified", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
// Observe checkout data
|
||||
viewModel.checkoutData.observe(this) { data ->
|
||||
setupProductRecyclerView(data)
|
||||
updateOrderSummary()
|
||||
|
||||
// Load payment methods
|
||||
viewModel.getPaymentMethods { paymentMethods ->
|
||||
if (paymentMethods.isNotEmpty()) {
|
||||
setupPaymentMethodsRecyclerView(paymentMethods)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observe address details
|
||||
viewModel.addressDetails.observe(this) { address ->
|
||||
binding.tvPlacesAddress.text = address?.recipient
|
||||
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
|
||||
}
|
||||
|
||||
// Observe payment details
|
||||
viewModel.paymentDetails.observe(this) { payment ->
|
||||
if (payment != null) {
|
||||
// Update selected payment in adapter by name instead of ID
|
||||
paymentAdapter?.setSelectedPaymentName(payment.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Observe loading state
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
binding.btnPay.isEnabled = !isLoading
|
||||
// Show/hide loading indicator if you have one
|
||||
}
|
||||
|
||||
// Observe error messages
|
||||
viewModel.errorMessage.observe(this) { message ->
|
||||
if (message.isNotEmpty()) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
// Observe order creation
|
||||
viewModel.orderCreated.observe(this) { created ->
|
||||
if (created) {
|
||||
Toast.makeText(this, "Order successfully created!", Toast.LENGTH_SHORT).show()
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupProductRecyclerView(checkoutData: CheckoutData) {
|
||||
val adapter = if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
|
||||
CheckoutSellerAdapter(checkoutData)
|
||||
} else {
|
||||
CartCheckoutAdapter(checkoutData)
|
||||
}
|
||||
|
||||
binding.rvProductItems.apply {
|
||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||
this.adapter = adapter
|
||||
isNestedScrollingEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentInfoItem>) {
|
||||
paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment ->
|
||||
// When a payment method is selected
|
||||
// Since PaymentInfoItem doesn't have an id field, we'll use the name as identifier
|
||||
// You might need to convert the name to an ID if your backend expects an integer
|
||||
val paymentId = payment.name.toIntOrNull() ?: 0
|
||||
viewModel.setPaymentMethod(paymentId)
|
||||
}
|
||||
|
||||
binding.rvPaymentMethods.apply {
|
||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||
adapter = paymentAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOrderSummary() {
|
||||
viewModel.checkoutData.value?.let { data ->
|
||||
// Update price information
|
||||
binding.tvItemTotal.text = formatCurrency(viewModel.calculateSubtotal())
|
||||
|
||||
// Get shipping price
|
||||
val shipPrice = if (data.isBuyNow) {
|
||||
(data.orderRequest as OrderRequestBuy).shipPrice.toDouble()
|
||||
} else {
|
||||
(data.orderRequest as OrderRequest).shipPrice.toDouble()
|
||||
}
|
||||
binding.tvShippingFee.text = formatCurrency(shipPrice)
|
||||
|
||||
// Update total
|
||||
val total = viewModel.calculateTotal()
|
||||
binding.tvTotal.text = formatCurrency(total)
|
||||
binding.tvBottomTotal.text = formatCurrency(total)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) {
|
||||
if (shipName.isNotEmpty() && shipService.isNotEmpty()) {
|
||||
// Display shipping name and service in one line
|
||||
binding.tvCourierName.text = "$shipName $shipService"
|
||||
binding.tvDeliveryEstimate.text = "$shipEtd hari kerja"
|
||||
binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble())
|
||||
binding.rbJne.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
// Address selection
|
||||
binding.tvChangeAddress.setOnClickListener {
|
||||
val intent = Intent(this, AddressActivity::class.java)
|
||||
addressSelectionLauncher.launch(intent)
|
||||
}
|
||||
|
||||
// Shipping method selection
|
||||
binding.layoutShippingMethod.setOnClickListener {
|
||||
val addressId = viewModel.addressDetails.value?.id ?: 0
|
||||
if (addressId <= 0) {
|
||||
Toast.makeText(this, "Please select delivery address first", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
// Launch shipping selection with address and product info
|
||||
val intent = Intent(this, ShippingActivity::class.java)
|
||||
intent.putExtra(ShippingActivity.EXTRA_ADDRESS_ID, addressId)
|
||||
|
||||
// Add product info for courier cost calculation
|
||||
val currentData = viewModel.checkoutData.value
|
||||
if (currentData != null) {
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
intent.putExtra(ShippingActivity.EXTRA_PRODUCT_ID, buyRequest.productId)
|
||||
intent.putExtra(ShippingActivity.EXTRA_QUANTITY, buyRequest.quantity)
|
||||
} else {
|
||||
// For cart, we'll pass the first item's info
|
||||
val firstItem = currentData.cartItems.firstOrNull()
|
||||
if (firstItem != null) {
|
||||
intent.putExtra(ShippingActivity.EXTRA_PRODUCT_ID, firstItem.productId)
|
||||
intent.putExtra(ShippingActivity.EXTRA_QUANTITY, firstItem.quantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shippingSelectionLauncher.launch(intent)
|
||||
}
|
||||
|
||||
// Create order button
|
||||
binding.btnPay.setOnClickListener {
|
||||
if (validateOrder()) {
|
||||
viewModel.createOrder()
|
||||
}
|
||||
}
|
||||
|
||||
// Voucher section (if implemented)
|
||||
binding.layoutVoucher?.setOnClickListener {
|
||||
Toast.makeText(this, "Voucher feature not implemented", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private val addressSelectionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val addressId = result.data?.getIntExtra(AddressActivity.EXTRA_ADDRESS_ID, 0) ?: 0
|
||||
if (addressId > 0) {
|
||||
viewModel.setSelectedAddress(addressId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val shippingSelectionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val data = result.data ?: return@registerForActivityResult
|
||||
val shipName = data.getStringExtra(ShippingActivity.EXTRA_SHIP_NAME) ?: return@registerForActivityResult
|
||||
val shipService = data.getStringExtra(ShippingActivity.EXTRA_SHIP_SERVICE) ?: return@registerForActivityResult
|
||||
val shipPrice = data.getIntExtra(ShippingActivity.EXTRA_SHIP_PRICE, 0)
|
||||
val shipEtd = data.getStringExtra(ShippingActivity.EXTRA_SHIP_ETD) ?: ""
|
||||
|
||||
// Update shipping in ViewModel
|
||||
viewModel.setShippingMethod(shipName, shipService, shipPrice, shipEtd)
|
||||
|
||||
// Update UI - display shipping name and service in one line
|
||||
updateShippingUI(shipName, shipService, shipEtd, shipPrice)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
|
||||
private fun validateOrder(): Boolean {
|
||||
// Check if address is selected
|
||||
if (viewModel.addressDetails.value == null) {
|
||||
Toast.makeText(this, "Silakan pilih alamat pengiriman", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if shipping is selected
|
||||
val checkoutData = viewModel.checkoutData.value ?: return false
|
||||
val shipName = if (checkoutData.isBuyNow) {
|
||||
(checkoutData.orderRequest as OrderRequestBuy).shipName
|
||||
} else {
|
||||
(checkoutData.orderRequest as OrderRequest).shipName
|
||||
}
|
||||
|
||||
if (shipName.isEmpty()) {
|
||||
Toast.makeText(this, "Silakan pilih metode pengiriman", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if payment method is selected
|
||||
if (viewModel.paymentDetails.value == null) {
|
||||
Toast.makeText(this, "Silakan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Intent extras
|
||||
const val EXTRA_CART_ITEM_IDS = "extra_cart_item_ids"
|
||||
const val EXTRA_STORE_ID = "STORE_ID"
|
||||
const val EXTRA_STORE_NAME = "STORE_NAME"
|
||||
const val EXTRA_PRODUCT_ID = "PRODUCT_ID"
|
||||
const val EXTRA_PRODUCT_NAME = "PRODUCT_NAME"
|
||||
const val EXTRA_PRODUCT_IMAGE = "PRODUCT_IMAGE"
|
||||
const val EXTRA_QUANTITY = "QUANTITY"
|
||||
const val EXTRA_PRICE = "PRICE"
|
||||
|
||||
// Helper methods for starting activity
|
||||
|
||||
// For Buy Now
|
||||
fun startForBuyNow(
|
||||
context: Context,
|
||||
storeId: Int,
|
||||
storeName: String?,
|
||||
productId: Int,
|
||||
productName: String?,
|
||||
productImage: String?,
|
||||
quantity: Int,
|
||||
price: Double
|
||||
) {
|
||||
val intent = Intent(context, CheckoutActivity::class.java).apply {
|
||||
putExtra(EXTRA_STORE_ID, storeId)
|
||||
putExtra(EXTRA_STORE_NAME, storeName)
|
||||
putExtra(EXTRA_PRODUCT_ID, productId)
|
||||
putExtra(EXTRA_PRODUCT_NAME, productName)
|
||||
putExtra(EXTRA_PRODUCT_IMAGE, productImage)
|
||||
putExtra(EXTRA_QUANTITY, quantity)
|
||||
putExtra(EXTRA_PRICE, price)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
// For Cart checkout
|
||||
fun startForCart(
|
||||
context: Context,
|
||||
cartItemIds: List<Int>
|
||||
) {
|
||||
val intent = Intent(context, CheckoutActivity::class.java).apply {
|
||||
putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray())
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderSellerBinding
|
||||
|
||||
// Adapter for seller section that contains the product
|
||||
class CheckoutSellerAdapter(private val checkoutData: CheckoutData) :
|
||||
RecyclerView.Adapter<CheckoutSellerAdapter.SellerViewHolder>() {
|
||||
|
||||
class SellerViewHolder(val binding: ItemOrderSellerBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
|
||||
val binding = ItemOrderSellerBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return SellerViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1 // Only one seller
|
||||
|
||||
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set seller name
|
||||
tvStoreName.text = checkoutData.sellerName
|
||||
|
||||
// Set up products RecyclerView
|
||||
rvSellerOrderProduct.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = if (checkoutData.isBuyNow) {
|
||||
// Single product for Buy Now
|
||||
SingleProductAdapter(checkoutData)
|
||||
} else {
|
||||
// Single cart item
|
||||
SingleCartItemAdapter(checkoutData.cartItems.first())
|
||||
}
|
||||
isNestedScrollingEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,330 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.DataItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.PaymentInfoItem
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
|
||||
private val _checkoutData = MutableLiveData<CheckoutData>()
|
||||
val checkoutData: LiveData<CheckoutData> = _checkoutData
|
||||
|
||||
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
||||
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
||||
|
||||
private val _paymentDetails = MutableLiveData<PaymentInfoItem?>()
|
||||
val paymentDetails: LiveData<PaymentInfoItem?> = _paymentDetails
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage: LiveData<String> = _errorMessage
|
||||
|
||||
private val _orderCreated = MutableLiveData<Boolean>()
|
||||
val orderCreated: LiveData<Boolean> = _orderCreated
|
||||
|
||||
// Initialize "Buy Now" checkout
|
||||
fun initializeBuyNow(
|
||||
storeId: Int,
|
||||
storeName: String?,
|
||||
productId: Int,
|
||||
productName: String?,
|
||||
productImage: String?,
|
||||
quantity: Int,
|
||||
price: Double
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
// Create initial OrderRequestBuy object
|
||||
val orderRequest = OrderRequestBuy(
|
||||
addressId = 0, // Will be set when user selects address
|
||||
paymentMethodId = 0, // Will be set when user selects payment
|
||||
shipPrice = 0, // Will be set when user selects shipping
|
||||
shipName = "",
|
||||
shipService = "",
|
||||
isNego = false, // Default value
|
||||
productId = productId,
|
||||
quantity = quantity,
|
||||
shipEtd = ""
|
||||
)
|
||||
|
||||
// Create checkout data
|
||||
_checkoutData.value = CheckoutData(
|
||||
orderRequest = orderRequest,
|
||||
productName = productName,
|
||||
productImageUrl = productImage ?: "",
|
||||
productPrice = price,
|
||||
sellerName = storeName ?: "",
|
||||
sellerId = storeId,
|
||||
quantity = quantity,
|
||||
isBuyNow = true
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error initializing Buy Now data", e)
|
||||
_errorMessage.value = "Failed to initialize checkout: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize checkout from cart
|
||||
fun initializeFromCart(cartItemIds: List<Int>) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
// Get cart data
|
||||
val cartResult = repository.getCart()
|
||||
|
||||
if (cartResult is Result.Success) {
|
||||
// Find matching cart items
|
||||
val matchingItems = mutableListOf<CartItemsItem>()
|
||||
var storeData: DataItem? = null
|
||||
|
||||
for (store in cartResult.data) {
|
||||
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
|
||||
if (storeItems.isNotEmpty()) {
|
||||
matchingItems.addAll(storeItems)
|
||||
storeData = store
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingItems.isNotEmpty() && storeData != null) {
|
||||
// Create initial OrderRequest object
|
||||
val orderRequest = OrderRequest(
|
||||
addressId = 0, // Will be set when user selects address
|
||||
paymentMethodId = 0, // Will be set when user selects payment
|
||||
shipPrice = 0, // Will be set when user selects shipping
|
||||
shipName = "",
|
||||
shipService = "",
|
||||
isNego = false,
|
||||
cartItemId = cartItemIds,
|
||||
shipEtd = ""
|
||||
)
|
||||
|
||||
// Create checkout data
|
||||
_checkoutData.value = CheckoutData(
|
||||
orderRequest = orderRequest,
|
||||
productName = matchingItems.first().productName,
|
||||
sellerName = storeData.storeName,
|
||||
sellerId = storeData.storeId,
|
||||
isBuyNow = false,
|
||||
cartItems = matchingItems
|
||||
)
|
||||
} else {
|
||||
_errorMessage.value = "No matching cart items found"
|
||||
}
|
||||
} else if (cartResult is Result.Error) {
|
||||
_errorMessage.value = "Failed to fetch cart items: ${cartResult.exception.message}"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error initializing cart checkout", e)
|
||||
_errorMessage.value = "Error: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get payment methods from API
|
||||
fun getPaymentMethods(callback: (List<PaymentInfoItem>) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val storeId = _checkoutData.value?.sellerId ?: return@launch
|
||||
|
||||
// Use fetchStoreDetail instead of getStore
|
||||
val storeResult = repository.fetchStoreDetail(storeId)
|
||||
|
||||
if (storeResult is Result.Success && storeResult.data != null) {
|
||||
callback(storeResult.data.paymentInfo)
|
||||
} else {
|
||||
callback(emptyList())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error fetching payment methods", e)
|
||||
callback(emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set selected address
|
||||
fun setSelectedAddress(addressId: Int) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
try {
|
||||
// Get address details from API
|
||||
val addressResponse = repository.getAddress()
|
||||
if (addressResponse != null && !addressResponse.addresses.isNullOrEmpty()) {
|
||||
val address = addressResponse.addresses.find { it.id == addressId }
|
||||
if (addressResponse != null && !addressResponse.addresses.isNullOrEmpty()) {
|
||||
val address = addressResponse.addresses.find { it.id == addressId }
|
||||
// No need for null check since _addressDetails now accepts nullable values
|
||||
_addressDetails.value = address
|
||||
|
||||
// Update order request with address ID only if address isn't null
|
||||
if (address != null) {
|
||||
val currentData = _checkoutData.value ?: return@launch
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
val updatedRequest = buyRequest.copy(addressId = addressId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
} else {
|
||||
val cartRequest = currentData.orderRequest as OrderRequest
|
||||
val updatedRequest = cartRequest.copy(addressId = addressId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = "Error loading address: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set shipping method
|
||||
fun setShippingMethod(shipName: String, shipService: String, shipPrice: Int, shipEtd: String) {
|
||||
val currentData = _checkoutData.value ?: return
|
||||
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
val updatedRequest = buyRequest.copy(
|
||||
shipName = shipName,
|
||||
shipService = shipService,
|
||||
shipPrice = shipPrice,
|
||||
shipEtd = shipEtd
|
||||
)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
} else {
|
||||
val cartRequest = currentData.orderRequest as OrderRequest
|
||||
val updatedRequest = cartRequest.copy(
|
||||
shipName = shipName,
|
||||
shipService = shipService,
|
||||
shipPrice = shipPrice,
|
||||
shipEtd = shipEtd
|
||||
)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Set payment method
|
||||
fun setPaymentMethod(paymentId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val storeId = _checkoutData.value?.sellerId ?: return@launch
|
||||
|
||||
// Use fetchStoreDetail instead of getStore
|
||||
val storeResult = repository.fetchStoreDetail(storeId)
|
||||
if (storeResult is Result.Success && storeResult.data != null) {
|
||||
// Find the selected payment in the payment info list
|
||||
val payment = storeResult.data.paymentInfo.find { it.name == paymentId.toString() }
|
||||
_paymentDetails.value = payment
|
||||
|
||||
// Update order request if payment isn't null
|
||||
if (payment != null) {
|
||||
val currentData = _checkoutData.value ?: return@launch
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
val updatedRequest = buyRequest.copy(paymentMethodId = paymentId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
} else {
|
||||
val cartRequest = currentData.orderRequest as OrderRequest
|
||||
val updatedRequest = cartRequest.copy(paymentMethodId = paymentId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = "Error setting payment method: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create order
|
||||
fun createOrder() {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
val data = _checkoutData.value ?: throw Exception("No checkout data available")
|
||||
|
||||
val response = if (data.isBuyNow) {
|
||||
// For Buy Now, use the dedicated endpoint
|
||||
val buyRequest = data.orderRequest as OrderRequestBuy
|
||||
repository.createOrderBuyNow(buyRequest)
|
||||
} else {
|
||||
// For Cart checkout, use the standard order endpoint
|
||||
val cartRequest = data.orderRequest as OrderRequest
|
||||
repository.createOrder(cartRequest)
|
||||
}
|
||||
|
||||
if (response.isSuccessful) {
|
||||
_orderCreated.value = true
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
_errorMessage.value = "Failed to create order: $errorMsg"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = "Error creating order: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total price (subtotal + shipping)
|
||||
fun calculateTotal(): Double {
|
||||
val data = _checkoutData.value ?: return 0.0
|
||||
|
||||
return calculateSubtotal() + getShippingPrice()
|
||||
}
|
||||
|
||||
// Calculate subtotal (without shipping)
|
||||
fun calculateSubtotal(): Double {
|
||||
val data = _checkoutData.value ?: return 0.0
|
||||
|
||||
return if (data.isBuyNow) {
|
||||
// For Buy Now, use product price * quantity
|
||||
val buyRequest = data.orderRequest as OrderRequestBuy
|
||||
data.productPrice * buyRequest.quantity
|
||||
} else {
|
||||
// For Cart, sum all items
|
||||
data.cartItems.sumOf { it.price * it.quantity.toDouble() }
|
||||
}
|
||||
}
|
||||
|
||||
// Get shipping price
|
||||
private fun getShippingPrice(): Double {
|
||||
val data = _checkoutData.value ?: return 0.0
|
||||
|
||||
return if (data.isBuyNow) {
|
||||
(data.orderRequest as OrderRequestBuy).shipPrice.toDouble()
|
||||
} else {
|
||||
(data.orderRequest as OrderRequest).shipPrice.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CheckoutViewModel"
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.product.PaymentInfoItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemPaymentMethodBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
class PaymentMethodAdapter(
|
||||
private val paymentMethods: List<PaymentInfoItem>,
|
||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||
|
||||
// Track the selected position
|
||||
private var selectedPosition = -1
|
||||
|
||||
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentMethodViewHolder {
|
||||
val binding = ItemPaymentMethodBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return PaymentMethodViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = paymentMethods.size
|
||||
|
||||
override fun onBindViewHolder(holder: PaymentMethodViewHolder, position: Int) {
|
||||
val payment = paymentMethods[position]
|
||||
|
||||
with(holder.binding) {
|
||||
// Set payment method name
|
||||
tvPaymentMethodName.text = payment.name
|
||||
|
||||
// Set radio button state
|
||||
rbPaymentMethod.isChecked = selectedPosition == position
|
||||
|
||||
// Load payment icon if available
|
||||
if (payment.qrisImage.isNotEmpty()) {
|
||||
Glide.with(ivPaymentMethod.context)
|
||||
.load(payment.qrisImage)
|
||||
.apply(RequestOptions()
|
||||
.placeholder(R.drawable.outline_store_24)
|
||||
.error(R.drawable.outline_store_24))
|
||||
.into(ivPaymentMethod)
|
||||
} else {
|
||||
// Default icon for bank transfers
|
||||
ivPaymentMethod.setImageResource(R.drawable.outline_store_24)
|
||||
}
|
||||
|
||||
// Handle click on the entire item
|
||||
root.setOnClickListener {
|
||||
selectPayment(position)
|
||||
onPaymentSelected(payment)
|
||||
}
|
||||
|
||||
// Handle click on the radio button
|
||||
rbPaymentMethod.setOnClickListener {
|
||||
selectPayment(position)
|
||||
onPaymentSelected(payment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to handle payment selection
|
||||
private fun selectPayment(position: Int) {
|
||||
if (selectedPosition != position) {
|
||||
val previousPosition = selectedPosition
|
||||
selectedPosition = position
|
||||
|
||||
// Update UI for previous and new selection
|
||||
notifyItemChanged(previousPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
//selected by name
|
||||
fun setSelectedPaymentName(paymentName: String) {
|
||||
val position = paymentMethods.indexOfFirst { it.name == paymentName }
|
||||
if (position != -1 && position != selectedPosition) {
|
||||
selectPayment(position)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityShippingBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
|
||||
class ShippingActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityShippingBinding
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var shippingAdapter: ShippingAdapter
|
||||
|
||||
private val viewModel: ShippingViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val repository = OrderRepository(apiService)
|
||||
ShippingViewModel(repository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityShippingBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Initialize SessionManager
|
||||
sessionManager = SessionManager(this)
|
||||
|
||||
// Get data from intent
|
||||
val addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, 0)
|
||||
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
||||
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
||||
|
||||
// Validate required information
|
||||
if (addressId <= 0 || productId <= 0) {
|
||||
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
// Setup UI components
|
||||
setupToolbar()
|
||||
setupRecyclerView()
|
||||
setupObservers()
|
||||
|
||||
// Load shipping options
|
||||
viewModel.loadShippingOptions(addressId, productId, quantity)
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
||||
// Handle shipping method selection
|
||||
returnSelectedShipping(
|
||||
courierCostsItem.courier,
|
||||
service.service,
|
||||
service.cost,
|
||||
service.etd
|
||||
)
|
||||
}
|
||||
|
||||
binding.rvShipmentOrder.apply {
|
||||
layoutManager = LinearLayoutManager(this@ShippingActivity)
|
||||
adapter = shippingAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
// Observe shipping options
|
||||
viewModel.shippingOptions.observe(this) { courierOptions ->
|
||||
shippingAdapter.submitList(courierOptions)
|
||||
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
||||
}
|
||||
|
||||
// Observe loading state
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
// binding.progressBar.isVisible = isLoading
|
||||
}
|
||||
|
||||
// Observe error messages
|
||||
viewModel.errorMessage.observe(this) { message ->
|
||||
if (message.isNotEmpty()) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEmptyState(isEmpty: Boolean) {
|
||||
// binding.layoutEmptyShipping.isVisible = isEmpty
|
||||
binding.rvShipmentOrder.isVisible = !isEmpty
|
||||
}
|
||||
|
||||
private fun returnSelectedShipping(
|
||||
shipName: String,
|
||||
shipService: String,
|
||||
shipPrice: Int,
|
||||
shipEtd: String
|
||||
) {
|
||||
val intent = Intent().apply {
|
||||
putExtra(EXTRA_SHIP_NAME, shipName)
|
||||
putExtra(EXTRA_SHIP_SERVICE, shipService)
|
||||
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
||||
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
||||
}
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Constants for intent extras
|
||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||
const val EXTRA_QUANTITY = "extra_quantity"
|
||||
const val EXTRA_SHIP_NAME = "extra_ship_name"
|
||||
const val EXTRA_SHIP_SERVICE = "extra_ship_service"
|
||||
const val EXTRA_SHIP_PRICE = "extra_ship_price"
|
||||
const val EXTRA_SHIP_ETD = "extra_ship_etd"
|
||||
}
|
||||
}
|
||||
|
||||
//val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
// if (result.resultCode == RESULT_OK) {
|
||||
// val data = result.data
|
||||
// val shipName = data?.getStringExtra("ship_name")
|
||||
// val shipPrice = data?.getIntExtra("ship_price", 0)
|
||||
// val shipService = data?.getStringExtra("ship_service")
|
||||
// // use the data as needed
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// launch the shipping activity
|
||||
//val intent = Intent(this, ShippingActivity::class.java).apply {
|
||||
// putExtra("address_id", addressId)
|
||||
// putExtra("product_id", productId)
|
||||
// putExtra("quantity", quantity)
|
||||
//}
|
||||
//launcher.launch(intent)
|
@ -0,0 +1,96 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ServicesItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemShippingOrderBinding
|
||||
|
||||
class ShippingAdapter(
|
||||
private val onItemSelected: (CourierCostsItem, ServicesItem) -> Unit
|
||||
) : RecyclerView.Adapter<ShippingAdapter.ShippingViewHolder>() {
|
||||
|
||||
private val courierCostsList = mutableListOf<CourierCostsItem>()
|
||||
private var selectedPosition = RecyclerView.NO_POSITION
|
||||
private var selectedCourierPosition = RecyclerView.NO_POSITION
|
||||
|
||||
fun submitList(courierCostsList: List<CourierCostsItem>) {
|
||||
this.courierCostsList.clear()
|
||||
this.courierCostsList.addAll(courierCostsList)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ShippingViewHolder(
|
||||
private val binding: ItemShippingOrderBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(courierCostsItem: CourierCostsItem, service: ServicesItem, isSelected: Boolean) {
|
||||
binding.apply {
|
||||
// Combine courier name and service
|
||||
courierNameCost.text = "${courierCostsItem.courier} - ${service.service}"
|
||||
estDate.text = "Estimasi ${service.etd} hari"
|
||||
costPrice.text = "Rp${service.cost}"
|
||||
|
||||
// Single click handler for both item and radio button
|
||||
val onClickAction = {
|
||||
val newPosition = adapterPosition
|
||||
if (newPosition != RecyclerView.NO_POSITION) {
|
||||
// Update selected position
|
||||
val oldPosition = selectedPosition
|
||||
selectedPosition = newPosition
|
||||
selectedCourierPosition = getParentCourierPosition(courierCostsItem)
|
||||
|
||||
// Notify only the changed items to improve performance
|
||||
notifyItemChanged(oldPosition)
|
||||
notifyItemChanged(newPosition)
|
||||
|
||||
// Call the callback with both courier and service
|
||||
onItemSelected(courierCostsItem, service)
|
||||
}
|
||||
}
|
||||
|
||||
root.setOnClickListener { onClickAction() }
|
||||
radioBtnCost.apply {
|
||||
isChecked = isSelected
|
||||
setOnClickListener { onClickAction() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getParentCourierPosition(courierCostsItem: CourierCostsItem): Int {
|
||||
return courierCostsList.indexOfFirst { it == courierCostsItem }
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShippingViewHolder {
|
||||
val binding = ItemShippingOrderBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ShippingViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ShippingViewHolder, position: Int) {
|
||||
// Flatten the nested structure for binding
|
||||
var currentPosition = 0
|
||||
for (courierCostsItem in courierCostsList) {
|
||||
for (service in courierCostsItem.services) {
|
||||
if (currentPosition == position) {
|
||||
holder.bind(
|
||||
courierCostsItem,
|
||||
service,
|
||||
currentPosition == selectedPosition
|
||||
)
|
||||
return
|
||||
}
|
||||
currentPosition++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return courierCostsList.sumOf { it.services.size }
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CostProduct
|
||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostsItem
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ShippingViewModel(
|
||||
private val repository: OrderRepository
|
||||
) : ViewModel() {
|
||||
|
||||
// Shipping options LiveData
|
||||
private val _shippingOptions = MutableLiveData<List<CourierCostsItem>>()
|
||||
val shippingOptions: LiveData<List<CourierCostsItem>> = _shippingOptions
|
||||
|
||||
// Loading state LiveData
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
// Error message LiveData
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage: LiveData<String> = _errorMessage
|
||||
|
||||
/**
|
||||
* Load shipping options based on address, product, and quantity
|
||||
*/
|
||||
fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
|
||||
// Reset previous state
|
||||
_isLoading.value = true
|
||||
_errorMessage.value = ""
|
||||
|
||||
// Prepare the request
|
||||
val request = CourierCostRequest(
|
||||
addressId = addressId,
|
||||
itemCost = CostProduct(
|
||||
productId = productId,
|
||||
quantity = quantity
|
||||
)
|
||||
)
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// Fetch courier costs
|
||||
val result = repository.getCountCourierCost(request)
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
// Update shipping options directly with courier costs
|
||||
_shippingOptions.value = result.data.courierCosts
|
||||
}
|
||||
is Result.Error -> {
|
||||
// Handle error case
|
||||
_errorMessage.value = result.exception.message ?: "Unknown error occurred"
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Typically handled by the loading state
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Catch any unexpected exceptions
|
||||
_errorMessage.value = e.localizedMessage ?: "An unexpected error occurred"
|
||||
} finally {
|
||||
// Always set loading to false
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class SingleCartItemAdapter(private val cartItem: CartItemsItem) :
|
||||
RecyclerView.Adapter<SingleCartItemAdapter.CartItemViewHolder>() {
|
||||
|
||||
class CartItemViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
|
||||
val binding = ItemOrderProductBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return CartItemViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
|
||||
override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set cart item details
|
||||
tvProductName.text = cartItem.productName
|
||||
tvProductQuantity.text = "${cartItem.quantity} buah"
|
||||
tvProductPrice.text = formatCurrency(cartItem.price.toDouble())
|
||||
|
||||
// Load placeholder image
|
||||
Glide.with(ivProduct.context)
|
||||
.load(R.drawable.placeholder_image)
|
||||
.into(ivProduct)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class SingleProductAdapter(private val checkoutData: CheckoutData) :
|
||||
RecyclerView.Adapter<SingleProductAdapter.ProductViewHolder>() {
|
||||
|
||||
class ProductViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
|
||||
val binding = ItemOrderProductBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return ProductViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
|
||||
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set product details
|
||||
tvProductName.text = checkoutData.productName
|
||||
|
||||
val quantity = (checkoutData.orderRequest as OrderRequestBuy).quantity
|
||||
tvProductQuantity.text = "$quantity buah"
|
||||
|
||||
tvProductPrice.text = formatCurrency(checkoutData.productPrice)
|
||||
|
||||
// Load product image
|
||||
Glide.with(ivProduct.context)
|
||||
.load(checkoutData.productImageUrl)
|
||||
.apply(
|
||||
RequestOptions()
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.error(R.drawable.placeholder_image))
|
||||
.into(ivProduct)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
|
||||
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddAddressActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityAddAddressBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var profileUser: UserProfile
|
||||
private lateinit var locationManager: LocationManager
|
||||
|
||||
private var latitude: Double? = null
|
||||
private var longitude: Double? = null
|
||||
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
||||
private val cityAdapter by lazy { CityAdapter(this) }
|
||||
|
||||
private val viewModel: AddAddressViewModel by viewModels {
|
||||
SavedStateViewModelFactory(this) { savedStateHandle ->
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
AddAddressViewModel(orderRepository, savedStateHandle)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAddAddressBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
|
||||
setupToolbar()
|
||||
setupAutoComplete()
|
||||
setupButtonListeners()
|
||||
collectFlows()
|
||||
requestLocationPermission()
|
||||
|
||||
|
||||
}
|
||||
|
||||
// private fun viewModelAddAddress(request: CreateAddressRequest) {
|
||||
// // Call the private fun in your ViewModel using reflection or expose it in ViewModel
|
||||
// val method = AddAddressViewModel::class.java.getDeclaredMethod("addAddress", CreateAddressRequest::class.java)
|
||||
// method.isAccessible = true
|
||||
// method.invoke(viewModel, request)
|
||||
// }
|
||||
// UI setup methods
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAutoComplete() {
|
||||
// Set adapters
|
||||
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
||||
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
||||
|
||||
// Set listeners
|
||||
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
||||
provinceAdapter.getProvinceId(position)?.let { provinceId ->
|
||||
viewModel.getCities(provinceId)
|
||||
binding.autoCompleteKabupaten.text.clear()
|
||||
}
|
||||
}
|
||||
|
||||
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
|
||||
cityAdapter.getCityId(position)?.let { cityId ->
|
||||
viewModel.selectedCityId = cityId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupButtonListeners() {
|
||||
binding.buttonSimpan.setOnClickListener {
|
||||
validateAndSubmitForm()
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectFlows() {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.provincesState.collect { state ->
|
||||
handleProvinceState(state)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.citiesState.collect { state ->
|
||||
handleCityState(state)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.addressSubmissionState.collect { state ->
|
||||
handleAddressSubmissionState(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
|
||||
when (state) {
|
||||
is ViewState.Loading -> null //showProvinceLoading(true)
|
||||
is ViewState.Success -> {
|
||||
provinceAdapter.updateData(state.data)
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
showError(state.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCityState(state: ViewState<List<CitiesItem>>) {
|
||||
when (state) {
|
||||
is ViewState.Loading -> null //showCityLoading(true)
|
||||
is ViewState.Success -> {
|
||||
// showCityLoading(false)
|
||||
cityAdapter.updateData(state.data)
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
// showCityLoading(false)
|
||||
showError(state.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
||||
when (state) {
|
||||
is ViewState.Loading -> showSubmitLoading(true)
|
||||
is ViewState.Success -> {
|
||||
showSubmitLoading(false)
|
||||
showSuccessAndFinish(state.data)
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
showSubmitLoading(false)
|
||||
showError(state.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun showSubmitLoading(isLoading: Boolean) {
|
||||
binding.buttonSimpan.isEnabled = !isLoading
|
||||
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
|
||||
// You might want to show a progress bar as well
|
||||
}
|
||||
|
||||
private fun showError(message: String) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun showSuccessAndFinish(message: String) {
|
||||
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
private fun validateAndSubmitForm() {
|
||||
val lat = latitude
|
||||
val long = longitude
|
||||
|
||||
if (lat == null || long == null) {
|
||||
showError("Lokasi belum terdeteksi")
|
||||
return
|
||||
}
|
||||
|
||||
val street = binding.etDetailAlamat.text.toString()
|
||||
val subDistrict = binding.etKecamatan.text.toString()
|
||||
val postalCode = binding.etKodePos.text.toString()
|
||||
val recipient = binding.etNamaPenerima.text.toString()
|
||||
val phone = binding.etNomorHp.text.toString()
|
||||
val userId = profileUser.userId
|
||||
val isStoreLocation = false
|
||||
|
||||
val provinceId = viewModel.selectedProvinceId
|
||||
val cityId = viewModel.selectedCityId
|
||||
|
||||
if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
|
||||
showError("Lengkapi semua field wajib")
|
||||
return
|
||||
}
|
||||
|
||||
if (provinceId == null) {
|
||||
showError("Pilih provinsi terlebih dahulu")
|
||||
return
|
||||
}
|
||||
|
||||
if (cityId == null) {
|
||||
showError("Pilih kota/kabupaten terlebih dahulu")
|
||||
return
|
||||
}
|
||||
|
||||
val request = CreateAddressRequest(
|
||||
lat = lat,
|
||||
long = long,
|
||||
street = street,
|
||||
subDistrict = subDistrict,
|
||||
cityId = cityId,
|
||||
provId = provinceId,
|
||||
postCode = postalCode,
|
||||
detailAddress = street,
|
||||
userId = userId,
|
||||
recipient = recipient,
|
||||
phone = phone,
|
||||
isStoreLocation = isStoreLocation
|
||||
)
|
||||
|
||||
viewModel.addAddress(request)
|
||||
}
|
||||
|
||||
private val locationPermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||
if (granted) requestLocation() else Toast.makeText(this, "Izin lokasi ditolak",Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun requestLocationPermission() {
|
||||
locationPermissionLauncher.launch(android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun requestLocation() {
|
||||
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
|
||||
if (!isGpsEnabled && !isNetworkEnabled) {
|
||||
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val provider = if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
|
||||
|
||||
locationManager.requestSingleUpdate(provider, object : LocationListener {
|
||||
override fun onLocationChanged(location: Location) {
|
||||
latitude = location.latitude
|
||||
longitude = location.longitude
|
||||
}
|
||||
|
||||
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
||||
override fun onProviderEnabled(provider: String) {}
|
||||
override fun onProviderDisabled(provider: String) {
|
||||
Toast.makeText(this@AddAddressActivity, "Provider dimatikan", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}, null)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
requestLocation()
|
||||
} else {
|
||||
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
||||
// Flow states for data
|
||||
private val _addressSubmissionState = MutableStateFlow<ViewState<String>>(ViewState.Loading)
|
||||
val addressSubmissionState = _addressSubmissionState.asStateFlow()
|
||||
|
||||
private val _provincesState = MutableStateFlow<ViewState<List<ProvincesItem>>>(ViewState.Loading)
|
||||
val provincesState = _provincesState.asStateFlow()
|
||||
|
||||
private val _citiesState = MutableStateFlow<ViewState<List<CitiesItem>>>(ViewState.Loading)
|
||||
val citiesState = _citiesState.asStateFlow()
|
||||
|
||||
// Stored in SavedStateHandle for configuration changes
|
||||
var selectedProvinceId: Int?
|
||||
get() = savedStateHandle.get<Int>("selectedProvinceId")
|
||||
set(value) { savedStateHandle["selectedProvinceId"] = value }
|
||||
|
||||
var selectedCityId: Int?
|
||||
get() = savedStateHandle.get<Int>("selectedCityId")
|
||||
set(value) { savedStateHandle["selectedCityId"] = value }
|
||||
|
||||
init {
|
||||
// Load provinces on initialization
|
||||
getProvinces()
|
||||
}
|
||||
|
||||
fun addAddress(request: CreateAddressRequest){
|
||||
viewModelScope.launch {
|
||||
when (val result = repository.addAddress(request)) {
|
||||
is Result.Success -> {
|
||||
val message = result.data.message // Ambil `message` dari CreateAddressResponse
|
||||
_addressSubmissionState.value = ViewState.Success(message)
|
||||
}
|
||||
is Result.Error -> {
|
||||
_addressSubmissionState.value =
|
||||
ViewState.Error(result.exception.message ?: "Unknown error")
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Optional, karena sudah set Loading di awal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getProvinces(){
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = repository.getListProvinces()
|
||||
result?.let {
|
||||
_provincesState.value = ViewState.Success(it.provinces)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCities(provinceId: Int){
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
selectedProvinceId = provinceId
|
||||
val result = repository.getListCities(provinceId)
|
||||
result?.let {
|
||||
_citiesState.value = ViewState.Success(it.cities)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedProvinceId(id: Int) {
|
||||
selectedProvinceId = id
|
||||
}
|
||||
|
||||
fun setSelectedCityId(id: Int) {
|
||||
selectedCityId = id
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AddAddressViewModel"
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ViewState<out T> {
|
||||
object Loading : ViewState<Nothing>()
|
||||
data class Success<T>(val data: T) : ViewState<T>()
|
||||
data class Error(val message: String) : ViewState<Nothing>()
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityAddressBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
|
||||
class AddressActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityAddressBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var adapter: AddressAdapter
|
||||
|
||||
private val viewModel: AddressViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
AddressViewModel(orderRepository)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAddressBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
setupToolbar()
|
||||
|
||||
adapter = AddressAdapter { selectedId ->
|
||||
viewModel.selectAddress(selectedId)
|
||||
}
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressedWithResult()
|
||||
}
|
||||
|
||||
binding.rvSellerOrder.layoutManager = LinearLayoutManager(this)
|
||||
binding.rvSellerOrder.adapter = adapter
|
||||
|
||||
viewModel.fetchAddresses()
|
||||
|
||||
viewModel.addresses.observe(this) { addressList ->
|
||||
adapter.submitList(addressList)
|
||||
}
|
||||
|
||||
viewModel.selectedAddressId.observe(this) { selectedId ->
|
||||
adapter.setSelectedAddressId(selectedId)
|
||||
}
|
||||
}
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
// private fun updateEmptyState(isEmpty: Boolean) {
|
||||
// binding.layoutEmptyAddresses.isVisible = isEmpty
|
||||
// binding.rvAddresses.isVisible = !isEmpty
|
||||
// }
|
||||
|
||||
private fun onBackPressedWithResult() {
|
||||
viewModel.selectedAddressId.value?.let {
|
||||
val intent = Intent()
|
||||
intent.putExtra(EXTRA_ADDRESS_ID, it)
|
||||
setResult(RESULT_OK, intent)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||
}
|
||||
}
|
||||
|
||||
//override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
// super.onActivityResult(requestCode, resultCode, data)
|
||||
// if (requestCode == REQUEST_ADDRESS && resultCode == RESULT_OK) {
|
||||
// val selectedAddressId = data?.getIntExtra("selected_address_id", -1)
|
||||
// // Use the selected address ID
|
||||
// }
|
||||
//}
|
@ -0,0 +1,67 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class AddressAdapter(
|
||||
private val onAddressClick: (Int) -> Unit
|
||||
) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) {
|
||||
|
||||
private var selectedAddressId: Int? = null
|
||||
|
||||
fun setSelectedAddressId(id: Int?) {
|
||||
selectedAddressId = id
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddressViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_card_address, parent, false)
|
||||
return AddressViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AddressViewHolder, position: Int) {
|
||||
val address = getItem(position)
|
||||
holder.bind(address, selectedAddressId == address.id)
|
||||
holder.itemView.setOnClickListener {
|
||||
onAddressClick(address.id)
|
||||
}
|
||||
}
|
||||
|
||||
class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val tvName: TextView = itemView.findViewById(R.id.tv_name_address)
|
||||
private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address)
|
||||
private val card: MaterialCardView = itemView as MaterialCardView
|
||||
|
||||
fun bind(address: AddressesItem, isSelected: Boolean) {
|
||||
tvName.text = address.recipient
|
||||
tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}"
|
||||
|
||||
card.setCardBackgroundColor(
|
||||
ContextCompat.getColor(
|
||||
itemView.context,
|
||||
if (isSelected) R.color.blue_50 else R.color.white
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<AddressesItem>() {
|
||||
override fun areItemsTheSame(oldItem: AddressesItem, newItem: AddressesItem) =
|
||||
oldItem.id == newItem.id
|
||||
|
||||
override fun areContentsTheSame(oldItem: AddressesItem, newItem: AddressesItem) =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddressViewModel(private val repository: OrderRepository): ViewModel() {
|
||||
|
||||
private val _addresses = MutableLiveData<List<AddressesItem>>()
|
||||
val addresses: LiveData<List<AddressesItem>> get() = _addresses
|
||||
|
||||
private val _selectedAddressId = MutableLiveData<Int?>()
|
||||
val selectedAddressId: LiveData<Int?> get() = _selectedAddressId
|
||||
|
||||
fun fetchAddresses() {
|
||||
viewModelScope.launch {
|
||||
val response = repository.getAddress()
|
||||
response?.let {
|
||||
_addresses.value = it.addresses
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectAddress(id: Int) {
|
||||
_selectedAddressId.value = id
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.alya.ecommerce_serang.R
|
||||
|
||||
class EditAddressActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.activity_edit_address)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.ArrayAdapter
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
||||
|
||||
// UI adapters and helpers
|
||||
class ProvinceAdapter(
|
||||
context: Context,
|
||||
resource: Int = android.R.layout.simple_dropdown_item_1line
|
||||
) : ArrayAdapter<String>(context, resource, ArrayList()) {
|
||||
|
||||
private val provinces = ArrayList<ProvincesItem>()
|
||||
|
||||
fun updateData(newProvinces: List<ProvincesItem>) {
|
||||
provinces.clear()
|
||||
provinces.addAll(newProvinces)
|
||||
|
||||
clear()
|
||||
addAll(provinces.map { it.province })
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getProvinceId(position: Int): Int? {
|
||||
return provinces.getOrNull(position)?.provinceId?.toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
class CityAdapter(
|
||||
context: Context,
|
||||
resource: Int = android.R.layout.simple_dropdown_item_1line
|
||||
) : ArrayAdapter<String>(context, resource, ArrayList()) {
|
||||
|
||||
private val cities = ArrayList<CitiesItem>()
|
||||
|
||||
fun updateData(newCities: List<CitiesItem>) {
|
||||
cities.clear()
|
||||
cities.addAll(newCities)
|
||||
|
||||
clear()
|
||||
addAll(cities.map { it.cityName })
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getCityId(position: Int): Int? {
|
||||
return cities.getOrNull(position)?.cityId?.toIntOrNull()
|
||||
}
|
||||
}
|
@ -1,25 +1,37 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
||||
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
||||
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class DetailProductActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityDetailProductBinding
|
||||
@ -27,12 +39,14 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private var productAdapter: HorizontalProductAdapter? = null
|
||||
private var reviewsAdapter: ReviewsAdapter? = null
|
||||
private var currentQuantity = 1
|
||||
|
||||
private val viewModel: ProductViewModel by viewModels {
|
||||
|
||||
private val viewModel: ProductUserViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val productRepository = ProductRepository(apiService)
|
||||
ProductViewModel(productRepository)
|
||||
ProductUserViewModel(productRepository)
|
||||
}
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -43,56 +57,148 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
setupUI()
|
||||
setupObservers()
|
||||
loadData()
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
val productId = intent.getIntExtra("PRODUCT_ID", -1)
|
||||
//nanti tambah get store id dari HomeFragment Product.storeId
|
||||
if (productId == -1) {
|
||||
Log.e("DetailProductActivity", "Invalid Product ID")
|
||||
Toast.makeText(this, "Invalid product ID", Toast.LENGTH_SHORT).show()
|
||||
finish() // Close activity if no valid ID
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.loadProductDetail(productId)
|
||||
viewModel.loadReviews(productId)
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.productDetail.observe(this) { product ->
|
||||
if (product != null) {
|
||||
Log.d("ProductDetail", "Name: ${product.productName}, Price: ${product.price}")
|
||||
// Update UI here, e.g., show in a TextView or ImageView
|
||||
viewModel.loadProductDetail(productId)
|
||||
|
||||
} else {
|
||||
Log.e("ProductDetail", "Failed to fetch product details")
|
||||
product?.let {
|
||||
updateUI(it)
|
||||
viewModel.loadOtherProducts(it.storeId)
|
||||
}
|
||||
}
|
||||
observeProductDetail()
|
||||
observeProductReviews()
|
||||
}
|
||||
private fun observeProductDetail() {
|
||||
viewModel.productDetail.observe(this) { product ->
|
||||
product?.let { updateUI(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeProductReviews() {
|
||||
viewModel.storeDetail.observe(this) { result ->
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
updateStoreInfo(result.data)
|
||||
}
|
||||
is Result.Error -> {
|
||||
// Show error message, maybe a Toast or Snackbar
|
||||
Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Show loading indicator if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.otherProducts.observe(this) { products ->
|
||||
updateOtherProducts(products)
|
||||
}
|
||||
|
||||
viewModel.reviewProduct.observe(this) { reviews ->
|
||||
setupRecyclerViewReviewsProduct(reviews)
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
binding.progressBarDetailProd.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
viewModel.error.observe(this) { errorMessage ->
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
viewModel.addCart.observe(this) { result ->
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val cartId = result.data.data.cartId
|
||||
Toast.makeText(this, result.data.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is Result.Error -> {
|
||||
Toast.makeText(this, "Failed to add to cart: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Show loading indicator if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStoreInfo(store: StoreProduct?) {
|
||||
store?.let {
|
||||
binding.tvSellerName.text = it.storeName
|
||||
binding.tvSellerRating.text = it.storeRating
|
||||
binding.tvSellerLocation.text = it.storeLocation
|
||||
|
||||
// Load store image using Glide
|
||||
val fullImageUrl = when (val img = it.storeImage) {
|
||||
is String -> {
|
||||
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||
}
|
||||
else -> R.drawable.placeholder_image
|
||||
}
|
||||
|
||||
Glide.with(this)
|
||||
.load(fullImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(binding.ivSellerImage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOtherProducts(products: List<ProductsItem>) {
|
||||
if (products.isEmpty()) {
|
||||
binding.recyclerViewOtherProducts.visibility = View.GONE
|
||||
binding.tvViewAllProducts.visibility = View.GONE
|
||||
} else {
|
||||
binding.recyclerViewOtherProducts.visibility = View.VISIBLE
|
||||
binding.tvViewAllProducts.visibility = View.VISIBLE
|
||||
productAdapter?.updateProducts(products)
|
||||
} }
|
||||
|
||||
private fun setupUI() {
|
||||
// binding.btnBack.setOnClickListener {
|
||||
// finish()
|
||||
// }
|
||||
|
||||
binding.tvViewAllReviews.setOnClickListener {
|
||||
viewModel.productDetail.value?.productId?.let { productId ->
|
||||
handleAllReviewsClick(productId)
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnBuyNow.setOnClickListener {
|
||||
viewModel.productDetail.value?.productId?.let { id ->
|
||||
showBuyNowPopup(id)
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnAddToCart.setOnClickListener {
|
||||
viewModel.productDetail.value?.productId?.let { id ->
|
||||
showAddToCartPopup(id)
|
||||
}
|
||||
}
|
||||
|
||||
setupRecyclerViewOtherProducts()
|
||||
}
|
||||
|
||||
private fun updateUI(product: Product){
|
||||
binding.tvProductName.text = product.productName
|
||||
binding.tvPrice.text = product.price
|
||||
binding.tvPrice.text = formatCurrency(product.price.toDouble())
|
||||
binding.tvSold.text = product.totalSold.toString()
|
||||
binding.tvRating.text = product.rating
|
||||
binding.tvWeight.text = product.weight.toString()
|
||||
binding.tvStock.text = product.stock.toString()
|
||||
binding.tvCategory.text = product.productCategory
|
||||
binding.tvDescription.text = product.description
|
||||
binding.tvSellerName.text = product.storeId.toString()
|
||||
|
||||
binding.tvViewAllReviews.setOnClickListener{
|
||||
handleAllReviewsClick(product.productId)
|
||||
}
|
||||
|
||||
|
||||
val fullImageUrl = when (val img = product.image) {
|
||||
is String -> {
|
||||
@ -106,8 +212,6 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
.load(fullImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(binding.ivProductImage)
|
||||
|
||||
setupRecyclerViewOtherProducts()
|
||||
}
|
||||
|
||||
private fun handleAllReviewsClick(productId: Int) {
|
||||
@ -119,7 +223,7 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
private fun setupRecyclerViewOtherProducts(){
|
||||
productAdapter = HorizontalProductAdapter(
|
||||
products = emptyList(),
|
||||
onClick = { productsItem -> handleProductClick(productsItem) }
|
||||
onClick = { productsItem -> handleProductClick(productsItem) }
|
||||
)
|
||||
|
||||
binding.recyclerViewOtherProducts.apply {
|
||||
@ -134,7 +238,15 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
|
||||
private fun setupRecyclerViewReviewsProduct(reviewList: List<ReviewsItem>){
|
||||
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
|
||||
|
||||
if (reviewList.isEmpty()) {
|
||||
binding.recyclerViewReviews.visibility = View.GONE
|
||||
binding.tvViewAllReviews.visibility = View.GONE
|
||||
// binding.tvNoReviews.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.recyclerViewReviews.visibility = View.VISIBLE
|
||||
binding.tvViewAllReviews.visibility = View.VISIBLE
|
||||
}
|
||||
// binding.tvNoReviews.visibility = View.GONE
|
||||
reviewsAdapter = ReviewsAdapter(
|
||||
reviewList = limitedReviewList
|
||||
)
|
||||
@ -154,4 +266,109 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun showBuyNowPopup(productId: Int) {
|
||||
showQuantityDialog(productId, true)
|
||||
}
|
||||
|
||||
private fun showAddToCartPopup(productId: Int) {
|
||||
showQuantityDialog(productId, false)
|
||||
}
|
||||
|
||||
private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
|
||||
val bottomSheetDialog = BottomSheetDialog(this)
|
||||
val view = layoutInflater.inflate(R.layout.dialog_count_buy, null)
|
||||
bottomSheetDialog.setContentView(view)
|
||||
|
||||
val btnDecrease = view.findViewById<Button>(R.id.btnDecrease)
|
||||
val btnIncrease = view.findViewById<Button>(R.id.btnIncrease)
|
||||
val tvQuantity = view.findViewById<TextView>(R.id.tvQuantity)
|
||||
val btnBuyNow = view.findViewById<Button>(R.id.btnBuyNow)
|
||||
val btnClose = view.findViewById<ImageButton>(R.id.btnCloseDialog)
|
||||
|
||||
// Set button text based on action
|
||||
if (!isBuyNow) {
|
||||
btnBuyNow.setText(R.string.add_to_cart)
|
||||
}
|
||||
|
||||
currentQuantity = 1
|
||||
tvQuantity.text = currentQuantity.toString()
|
||||
|
||||
val maxStock = viewModel.productDetail.value?.stock ?: 1
|
||||
|
||||
btnDecrease.setOnClickListener {
|
||||
if (currentQuantity > 1) {
|
||||
currentQuantity--
|
||||
tvQuantity.text = currentQuantity.toString()
|
||||
}
|
||||
}
|
||||
|
||||
btnIncrease.setOnClickListener {
|
||||
if (currentQuantity < maxStock) {
|
||||
currentQuantity++
|
||||
tvQuantity.text = currentQuantity.toString()
|
||||
} else {
|
||||
Toast.makeText(this, "Maximum stock reached", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
btnBuyNow.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
|
||||
if (isBuyNow) {
|
||||
// If it's Buy Now, navigate directly to checkout without adding to cart
|
||||
navigateToCheckout()
|
||||
} else {
|
||||
// If it's Add to Cart, add the item to the cart
|
||||
val cartItem = CartItem(
|
||||
productId = productId,
|
||||
quantity = currentQuantity
|
||||
)
|
||||
viewModel.reqCart(cartItem)
|
||||
}
|
||||
}
|
||||
|
||||
btnClose.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
bottomSheetDialog.show()
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
|
||||
private fun navigateToCheckout() {
|
||||
val productDetail = viewModel.productDetail.value ?: return
|
||||
val storeDetail = viewModel.storeDetail.value
|
||||
|
||||
if (storeDetail !is Result.Success || storeDetail.data == null) {
|
||||
Toast.makeText(this, "Store information not available", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
// Start checkout activity with buy now flow
|
||||
CheckoutActivity.startForBuyNow(
|
||||
context = this,
|
||||
storeId = productDetail.storeId,
|
||||
storeName = storeDetail.data.storeName,
|
||||
productId = productDetail.productId,
|
||||
productName = productDetail.productName,
|
||||
productImage = productDetail.image,
|
||||
quantity = currentQuantity,
|
||||
price = productDetail.price.toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||
|
||||
fun start(context: Context, productId: Int) {
|
||||
val intent = Intent(context, DetailProductActivity::class.java)
|
||||
intent.putExtra(EXTRA_PRODUCT_ID, productId)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProductUserViewModel(private val repository: ProductRepository) : ViewModel() {
|
||||
|
||||
private val _productDetail = MutableLiveData<Product?>()
|
||||
val productDetail: LiveData<Product?> get() = _productDetail
|
||||
|
||||
private val _storeDetail = MutableLiveData<Result<StoreProduct?>>()
|
||||
val storeDetail : LiveData<Result<StoreProduct?>> get() = _storeDetail
|
||||
|
||||
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
|
||||
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
|
||||
|
||||
private val _otherProducts = MutableLiveData<List<ProductsItem>>()
|
||||
val otherProducts: LiveData<List<ProductsItem>> get() = _otherProducts
|
||||
|
||||
private val _addCart = MutableLiveData<Result<AddCartResponse>>()
|
||||
val addCart: LiveData<Result<AddCartResponse>> get() = _addCart
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> get() = _isLoading
|
||||
|
||||
private val _error = MutableLiveData<String>()
|
||||
val error: LiveData<String> get() = _error
|
||||
|
||||
|
||||
fun loadProductDetail(productId: Int) {
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = repository.fetchProductDetail(productId)
|
||||
_productDetail.value = result?.product
|
||||
|
||||
//Load store details if product has a store ID
|
||||
result?.product?.storeId?.let { storeId ->
|
||||
loadStoreDetail(storeId)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProductViewModel", "Error loading product details: ${e.message}")
|
||||
_error.value = "Failed to load product details: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadStoreDetail(storeId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_storeDetail.value = Result.Loading
|
||||
val result = repository.fetchStoreDetail(storeId)
|
||||
_storeDetail.value = result
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProductViewModel", "Error loading store details: ${e.message}")
|
||||
_storeDetail.value = Result.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadReviews(productId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val reviews = repository.fetchProductReview(productId)
|
||||
_reviewProduct.value = reviews ?: emptyList()
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProductViewModel", "Error loading reviews: ${e.message}")
|
||||
_reviewProduct.value = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadOtherProducts(storeId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = repository.getAllProducts() // Fetch products
|
||||
|
||||
if (result is Result.Success) {
|
||||
val allProducts = result.data // Extract the list
|
||||
val filteredProducts = allProducts.filter {
|
||||
it.storeId == storeId && it.id != _productDetail.value?.productId
|
||||
} // Filter by storeId and exclude current product
|
||||
_otherProducts.value = filteredProducts // Update LiveData
|
||||
} else if (result is Result.Error) {
|
||||
Log.e("ProductViewModel", "Error loading other products: ${result.exception.message}")
|
||||
_otherProducts.value = emptyList() // Set empty list on failure
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProductViewModel", "Exception loading other products: ${e.message}")
|
||||
_otherProducts.value = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
fun reqCart(request: CartItem){
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
when (val result = repository.addToCart(request)) {
|
||||
is Result.Success -> {
|
||||
_addCart.value = result
|
||||
_isLoading.value = false
|
||||
}
|
||||
is Result.Error -> {
|
||||
_addCart.value = result
|
||||
_error.value = result.exception.message ?: "Unknown error"
|
||||
_isLoading.value = false
|
||||
}
|
||||
is Result.Loading -> {
|
||||
_isLoading.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// fun loadStoreDetail(storeId: Int){
|
||||
// viewModelScope.launch {
|
||||
// val storeResult = repository.fetchStoreDetail(storeId)
|
||||
// _storeDetail.value = storeResult
|
||||
// }
|
||||
// }
|
@ -13,18 +13,17 @@ import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityReviewProductBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
|
||||
|
||||
class ReviewProductActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityReviewProductBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private var reviewsAdapter: ReviewsAdapter? = null
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private val viewModel: ProductViewModel by viewModels {
|
||||
private val viewModel: ProductUserViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val productRepository = ProductRepository(apiService)
|
||||
ProductViewModel(productRepository)
|
||||
ProductUserViewModel(productRepository)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
@ -1,10 +1,12 @@
|
||||
package com.alya.ecommerce_serang.ui.profile
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
@ -62,25 +64,33 @@ class DetailProfileActivity : AppCompatActivity() {
|
||||
binding.tvNameUser.setText(user.name.toString())
|
||||
binding.tvUsername.setText(user.username)
|
||||
binding.tvEmailUser.setText(user.email)
|
||||
binding.tvDateBirth.setText(formatDate(user.birthDate))
|
||||
Log.d("ProfileActivity", "Raw Birth Date: ${user.birthDate}")
|
||||
binding.tvDateBirth.setText(user.birthDate?.let { formatDate(it) } ?: "N/A")
|
||||
Log.d("ProfileActivity", "Formatted Birth Date: ${formatDate(user.birthDate)}")
|
||||
binding.tvNumberPhoneUser.setText(user.phone)
|
||||
|
||||
if (user.image != null && user.image is String) {
|
||||
Glide.with(this)
|
||||
.load(user.image)
|
||||
.placeholder(R.drawable.baseline_account_circle_24)
|
||||
.into(binding.profileImage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatDate(dateString: String): String {
|
||||
private fun formatDate(dateString: String?): String {
|
||||
if (dateString.isNullOrEmpty()) return "N/A" // Return default if null
|
||||
|
||||
return try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) //from json
|
||||
inputFormat.timeZone = TimeZone.getTimeZone("UTC") //get timezone
|
||||
val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) // new format
|
||||
val date = inputFormat.parse(dateString) // Parse from json format
|
||||
outputFormat.format(date!!) // convert to new format
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||
inputFormat.timeZone = TimeZone.getTimeZone("UTC") // Ensure parsing in UTC
|
||||
|
||||
val outputFormat = SimpleDateFormat("dd-MM-yy", Locale.getDefault()) // Convert to "dd-MM-yy" format
|
||||
val date = inputFormat.parse(dateString)
|
||||
outputFormat.format(date ?: return "Invalid Date") // Ensure valid date
|
||||
} catch (e: Exception) {
|
||||
dateString // Return original if error occurs
|
||||
Log.e("ERROR", "Date parsing error: ${e.message}") // Log errors for debugging
|
||||
"Invalid Date"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package com.alya.ecommerce_serang.utils
|
||||
|
||||
import androidx.lifecycle.AbstractSavedStateViewModelFactory
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
|
||||
class BaseViewModelFactory<VM : ViewModel>(
|
||||
private val creator: () -> VM
|
||||
@ -10,4 +13,20 @@ class BaseViewModelFactory<VM : ViewModel>(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return creator() as T
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new factory for SavedStateHandle ViewModels
|
||||
class SavedStateViewModelFactory<VM : ViewModel>(
|
||||
private val owner: SavedStateRegistryOwner,
|
||||
private val creator: (SavedStateHandle) -> VM
|
||||
) : AbstractSavedStateViewModelFactory(owner, null) {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
key: String,
|
||||
modelClass: Class<T>,
|
||||
handle: SavedStateHandle
|
||||
): T {
|
||||
return creator(handle) as T
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.response.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -7,9 +7,9 @@ import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.Store
|
||||
import com.alya.ecommerce_serang.data.api.response.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.launch
|
||||
@ -20,8 +20,8 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
|
||||
private val _productDetail = MutableLiveData<Product?>()
|
||||
val productDetail: LiveData<Product?> get() = _productDetail
|
||||
|
||||
private val _storeDetail = MutableLiveData<Store?>()
|
||||
val storeDetail : LiveData<Store?> get() = _storeDetail
|
||||
private val _storeDetail = MutableLiveData<StoreProduct?>()
|
||||
val storeDetail : LiveData<StoreProduct?> get() = _storeDetail
|
||||
|
||||
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
|
||||
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
|
||||
|
@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.response.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
5
app/src/main/res/drawable/baseline_edit_24.xml
Normal file
5
app/src/main/res/drawable/baseline_edit_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="@color/blue_500" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@color/blue_500" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
|
||||
</vector>
|
5
app/src/main/res/drawable/baseline_location_pin_24.xml
Normal file
5
app/src/main/res/drawable/baseline_location_pin_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2L12,2C8.13,2 5,5.13 5,9c0,1.74 0.5,3.37 1.41,4.84c0.95,1.54 2.2,2.86 3.16,4.4c0.47,0.75 0.81,1.45 1.17,2.26C11,21.05 11.21,22 12,22h0c0.79,0 1,-0.95 1.25,-1.5c0.37,-0.81 0.7,-1.51 1.17,-2.26c0.96,-1.53 2.21,-2.85 3.16,-4.4C18.5,12.37 19,10.74 19,9C19,5.13 15.87,2 12,2zM12,11.75c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5S13.38,11.75 12,11.75z"/>
|
||||
|
||||
</vector>
|
8
app/src/main/res/drawable/bg_popup_count.xml
Normal file
8
app/src/main/res/drawable/bg_popup_count.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<corners android:radius="16dp"/>
|
||||
<padding android:left="16dp" android:top="16dp"
|
||||
android:right="16dp" android:bottom="16dp"/>
|
||||
</shape>
|
6
app/src/main/res/drawable/button_address_background.xml
Normal file
6
app/src/main/res/drawable/button_address_background.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/blue_500" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
9
app/src/main/res/drawable/edit_text_background.xml
Normal file
9
app/src/main/res/drawable/edit_text_background.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E0E0E0" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
204
app/src/main/res/layout/activity_add_address.xml
Normal file
204
app/src/main/res/layout/activity_add_address.xml
Normal file
@ -0,0 +1,204 @@
|
||||
<?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:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
tools:context=".ui.order.address.AddAddressActivity">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:title="Tambah Alamat" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#E0E0E0"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/buttonSimpan"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider">
|
||||
|
||||
<LinearLayout
|
||||
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="Nama Penerima"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etNamaPenerima"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:hint="Isi nama penerima"
|
||||
android:inputType="textPersonName"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Nomor Hp"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etNomorHp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:hint="Isi nomor handphone aktif"
|
||||
android:inputType="phone"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Detail Alamat"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etDetailAlamat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:gravity="top"
|
||||
android:hint="Isi detail alamat (nomor rumah, lantai, dll)"
|
||||
android:inputType="textMultiLine"
|
||||
android:lines="3"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- Provinsi -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Provinsi"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="Pilih Provinsi"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/autoCompleteProvinsi"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Kabupaten / Kota -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Kabupaten / Kota"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/autoCompleteKabupaten"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:hint="Masukkan Kabupaten"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Kecamatan / Desa -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Kecamatan / Desa"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="Isi Kecamatan / Desa"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etKecamatan"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp"
|
||||
android:inputType="textCapWords" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Kode Pos"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etKodePos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:hint="Isi jawaban Anda di sini"
|
||||
android:inputType="number"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonSimpan"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@drawable/button_address_background"
|
||||
android:text="Simpan"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
61
app/src/main/res/layout/activity_address.xml
Normal file
61
app/src/main/res/layout/activity_address.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?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:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.order.address.AddressActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:paddingEnd="24dp"
|
||||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:title="Alamat Pengiriman " />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="textEnd"
|
||||
android:layout_gravity="center"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:textColor="@color/blue_500"
|
||||
android:fontFamily="@font/dmsans_semibold"
|
||||
android:textSize="14sp"
|
||||
android:clickable="true"
|
||||
android:text="Tambah Alamat"
|
||||
tools:ignore="RtlCompat" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#E0E0E0"
|
||||
app:layout_constraintTop_toBottomOf="@id/linear_toolbar" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/divider"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_seller_order"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_card_address"/>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
10
app/src/main/res/layout/activity_cart.xml
Normal file
10
app/src/main/res/layout/activity_cart.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?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:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".data.api.response.cart.CartActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
425
app/src/main/res/layout/activity_checkout.xml
Normal file
425
app/src/main/res/layout/activity_checkout.xml
Normal file
@ -0,0 +1,425 @@
|
||||
<?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:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black_800"
|
||||
tools:context=".ui.order.CheckoutActivity">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/white"
|
||||
android:elevation="2dp"
|
||||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:title="Pemesanan" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_payment_bar">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Delivery Address Section -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_delivery_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_location_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/baseline_location_pin_24"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:tint="#3D84FF" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Alamat Pengiriman"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:layout_marginStart="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_places_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rumah"
|
||||
android:textColor="#5A5A5A"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="2dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="32dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Jl. Pegangasan Timur"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginStart="32dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_change_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pilih Alamat"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Product Items Section -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_product"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_product_items"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
tools:listitem="@layout/item_order_seller" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="#F5F5F5" />
|
||||
|
||||
<!-- Voucher Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_voucher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:layout_width="24dp"-->
|
||||
<!-- android:layout_height="24dp"-->
|
||||
<!-- android:src="@drawable/ic_voucher_24" />-->
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Gunakan Voucher"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginStart="8dp" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_arrow_right" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Shipping Method Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_shipping_method"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Metode Pengiriman"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_option"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Opsi Pengiriman"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="0dp"
|
||||
app:cardBackgroundColor="#F5F5F5">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_jne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_courier_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="JNE"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_delivery_estimate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="3 - 4 hari kerja"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#757575" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_price"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp15.000"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:layout_gravity="center_vertical" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Payment Method Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_payment_method"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Metode Pembayaran"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_payment_methods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_payment_method"
|
||||
tools:itemCount="2" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Price Summary Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_price_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="1 item"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_item_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp65.000"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Biaya Pengiriman"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_fee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp15.000"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/black_50"
|
||||
android:layout_marginVertical="12dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Total"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp75.000"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- Bottom Payment Bar -->
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_payment_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="@color/white"
|
||||
android:elevation="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total:"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_bottom_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp75.000"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="18sp"
|
||||
android:fontFamily="@font/dmsans_bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_pay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bayar"
|
||||
android:textAllCaps="false"
|
||||
android:paddingHorizontal="32dp"
|
||||
app:cornerRadius="8dp"
|
||||
android:backgroundTint="#3D84FF" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -406,6 +406,13 @@
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar_detail_prod"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<!-- Bottom Action Bar -->
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
|
10
app/src/main/res/layout/activity_edit_address.xml
Normal file
10
app/src/main/res/layout/activity_edit_address.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?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:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.order.address.EditAddressActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
32
app/src/main/res/layout/activity_shipping.xml
Normal file
32
app/src/main/res/layout/activity_shipping.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?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:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/cardview_light_background"
|
||||
tools:context=".ui.order.ShippingActivity">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:title="Pengiriman" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_shipment_order"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_shipping_order"/>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
71
app/src/main/res/layout/dialog_count_buy.xml
Normal file
71
app/src/main/res/layout/dialog_count_buy.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_popup_count"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingTop="32dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQuantityTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Jumlah Produk"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDecrease"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="-" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvQuantity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:text="1"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnIncrease"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="+" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBuyNow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Beli Sekarang"
|
||||
android:layout_marginTop="24dp"
|
||||
android:backgroundTint="@color/blue_500"
|
||||
android:textColor="@android:color/white" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Tombol X Close -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnCloseDialog"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="end|top"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:contentDescription="Close" />
|
||||
</FrameLayout>
|
||||
|
54
app/src/main/res/layout/item_card_address.xml
Normal file
54
app/src/main/res/layout/item_card_address.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardUseCompatPadding="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/cardview_light_background"
|
||||
android:padding="16dp">
|
||||
<ImageView
|
||||
android:id="@+id/iv_Location"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:src="@drawable/baseline_location_pin_24"/>
|
||||
<TextView
|
||||
android:id="@+id/tv_name_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/dmsans_semibold"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:text="Nama Penerima"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_Location"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
<ImageView
|
||||
android:id="@+id/iv_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/baseline_edit_24"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
<TextView
|
||||
android:id="@+id/tv_detail_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Jl. Salak"
|
||||
android:fontFamily="@font/dmsans_light"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/iv_Location"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
61
app/src/main/res/layout/item_order_product.xml
Normal file
61
app/src/main/res/layout/item_order_product.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?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"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_product"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/placeholder_image"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_product_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Keripik Balado"
|
||||
android:fontFamily="@font/dmsans_bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_product_quantity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1 buah"
|
||||
android:fontFamily="@font/dmsans_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_product_price"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp65,000"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerInsetStart="16dp"
|
||||
app:dividerInsetEnd="16dp"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
44
app/src/main/res/layout/item_order_seller.xml
Normal file
44
app/src/main/res/layout/item_order_seller.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<?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"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_seller_order"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_margin="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/outline_store_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_store_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SnackEnak"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_semibold"
|
||||
android:layout_marginStart="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_seller_order_product"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_order_product"/>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerInsetStart="16dp"
|
||||
app:dividerInsetEnd="16dp"/>
|
||||
|
||||
</LinearLayout>
|
29
app/src/main/res/layout/item_payment_method.xml
Normal file
29
app/src/main/res/layout/item_payment_method.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?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"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_payment_method"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/outline_store_24"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_payment_method_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Bank Transfer"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_payment_method"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
64
app/src/main/res/layout/item_shipping_order.xml
Normal file
64
app/src/main/res/layout/item_shipping_order.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="start"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:orientation="horizontal">
|
||||
<RadioButton
|
||||
android:id="@+id/radio_btn_cost"
|
||||
android:layout_margin="4dp"
|
||||
android:clickable="true"
|
||||
android:gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/courier_name_cost"
|
||||
android:fontFamily="@font/dmsans_bold"
|
||||
android:textSize="20sp"
|
||||
android:padding="4dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="JNE"/>
|
||||
<TextView
|
||||
android:id="@+id/est_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:text="Estimasi 3-4 hari"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cost_price"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_margin="16dp"
|
||||
android:fontFamily="@font/dmsans_semibold"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="Rp15.0000"/>
|
||||
|
||||
</LinearLayout>
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#E0E0E0"
|
||||
app:layout_constraintTop_toBottomOf="@id/linear_toolbar" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
@ -1,43 +1,34 @@
|
||||
[versions]
|
||||
agp = "8.9.1"
|
||||
agp = "8.5.2"
|
||||
glide = "4.16.0"
|
||||
hiltAndroid = "2.51"
|
||||
hiltLifecycleViewmodel = "1.0.0-alpha03"
|
||||
kotlin = "1.9.0"
|
||||
coreKtx = "1.15.0"
|
||||
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
appcompat = "1.7.0"
|
||||
loggingInterceptor = "4.11.0"
|
||||
material = "1.12.0"
|
||||
activity = "1.10.1"
|
||||
constraintlayout = "2.2.1"
|
||||
activity = "1.9.2"
|
||||
constraintlayout = "2.1.4"
|
||||
legacySupportV4 = "1.0.0"
|
||||
lifecycleLivedataKtx = "2.8.7"
|
||||
lifecycleViewmodelKtx = "2.8.7"
|
||||
fragmentKtx = "1.8.6"
|
||||
navigationFragmentKtx = "2.8.9"
|
||||
navigationUiKtx = "2.8.9"
|
||||
pagingRuntime = "3.3.6"
|
||||
recyclerview = "1.4.0"
|
||||
retrofit = "2.9.0"
|
||||
swiperefreshlayout = "1.1.0"
|
||||
fragmentKtx = "1.5.6"
|
||||
navigationFragmentKtx = "2.8.5"
|
||||
navigationUiKtx = "2.8.5"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
androidx-hilt-lifecycle-viewmodel = { module = "androidx.hilt:hilt-lifecycle-viewmodel", version.ref = "hiltLifecycleViewmodel" }
|
||||
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" }
|
||||
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
@ -47,7 +38,6 @@ androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifec
|
||||
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
|
||||
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
|
||||
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
Reference in New Issue
Block a user