mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
Merge branch 'master' into gracia
This commit is contained in:
549
.idea/other.xml
generated
Normal file
549
.idea/other.xml
generated
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
<?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>
|
@ -23,7 +23,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
|
buildConfigField("String", "BASE_URL", "\"http://192.168.1.4:3000/\"")
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
@ -31,7 +31,7 @@ android {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
|
buildConfigField("String", "BASE_URL", "\"http://192.168.1.4:3000/\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@ -16,6 +17,9 @@
|
|||||||
android:theme="@style/Theme.Ecommerce_serang"
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.product.ReviewProductActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.profile.mystore.sells.SellsActivity"
|
android:name=".ui.profile.mystore.sells.SellsActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@ -41,7 +45,10 @@
|
|||||||
android:name=".ui.profile.mystore.TokoSayaActivity"
|
android:name=".ui.profile.mystore.TokoSayaActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.product.DetailProductActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.auth.RegisterActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@ -49,6 +56,15 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.auth.LoginActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.profile.DetailProfileActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.MainActivity"
|
||||||
|
android:exported="false"></activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,11 +0,0 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.dto
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class Category(
|
|
||||||
val id: String,
|
|
||||||
val image: String,
|
|
||||||
val title: String
|
|
||||||
): Parcelable
|
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
|
||||||
|
data class CategoryItem(
|
||||||
|
|
||||||
|
@field:SerializedName("image")
|
||||||
|
val image: String,
|
||||||
|
|
||||||
|
@field:SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("store_type_id")
|
||||||
|
val storeTypeId: Int
|
||||||
|
) : Parcelable
|
@ -14,7 +14,7 @@ data class DetailProduct(
|
|||||||
val inStock: Int,
|
val inStock: Int,
|
||||||
val price: Double,
|
val price: Double,
|
||||||
val rating: Double,
|
val rating: Double,
|
||||||
val related: List<Product>,
|
val related: List<ProductsItem>,
|
||||||
val reviews: Int,
|
val reviews: Int,
|
||||||
val title: String,
|
val title: String,
|
||||||
@SerializedName("free_delivery")
|
@SerializedName("free_delivery")
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class LoginRequest (
|
||||||
|
@SerializedName("emailOrPhone") val email: String,
|
||||||
|
@SerializedName("password") val password: String,
|
||||||
|
)
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class OtpRequest(
|
||||||
|
@SerializedName("email") val email: String
|
||||||
|
)
|
@ -1,17 +0,0 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.dto
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Product (
|
|
||||||
val id: String,
|
|
||||||
val discount: Double?,
|
|
||||||
@SerializedName("favorite")
|
|
||||||
var wishlist: Boolean,
|
|
||||||
val image: String,
|
|
||||||
val price: Double,
|
|
||||||
val rating: Double,
|
|
||||||
@SerializedName("rating_count")
|
|
||||||
val ratingCount: Int,
|
|
||||||
val title: String,
|
|
||||||
)
|
|
@ -1,8 +1,9 @@
|
|||||||
package com.alya.ecommerce_serang.data.model
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class Product(
|
data class ProductsItem(
|
||||||
|
|
||||||
@field:SerializedName("store_id")
|
@field:SerializedName("store_id")
|
||||||
val storeId: Int,
|
val storeId: Int,
|
||||||
@ -19,23 +20,20 @@ data class Product(
|
|||||||
@field:SerializedName("weight")
|
@field:SerializedName("weight")
|
||||||
val weight: Int,
|
val weight: Int,
|
||||||
|
|
||||||
@field:SerializedName("product_name")
|
|
||||||
val productName: String,
|
|
||||||
|
|
||||||
@field:SerializedName("is_pre_order")
|
@field:SerializedName("is_pre_order")
|
||||||
val isPreOrder: Boolean,
|
val isPreOrder: Boolean,
|
||||||
|
|
||||||
@field:SerializedName("duration")
|
|
||||||
val duration: Any,
|
|
||||||
|
|
||||||
@field:SerializedName("category_id")
|
@field:SerializedName("category_id")
|
||||||
val categoryId: Int,
|
val categoryId: Int,
|
||||||
|
|
||||||
@field:SerializedName("price")
|
@field:SerializedName("price")
|
||||||
val price: String,
|
val price: String,
|
||||||
|
|
||||||
@field:SerializedName("product_id")
|
@field:SerializedName("name")
|
||||||
val productId: Int,
|
val name: String,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
@field:SerializedName("min_order")
|
@field:SerializedName("min_order")
|
||||||
val minOrder: Int,
|
val minOrder: Int,
|
||||||
@ -46,6 +44,6 @@ data class Product(
|
|||||||
@field:SerializedName("stock")
|
@field:SerializedName("stock")
|
||||||
val stock: Int,
|
val stock: Int,
|
||||||
|
|
||||||
@field:SerializedName("product_category")
|
@field:SerializedName("status")
|
||||||
val productCategory: String
|
val status: String
|
||||||
)
|
)
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class RegisterRequest (
|
||||||
|
val name: String?,
|
||||||
|
val email: String?,
|
||||||
|
val password: String?,
|
||||||
|
val username: String?,
|
||||||
|
val phone: String?,
|
||||||
|
@SerializedName("birth_date") val birthDate: String?,
|
||||||
|
@SerializedName("userimg") val image: String?,
|
||||||
|
val otp: String? = null
|
||||||
|
)
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class UserProfile(
|
||||||
|
|
||||||
|
@field:SerializedName("image")
|
||||||
|
val image: String? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("role")
|
||||||
|
val role: String,
|
||||||
|
|
||||||
|
@field:SerializedName("user_id")
|
||||||
|
val userId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("phone")
|
||||||
|
val phone: String,
|
||||||
|
|
||||||
|
@field:SerializedName("birth_date")
|
||||||
|
val birthDate: String,
|
||||||
|
|
||||||
|
@field:SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:SerializedName("email")
|
||||||
|
val email: String,
|
||||||
|
|
||||||
|
@field:SerializedName("username")
|
||||||
|
val username: String
|
||||||
|
)
|
@ -1,5 +1,6 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.response
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class AllProductResponse(
|
data class AllProductResponse(
|
||||||
@ -11,47 +12,4 @@ data class AllProductResponse(
|
|||||||
val products: List<ProductsItem>
|
val products: List<ProductsItem>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ProductsItem(
|
|
||||||
|
|
||||||
@field:SerializedName("store_id")
|
|
||||||
val storeId: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("image")
|
|
||||||
val image: String,
|
|
||||||
|
|
||||||
@field:SerializedName("rating")
|
|
||||||
val rating: String,
|
|
||||||
|
|
||||||
@field:SerializedName("description")
|
|
||||||
val description: String,
|
|
||||||
|
|
||||||
@field:SerializedName("weight")
|
|
||||||
val weight: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("is_pre_order")
|
|
||||||
val isPreOrder: Boolean,
|
|
||||||
|
|
||||||
@field:SerializedName("category_id")
|
|
||||||
val categoryId: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("price")
|
|
||||||
val price: String,
|
|
||||||
|
|
||||||
@field:SerializedName("name")
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
@field:SerializedName("id")
|
|
||||||
val id: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("min_order")
|
|
||||||
val minOrder: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("total_sold")
|
|
||||||
val totalSold: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("stock")
|
|
||||||
val stock: Int,
|
|
||||||
|
|
||||||
@field:SerializedName("status")
|
|
||||||
val status: String
|
|
||||||
)
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class AllStoreResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("store")
|
||||||
|
val store: AllStore,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AllStore(
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class CategoryResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("Category")
|
||||||
|
val category: List<CategoryItem>,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
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
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class LoginResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("role")
|
||||||
|
val role: String,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("accessToken")
|
||||||
|
val accessToken: String
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class OtpResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
@ -17,7 +17,7 @@ data class Product(
|
|||||||
val storeId: Int,
|
val storeId: Int,
|
||||||
|
|
||||||
@field:SerializedName("image")
|
@field:SerializedName("image")
|
||||||
val image: String,
|
val image: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("rating")
|
@field:SerializedName("rating")
|
||||||
val rating: String,
|
val rating: String,
|
||||||
@ -35,7 +35,7 @@ data class Product(
|
|||||||
val isPreOrder: Boolean,
|
val isPreOrder: Boolean,
|
||||||
|
|
||||||
@field:SerializedName("duration")
|
@field:SerializedName("duration")
|
||||||
val duration: Any,
|
val duration: Any?,
|
||||||
|
|
||||||
@field:SerializedName("category_id")
|
@field:SerializedName("category_id")
|
||||||
val categoryId: Int,
|
val categoryId: Int,
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class ProfileResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("user")
|
||||||
|
val user: UserProfile
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class RegisterResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("user")
|
||||||
|
val user: User
|
||||||
|
)
|
||||||
|
|
||||||
|
data class User(
|
||||||
|
|
||||||
|
@field:SerializedName("image")
|
||||||
|
val image: String,
|
||||||
|
|
||||||
|
@field:SerializedName("password")
|
||||||
|
val password: String,
|
||||||
|
|
||||||
|
@field:SerializedName("role")
|
||||||
|
val role: String,
|
||||||
|
|
||||||
|
@field:SerializedName("phone")
|
||||||
|
val phone: String,
|
||||||
|
|
||||||
|
@field:SerializedName("birth_date")
|
||||||
|
val birthDate: String,
|
||||||
|
|
||||||
|
@field:SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("email")
|
||||||
|
val email: String,
|
||||||
|
|
||||||
|
@field:SerializedName("username")
|
||||||
|
val username: String
|
||||||
|
)
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class ReviewProductResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("reviews")
|
||||||
|
val reviews: List<ReviewsItem>,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReviewsItem(
|
||||||
|
|
||||||
|
@field:SerializedName("order_item_id")
|
||||||
|
val orderItemId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("review_date")
|
||||||
|
val reviewDate: String,
|
||||||
|
|
||||||
|
@field:SerializedName("user_image")
|
||||||
|
val userImage: String? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("product_id")
|
||||||
|
val productId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("rating")
|
||||||
|
val rating: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("review_text")
|
||||||
|
val reviewText: String,
|
||||||
|
|
||||||
|
@field:SerializedName("product_name")
|
||||||
|
val productName: String,
|
||||||
|
|
||||||
|
@field:SerializedName("username")
|
||||||
|
val username: String
|
||||||
|
)
|
@ -0,0 +1,138 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response
|
||||||
|
|
||||||
|
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: Any,
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
@ -1,6 +1,8 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.retrofit
|
package com.alya.ecommerce_serang.data.api.retrofit
|
||||||
|
|
||||||
import com.alya.ecommerce_serang.BuildConfig
|
import com.alya.ecommerce_serang.BuildConfig
|
||||||
|
import com.alya.ecommerce_serang.utils.AuthInterceptor
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
@ -8,17 +10,43 @@ import retrofit2.converter.gson.GsonConverterFactory
|
|||||||
|
|
||||||
class ApiConfig {
|
class ApiConfig {
|
||||||
companion object {
|
companion object {
|
||||||
fun getApiService(): ApiService {
|
fun getApiService(tokenManager: SessionManager): ApiService {
|
||||||
val loggingInterceptor =
|
|
||||||
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
|
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
val authInterceptor = AuthInterceptor(tokenManager)
|
||||||
|
|
||||||
val client = OkHttpClient.Builder()
|
val client = OkHttpClient.Builder()
|
||||||
.addInterceptor(loggingInterceptor)
|
.addInterceptor(loggingInterceptor)
|
||||||
|
.addInterceptor(authInterceptor)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val retrofit = Retrofit.Builder()
|
val retrofit = Retrofit.Builder()
|
||||||
.baseUrl(BuildConfig.BASE_URL)
|
.baseUrl(BuildConfig.BASE_URL)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.client(client)
|
.client(client)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
return retrofit.create(ApiService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUnauthenticatedApiService(): ApiService {
|
||||||
|
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.addInterceptor(loggingInterceptor)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl(BuildConfig.BASE_URL)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.client(client)
|
||||||
|
.build()
|
||||||
|
|
||||||
return retrofit.create(ApiService::class.java)
|
return retrofit.create(ApiService::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,67 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.retrofit
|
package com.alya.ecommerce_serang.data.api.retrofit
|
||||||
|
|
||||||
|
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.response.AllProductResponse
|
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.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 retrofit2.Call
|
import retrofit2.Call
|
||||||
|
import retrofit2.Response
|
||||||
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
interface ApiService {
|
interface ApiService {
|
||||||
@GET("product")
|
@POST("registeruser")
|
||||||
fun getAllProduct(
|
suspend fun register (
|
||||||
@Header("Authorization") token: String = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzEsIm5hbWUiOiJhbHlhIiwiZW1haWwiOiJha3VuYmVsYWphci5hbHlhQGdtYWlsLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzM4NDg0OTc0LCJleHAiOjE3NDEwNzY5NzR9.0JyXJQ_6CKiZEi0gvk1gcn-0ILu3a9lOr3HqjhJXbBE"
|
@Body registerRequest: RegisterRequest
|
||||||
): Call<AllProductResponse>
|
): Response<RegisterResponse>
|
||||||
|
|
||||||
|
@POST("otp")
|
||||||
|
suspend fun getOTP(
|
||||||
|
@Body otpRequest: OtpRequest
|
||||||
|
):OtpResponse
|
||||||
|
|
||||||
|
@POST("login")
|
||||||
|
suspend fun login(
|
||||||
|
@Body loginRequest: LoginRequest
|
||||||
|
): Response<LoginResponse>
|
||||||
|
|
||||||
|
@GET("category")
|
||||||
|
suspend fun allCategory(
|
||||||
|
): Response<CategoryResponse>
|
||||||
|
|
||||||
|
@GET("product")
|
||||||
|
suspend fun getAllProduct(): Response<AllProductResponse>
|
||||||
|
|
||||||
|
@GET("product/review/{id}")
|
||||||
|
suspend fun getProductReview(
|
||||||
|
@Path("id") productId: Int
|
||||||
|
): Response<ReviewProductResponse>
|
||||||
|
|
||||||
@GET("product/detail/{id}")
|
@GET("product/detail/{id}")
|
||||||
fun getDetailProduct (
|
suspend fun getDetailProduct (
|
||||||
@Header("Authorization") token: String,
|
|
||||||
@Path("id") productId: Int
|
@Path("id") productId: Int
|
||||||
): Call<ProductResponse>
|
): Response<ProductResponse>
|
||||||
|
|
||||||
|
@GET("profile")
|
||||||
|
suspend fun getUserProfile(): Response<ProfileResponse>
|
||||||
|
|
||||||
|
@GET("store/detail/{id}")
|
||||||
|
suspend fun getDetailStore (
|
||||||
|
@Path("id") storeId: Int
|
||||||
|
): Response<DetailStoreProductResponse>
|
||||||
|
|
||||||
|
|
||||||
|
@GET("mystore")
|
||||||
|
fun getStore (): Call<StoreResponse>
|
||||||
}
|
}
|
@ -1,7 +1,10 @@
|
|||||||
package com.alya.ecommerce_serang.data.repository
|
package com.alya.ecommerce_serang.data.repository
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
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.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -11,25 +14,84 @@ class ProductRepository(private val apiService: ApiService) {
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
Log.d("ProductRepository", "Attempting to fetch products")
|
Log.d("ProductRepository", "Attempting to fetch products")
|
||||||
val response = apiService.getAllProduct().execute()
|
val response = apiService.getAllProduct()
|
||||||
Log.d("ProductRepository", "Response received. Success: ${response.isSuccessful}")
|
|
||||||
Log.d("ProductRepository", "Response code: ${response.code()}")
|
|
||||||
Log.d("ProductRepository", "Response message: ${response.message()}")
|
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
Result.success(response.body()?.products ?: emptyList())
|
// Return a Result.Success with the list of products
|
||||||
|
|
||||||
|
Result.Success(response.body()?.products ?: emptyList())
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Result.failure(Exception("Failed to fetch products"))
|
// Return a Result.Error with a custom Exception
|
||||||
|
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||||
|
Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Result.failure(e)
|
// Return a Result.Error with the exception caught
|
||||||
|
Result.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// suspend fun getCategories():List<Category>
|
|
||||||
|
suspend fun fetchProductDetail(productId: Int): ProductResponse? {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getDetailProduct(productId)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()
|
||||||
|
} else {
|
||||||
|
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAllCategories(): Result<List<CategoryItem>> =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
Log.d("Categories", "Attempting to fetch categories")
|
||||||
|
val response = apiService.allCategory()
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val categories = response.body()?.category ?: emptyList()
|
||||||
|
Log.d("Categories", "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)
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchProductReview(productId: Int): List<ReviewsItem>? {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getProductReview(productId)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.reviews // Ambil daftar review dari response
|
||||||
|
} else {
|
||||||
|
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// suspend fun fetchStoreDetail(storeId: Int): Store? {
|
||||||
|
// return try {
|
||||||
|
// val response = apiService.getStore(storeId)
|
||||||
|
// if (response.isSucessful) {
|
||||||
|
// response.body()?.store
|
||||||
|
// } else {
|
||||||
|
// Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||||
//
|
//
|
||||||
// fun getProducts(query: ProductQuery) : Flow<PagingData<Product>>
|
// null
|
||||||
// fun getRecentSearchs(): Flow<List<String>>
|
// }
|
||||||
// suspend fun clearRecents()
|
// } catch (e: Exception) {
|
||||||
// suspend fun addRecents(search:String)
|
// null
|
||||||
// suspend fun getProduct(id:String):DetailProduct
|
// }
|
||||||
}
|
// }
|
@ -0,0 +1,69 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.repository
|
||||||
|
|
||||||
|
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.retrofit.ApiService
|
||||||
|
|
||||||
|
class UserRepository(private val apiService: ApiService) {
|
||||||
|
|
||||||
|
suspend fun requestOtpRep(email: String): OtpResponse {
|
||||||
|
|
||||||
|
// fun requestOtpRep(email: String): Result<String> {
|
||||||
|
|
||||||
|
return apiService.getOTP(OtpRequest(email))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun registerUser(request: RegisterRequest): String {
|
||||||
|
val response = apiService.register(request) // API call
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val responseBody = response.body() ?: throw Exception("Empty response body")
|
||||||
|
return responseBody.message // Get the message from RegisterResponse
|
||||||
|
} else {
|
||||||
|
throw Exception("Registration failed: ${response.errorBody()?.string()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun login(email: String, password: String): Result<LoginResponse> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.login(LoginRequest(email, password))
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.let {
|
||||||
|
Result.Success(it)
|
||||||
|
} ?: Result.Error(Exception("Login response is empty"))
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchUserProfile(): Result<UserProfile?> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getUserProfile()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.user?.let {
|
||||||
|
Result.Success(it) // ✅ Returning only UserProfile
|
||||||
|
} ?: Result.Error(Exception("User data not found"))
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception("Error fetching profile: ${response.code()}"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Result<out T> {
|
||||||
|
data class Success<out T>(val data: T) : Result<T>()
|
||||||
|
data class Error(val exception: Throwable) : Result<Nothing>()
|
||||||
|
object Loading : Result<Nothing>()
|
||||||
|
}
|
@ -6,11 +6,16 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
|
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
//@AndroidEntryPoint
|
//@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
private lateinit var apiService: ApiService
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
private val navController by lazy {
|
private val navController by lazy {
|
||||||
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
|
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
|
||||||
}
|
}
|
||||||
@ -19,6 +24,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
apiService = ApiConfig.getApiService(sessionManager) // Inject SessionManager
|
||||||
|
|
||||||
setupBottomNavigation()
|
setupBottomNavigation()
|
||||||
observeDestinationChanges()
|
observeDestinationChanges()
|
||||||
|
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityLoginBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.MainActivity
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
|
class LoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityLoginBinding
|
||||||
|
private val loginViewModel: LoginViewModel by viewModels{
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getUnauthenticatedApiService()
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
LoginViewModel(userRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
setupListeners()
|
||||||
|
observeLoginState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
binding.btnLogin.setOnClickListener {
|
||||||
|
val email = binding.etLoginEmail.text.toString()
|
||||||
|
val password = binding.etLoginPassword.text.toString()
|
||||||
|
|
||||||
|
if (email.isEmpty() || password.isEmpty()) {
|
||||||
|
Toast.makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
loginViewModel.login(email, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeLoginState() {
|
||||||
|
loginViewModel.loginState.observe(this) { result ->
|
||||||
|
when (result) {
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||||
|
val accessToken = result.data.accessToken
|
||||||
|
|
||||||
|
val sessionManager = SessionManager(this)
|
||||||
|
sessionManager.saveToken(accessToken)
|
||||||
|
|
||||||
|
Toast.makeText(this, "Login Successful", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
||||||
|
Toast.makeText(this, "Login Failed: ${result.exception.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Show loading state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth
|
||||||
|
|
||||||
|
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.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class LoginViewModel(private val repository: UserRepository) : ViewModel() {
|
||||||
|
private val _loginState = MutableLiveData<com.alya.ecommerce_serang.data.repository.Result<LoginResponse>>()
|
||||||
|
val loginState: LiveData<Result<LoginResponse>> get() = _loginState
|
||||||
|
|
||||||
|
fun login(email: String, password: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_loginState.value = com.alya.ecommerce_serang.data.repository.Result.Loading
|
||||||
|
val result = repository.login(email, password)
|
||||||
|
_loginState.value = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
|
||||||
|
class OtpBottomSheetDialog(
|
||||||
|
private val userData: RegisterRequest, // Store user data
|
||||||
|
private val onRegister: (RegisterRequest) -> Unit)
|
||||||
|
: BottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(R.layout.dialog_otp, container, false)
|
||||||
|
|
||||||
|
val etOtp = view.findViewById<EditText>(R.id.etOtp)
|
||||||
|
val btnSubmit = view.findViewById<Button>(R.id.btnSubmit)
|
||||||
|
|
||||||
|
btnSubmit.setOnClickListener {
|
||||||
|
val otp = etOtp.text.toString()
|
||||||
|
if (otp.isNotEmpty()) {
|
||||||
|
val updatedUserData = userData.copy(otp = otp) // Add OTP to userData
|
||||||
|
onRegister(updatedUserData) // Send full data to ViewModel
|
||||||
|
dismiss() // Close dialog
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Please enter OTP", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
// override fun getTheme(): Int {
|
||||||
|
// return R.style.BottomSheetDialogTheme // Optional: Customize style
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
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.data.api.dto.RegisterRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityRegisterBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.MainActivity
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
|
class RegisterActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityRegisterBinding
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private val registerViewModel: RegisterViewModel by viewModels{
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getUnauthenticatedApiService()
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
RegisterViewModel(userRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
if (!sessionManager.getToken().isNullOrEmpty()) {
|
||||||
|
// User already logged in, redirect to MainActivity
|
||||||
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
binding = ActivityRegisterBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// Observe OTP state
|
||||||
|
observeOtpState()
|
||||||
|
|
||||||
|
binding.btnSignup.setOnClickListener {
|
||||||
|
// Retrieve values inside the click listener (so we get latest input)
|
||||||
|
val birthDate = binding.etBirthDate.text.toString()
|
||||||
|
val email = binding.etEmail.text.toString()
|
||||||
|
val password = binding.etPassword.text.toString()
|
||||||
|
val phone = binding.etNumberPhone.text.toString()
|
||||||
|
val username = binding.etUsername.text.toString()
|
||||||
|
val name = binding.etFullname.text.toString()
|
||||||
|
val image = "not yet"
|
||||||
|
|
||||||
|
val userData = RegisterRequest(name, email, password, username, phone, birthDate, image)
|
||||||
|
|
||||||
|
Log.d("RegisterActivity", "Requesting OTP for email: $email")
|
||||||
|
|
||||||
|
// Request OTP and wait for success before showing dialog
|
||||||
|
registerViewModel.requestOtp(userData.email.toString())
|
||||||
|
|
||||||
|
// Observe OTP state and show OTP dialog only when successful
|
||||||
|
registerViewModel.otpState.observe(this) { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
|
Log.d("RegisterActivity", "OTP sent successfully. Showing OTP dialog.")
|
||||||
|
// Show OTP dialog after OTP is successfully sent
|
||||||
|
val otpBottomSheet = OtpBottomSheetDialog(userData) { fullUserData ->
|
||||||
|
Log.d("RegisterActivity", "OTP entered successfully. Proceeding with registration.")
|
||||||
|
registerViewModel.registerUser(fullUserData) // Send complete data
|
||||||
|
}
|
||||||
|
otpBottomSheet.show(supportFragmentManager, "OtpBottomSheet")
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
// Show error message if OTP request fails
|
||||||
|
Log.e("RegisterActivity", "Failed to request OTP: ${result.exception.message}")
|
||||||
|
Toast.makeText(this, "Failed to request OTP: ${result.exception.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Optional: Show loading indicator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe Register state
|
||||||
|
observeRegisterState()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvLoginAlt.setOnClickListener{
|
||||||
|
val intent = Intent(this, LoginActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeOtpState() {
|
||||||
|
registerViewModel.otpState.observe(this) { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Show loading indicator
|
||||||
|
binding.progressBarOtp.visibility = android.view.View.VISIBLE
|
||||||
|
}
|
||||||
|
is Result.Success -> {
|
||||||
|
// Hide loading indicator and show success message
|
||||||
|
binding.progressBarOtp.visibility = android.view.View.GONE
|
||||||
|
// Toast.makeText(this@RegisterActivity, result.data, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
// Hide loading indicator and show error message
|
||||||
|
binding.progressBarOtp.visibility = android.view.View.GONE
|
||||||
|
Toast.makeText(this, "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRegisterState() {
|
||||||
|
registerViewModel.registerState.observe(this) { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Show loading indicator for registration
|
||||||
|
binding.progressBarRegister.visibility = android.view.View.VISIBLE
|
||||||
|
}
|
||||||
|
is Result.Success -> {
|
||||||
|
// Hide loading indicator and show success message
|
||||||
|
binding.progressBarRegister.visibility = android.view.View.GONE
|
||||||
|
Toast.makeText(this, result.data, Toast.LENGTH_SHORT).show()
|
||||||
|
val intent = Intent(this, LoginActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
// Navigate to another screen if needed
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
||||||
|
// Hide loading indicator and show error message
|
||||||
|
binding.progressBarRegister.visibility = android.view.View.GONE
|
||||||
|
Toast.makeText(this, "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.auth
|
||||||
|
|
||||||
|
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.RegisterRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.OtpResponse
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
|
||||||
|
|
||||||
|
// MutableLiveData for handling register state (Loading, Success, or Error)
|
||||||
|
private val _registerState = MutableLiveData<Result<String>>()
|
||||||
|
val registerState: LiveData<Result<String>> = _registerState
|
||||||
|
|
||||||
|
// MutableLiveData for handling OTP request state
|
||||||
|
private val _otpState = MutableLiveData<Result<Unit>>()
|
||||||
|
val otpState: LiveData<Result<Unit>> = _otpState
|
||||||
|
|
||||||
|
// MutableLiveData to store messages from API responses
|
||||||
|
private val _message = MutableLiveData<String>()
|
||||||
|
val message: LiveData<String> = _message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to request OTP by sending an email to the API.
|
||||||
|
* - It sets the OTP state to `Loading` before calling the repository.
|
||||||
|
* - If successful, it updates `_message` with the response message and signals success.
|
||||||
|
* - If an error occurs, it updates `_otpState` with `Result.Error` and logs the failure.
|
||||||
|
*/
|
||||||
|
fun requestOtp(email: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_otpState.value = Result.Loading // Indicating API call in progress
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call the repository function to request OTP
|
||||||
|
val response: OtpResponse = repository.requestOtpRep(email)
|
||||||
|
|
||||||
|
// Log and store success message
|
||||||
|
Log.d("RegisterViewModel", "OTP Response: ${response.message}")
|
||||||
|
_message.value = response.message // Store the message for UI feedback
|
||||||
|
|
||||||
|
// Update state to indicate success
|
||||||
|
_otpState.value = Result.Success(Unit)
|
||||||
|
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// Handle any errors and update state
|
||||||
|
_otpState.value = Result.Error(exception)
|
||||||
|
_message.value = exception.localizedMessage ?: "Failed to request OTP"
|
||||||
|
|
||||||
|
// Log the error for debugging
|
||||||
|
Log.e("RegisterViewModel", "OTP request failed for: $email", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to register a new user.
|
||||||
|
* - It first sets `_registerState` to `Loading` to indicate the process is starting.
|
||||||
|
* - Calls the repository function to handle user registration.
|
||||||
|
* - If successful, it updates `_message` and signals success with the response message.
|
||||||
|
* - If an error occurs, it updates `_registerState` with `Result.Error` and logs the failure.
|
||||||
|
*/
|
||||||
|
fun registerUser(request: RegisterRequest) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_registerState.value = Result.Loading // Indicating API call in progress
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call repository function to register the user
|
||||||
|
val message = repository.registerUser(request)
|
||||||
|
|
||||||
|
// Store and display success message
|
||||||
|
_message.value = message
|
||||||
|
_registerState.value = Result.Success(message) // Store success result
|
||||||
|
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// Handle any errors and update state
|
||||||
|
_registerState.value = Result.Error(exception)
|
||||||
|
_message.value = exception.localizedMessage ?: "Registration failed"
|
||||||
|
|
||||||
|
// Log the error for debugging
|
||||||
|
Log.e("RegisterViewModel", "User registration failed", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
|||||||
package com.alya.ecommerce_serang.ui.home
|
package com.alya.ecommerce_serang.ui.home
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.alya.ecommerce_serang.data.api.dto.Category
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||||
import com.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding
|
import com.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
class HomeCategoryAdapter(
|
class HomeCategoryAdapter(
|
||||||
private val categories:List<Category>,
|
private var categories:List<CategoryItem>,
|
||||||
//A lambda function that will be invoked when a category item is clicked.
|
//A lambda function that will be invoked when a category item is clicked.
|
||||||
private val onClick:(category:Category) -> Unit
|
private val onClick:(category:CategoryItem) -> Unit
|
||||||
): RecyclerView.Adapter<HomeCategoryAdapter.ViewHolder>() {
|
): RecyclerView.Adapter<HomeCategoryAdapter.ViewHolder>() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -18,12 +21,25 @@ class HomeCategoryAdapter(
|
|||||||
the RecyclerView.It binds the Category data to the corresponding views within the item layout.
|
the RecyclerView.It binds the Category data to the corresponding views within the item layout.
|
||||||
*/
|
*/
|
||||||
inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
|
inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
|
||||||
fun bind(category: Category) = with(binding){
|
fun bind(category: CategoryItem) = with(binding) {
|
||||||
Glide.with(root).load(category.image).into(image)
|
Log.d("CategoriesAdapter", "Binding category: ${category.name}, Image: ${category.image}")
|
||||||
name.text = category.title
|
|
||||||
root.setOnClickListener{
|
val fullImageUrl = if (category.image.startsWith("/")) {
|
||||||
onClick(category)
|
BASE_URL + category.image.removePrefix("/") // Append base URL if the path starts with "/"
|
||||||
|
} else {
|
||||||
|
category.image // Use as is if it's already a full URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("CategoriesAdapter", "Loading image: $fullImageUrl")
|
||||||
|
|
||||||
|
Glide.with(itemView.context)
|
||||||
|
.load(fullImageUrl) // Ensure full URL
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
.into(imageCategory)
|
||||||
|
|
||||||
|
name.text = category.name
|
||||||
|
|
||||||
|
root.setOnClickListener { onClick(category) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,4 +52,14 @@ class HomeCategoryAdapter(
|
|||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.bind(categories[position])
|
holder.bind(categories[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateData(newCategories: List<CategoryItem>) {
|
||||||
|
categories = newCategories.toList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLimitedCategory(newCategories: List<CategoryItem>){
|
||||||
|
val limitedCategories = newCategories.take(10)
|
||||||
|
updateData(limitedCategories)
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,21 +1,28 @@
|
|||||||
package com.alya.ecommerce_serang.ui.home
|
package com.alya.ecommerce_serang.ui.home
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
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.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
|
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
|
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import com.alya.ecommerce_serang.utils.setLightStatusBar
|
import com.alya.ecommerce_serang.utils.setLightStatusBar
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -24,30 +31,33 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
private var _binding: FragmentHomeBinding? = null
|
private var _binding: FragmentHomeBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private lateinit var viewModel: HomeViewModel
|
|
||||||
private var productAdapter: HorizontalProductAdapter? = null
|
private var productAdapter: HorizontalProductAdapter? = null
|
||||||
|
private var categoryAdapter: HomeCategoryAdapter? = null
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private val viewModel: HomeViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val productRepository = ProductRepository(apiService)
|
||||||
|
HomeViewModel(productRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return _binding!!.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val repository = ProductRepository(ApiConfig.getApiService())
|
|
||||||
viewModel = ViewModelProvider(
|
|
||||||
this,
|
|
||||||
// Pass a lambda that creates the ViewModel
|
|
||||||
BaseViewModelFactory {
|
|
||||||
HomeViewModel(repository)
|
|
||||||
}
|
|
||||||
)[HomeViewModel::class.java]
|
|
||||||
|
|
||||||
initUi()
|
initUi()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
observeData()
|
observeData()
|
||||||
@ -59,6 +69,11 @@ class HomeFragment : Fragment() {
|
|||||||
onClick = { product -> handleProductClick(product) }
|
onClick = { product -> handleProductClick(product) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
categoryAdapter = HomeCategoryAdapter(
|
||||||
|
categories = emptyList(),
|
||||||
|
onClick = { category -> handleCategoryProduct(category)}
|
||||||
|
)
|
||||||
|
|
||||||
binding.newProducts.apply {
|
binding.newProducts.apply {
|
||||||
adapter = productAdapter
|
adapter = productAdapter
|
||||||
layoutManager = LinearLayoutManager(
|
layoutManager = LinearLayoutManager(
|
||||||
@ -67,10 +82,20 @@ class HomeFragment : Fragment() {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.categories.apply {
|
||||||
|
adapter = categoryAdapter
|
||||||
|
layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeData() {
|
private fun observeData() {
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
viewModel.uiState.collect { state ->
|
viewModel.uiState.collect { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is HomeUiState.Loading -> {
|
is HomeUiState.Loading -> {
|
||||||
@ -82,7 +107,7 @@ class HomeFragment : Fragment() {
|
|||||||
binding.loading.root.isVisible = false
|
binding.loading.root.isVisible = false
|
||||||
binding.error.root.isVisible = false
|
binding.error.root.isVisible = false
|
||||||
binding.home.isVisible = true
|
binding.home.isVisible = true
|
||||||
productAdapter?.updateProducts(state.products)
|
productAdapter?.updateLimitedProducts(state.products) // Ensure productAdapter is initialized
|
||||||
}
|
}
|
||||||
is HomeUiState.Error -> {
|
is HomeUiState.Error -> {
|
||||||
binding.loading.root.isVisible = false
|
binding.loading.root.isVisible = false
|
||||||
@ -98,6 +123,18 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
viewModel.categories.collect { categories ->
|
||||||
|
Log.d("Categories", "Updated Categories: $categories")
|
||||||
|
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
|
||||||
|
categoryAdapter?.updateLimitedCategory(categories)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun initUi() {
|
private fun initUi() {
|
||||||
// For LightStatusBar
|
// For LightStatusBar
|
||||||
setLightStatusBar()
|
setLightStatusBar()
|
||||||
@ -124,22 +161,23 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
|
|
||||||
private fun handleProductClick(product: ProductsItem) {
|
private fun handleProductClick(product: ProductsItem) {
|
||||||
// Navigate to product detail
|
val intent = Intent(requireContext(), DetailProductActivity::class.java)
|
||||||
// findNavController().navigate(
|
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
|
||||||
// HomeFragmentDirections.actionHomeToDetail(product.id)
|
startActivity(intent)
|
||||||
// )
|
}
|
||||||
|
|
||||||
|
private fun handleCategoryProduct(category: CategoryItem) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
productAdapter = null
|
||||||
|
categoryAdapter = null
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLoading(isLoading: Boolean) {
|
private fun showLoading(isLoading: Boolean) {
|
||||||
if (isLoading) {
|
binding.progressBar.isVisible = isLoading
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,12 @@
|
|||||||
package com.alya.ecommerce_serang.ui.home
|
package com.alya.ecommerce_serang.ui.home
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
|
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
|
||||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -18,37 +17,56 @@ class HomeViewModel (
|
|||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
|
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
|
||||||
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
|
||||||
val home = MutableLiveData<AllProductResponse?>(null)
|
|
||||||
constructor() : this(ProductRepository(ApiConfig.getApiService()))
|
|
||||||
|
|
||||||
|
private val _categories = MutableStateFlow<List<CategoryItem>>(emptyList())
|
||||||
|
val categories: StateFlow<List<CategoryItem>> = _categories.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadProducts()
|
loadProducts()
|
||||||
|
loadCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadProducts() {
|
private fun loadProducts() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.value = HomeUiState.Loading
|
_uiState.value = HomeUiState.Loading
|
||||||
productRepository.getAllProducts()
|
when (val result = productRepository.getAllProducts()) {
|
||||||
.onSuccess { products ->
|
is Result.Success -> _uiState.value = HomeUiState.Success(result.data)
|
||||||
_uiState.value = HomeUiState.Success(products)
|
is Result.Error -> _uiState.value = HomeUiState.Error(result.exception.message ?: "Unknown error")
|
||||||
}
|
is Result.Loading -> {}
|
||||||
.onFailure { error ->
|
|
||||||
_uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
|
|
||||||
Log.e("ProductViewModel", "Products fetch failed", error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadCategories() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = productRepository.getAllCategories()) {
|
||||||
|
is Result.Success -> _categories.value = result.data
|
||||||
|
is Result.Error -> Log.e("HomeViewModel", "Failed to fetch categories", result.exception)
|
||||||
|
is Result.Loading -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun retry() {
|
fun retry() {
|
||||||
loadProducts()
|
loadProducts()
|
||||||
|
loadCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun toggleWishlist(product: Product) = viewModelScope.launch {
|
// private fun fetchUserData() {
|
||||||
|
// viewModelScope.launch {
|
||||||
// try {
|
// try {
|
||||||
// productRepository.toggleWishlist(product.id,product.wishlist)
|
// val response = apiService.getProtectedData() // Example API request
|
||||||
|
// if (response.isSuccessful) {
|
||||||
|
// val data = response.body()
|
||||||
|
// Log.d("HomeFragment", "User Data: $data")
|
||||||
|
// // Update UI with data
|
||||||
|
// } else {
|
||||||
|
// Log.e("HomeFragment", "Error: ${response.message()}")
|
||||||
|
// }
|
||||||
// } catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
//
|
// Log.e("HomeFragment", "Exception: ${e.message}")
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package com.alya.ecommerce_serang.ui.home
|
package com.alya.ecommerce_serang.ui.home
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||||
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
|
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
@ -16,17 +20,24 @@ class HorizontalProductAdapter(
|
|||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(product: ProductsItem) = with(binding) {
|
fun bind(product: ProductsItem) = with(binding) {
|
||||||
|
|
||||||
|
val fullImageUrl = if (product.image.startsWith("/")) {
|
||||||
|
BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/"
|
||||||
|
} else {
|
||||||
|
product.image // Use as is if it's already a full URL
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
|
||||||
|
|
||||||
itemName.text = product.name
|
itemName.text = product.name
|
||||||
itemPrice.text = product.price
|
itemPrice.text = product.price
|
||||||
rating.text = product.rating
|
rating.text = product.rating
|
||||||
// productSold.text = "${product.totalSold} sold"
|
|
||||||
|
|
||||||
// Load image using Glide
|
// Load image using Glide
|
||||||
Glide.with(itemView)
|
Glide.with(itemView)
|
||||||
// .load("${BuildConfig.BASE_URL}/product/${product.image}")
|
.load(fullImageUrl)
|
||||||
// .load("${BuildConfig.BASE_URL}/${product.image}")
|
.placeholder(R.drawable.placeholder_image)
|
||||||
.load(product.image)
|
.into(imageProduct)
|
||||||
.into(image)
|
|
||||||
|
|
||||||
root.setOnClickListener { onClick(product) }
|
root.setOnClickListener { onClick(product) }
|
||||||
}
|
}
|
||||||
@ -46,7 +57,32 @@ class HorizontalProductAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateProducts(newProducts: List<ProductsItem>) {
|
fun updateProducts(newProducts: List<ProductsItem>) {
|
||||||
|
val diffCallback = ProductDiffCallback(products, newProducts)
|
||||||
|
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||||
products = newProducts
|
products = newProducts
|
||||||
notifyDataSetChanged()
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLimitedProducts(newProducts: List<ProductsItem>) {
|
||||||
|
val diffCallback = ProductDiffCallback(products, newProducts)
|
||||||
|
val limitedProducts = newProducts.take(10) // Limit to 10 items
|
||||||
|
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||||
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
updateProducts(limitedProducts)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProductDiffCallback(
|
||||||
|
private val oldList: List<ProductsItem>,
|
||||||
|
private val newList: List<ProductsItem>
|
||||||
|
) : DiffUtil.Callback() {
|
||||||
|
|
||||||
|
override fun getOldListSize() = oldList.size
|
||||||
|
override fun getNewListSize() = newList.size
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||||
|
oldList[oldItemPosition].id == newList[newItemPosition].id // Compare unique IDs
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||||
|
oldList[oldItemPosition] == newList[newItemPosition] // Compare entire object
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.product
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
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.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.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
|
class DetailProductActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityDetailProductBinding
|
||||||
|
private lateinit var apiService: ApiService
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private var productAdapter: HorizontalProductAdapter? = null
|
||||||
|
private var reviewsAdapter: ReviewsAdapter? = null
|
||||||
|
|
||||||
|
private val viewModel: ProductViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val productRepository = ProductRepository(apiService)
|
||||||
|
ProductViewModel(productRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityDetailProductBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
|
||||||
|
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")
|
||||||
|
finish() // Close activity if no valid ID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadProductDetail(productId)
|
||||||
|
viewModel.loadReviews(productId)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observeProductDetail()
|
||||||
|
observeProductReviews()
|
||||||
|
}
|
||||||
|
private fun observeProductDetail() {
|
||||||
|
viewModel.productDetail.observe(this) { product ->
|
||||||
|
product?.let { updateUI(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeProductReviews() {
|
||||||
|
viewModel.reviewProduct.observe(this) { reviews ->
|
||||||
|
setupRecyclerViewReviewsProduct(reviews)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(product: Product){
|
||||||
|
binding.tvProductName.text = product.productName
|
||||||
|
binding.tvPrice.text = product.price
|
||||||
|
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 -> {
|
||||||
|
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||||
|
}
|
||||||
|
else -> R.drawable.placeholder_image // Default image for null
|
||||||
|
}
|
||||||
|
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
|
||||||
|
|
||||||
|
Glide.with(this)
|
||||||
|
.load(fullImageUrl)
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
.into(binding.ivProductImage)
|
||||||
|
|
||||||
|
setupRecyclerViewOtherProducts()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAllReviewsClick(productId: Int) {
|
||||||
|
val intent = Intent(this, ReviewProductActivity::class.java)
|
||||||
|
intent.putExtra("PRODUCT_ID", productId) // Pass product ID
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerViewOtherProducts(){
|
||||||
|
productAdapter = HorizontalProductAdapter(
|
||||||
|
products = emptyList(),
|
||||||
|
onClick = { productsItem -> handleProductClick(productsItem) }
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.recyclerViewOtherProducts.apply {
|
||||||
|
adapter = productAdapter
|
||||||
|
layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerViewReviewsProduct(reviewList: List<ReviewsItem>){
|
||||||
|
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
|
||||||
|
|
||||||
|
reviewsAdapter = ReviewsAdapter(
|
||||||
|
reviewList = limitedReviewList
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.recyclerViewReviews.apply {
|
||||||
|
adapter = reviewsAdapter
|
||||||
|
layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleProductClick(product: ProductsItem) {
|
||||||
|
val intent = Intent(this, DetailProductActivity::class.java)
|
||||||
|
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
package com.alya.ecommerce_serang.ui.product
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.alya.ecommerce_serang.R
|
|
||||||
import com.alya.ecommerce_serang.data.api.dto.Product
|
|
||||||
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
|
|
||||||
class ProductViewHolder(private val binding: ItemProductHorizontalBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
fun bind(
|
|
||||||
product: Product,
|
|
||||||
onClick: (product: Product) -> Unit,
|
|
||||||
) = with(binding) {
|
|
||||||
Glide.with(root).load(product.image).into(image)
|
|
||||||
|
|
||||||
// discount.isVisible = product.discount != null
|
|
||||||
// product.discount?.let {
|
|
||||||
// val discount = (product.discount / product.price * 100).roundToInt()
|
|
||||||
// binding.discount.text =
|
|
||||||
// root.context.getString(R.string.fragment_item_product_discount, discount)
|
|
||||||
// }
|
|
||||||
|
|
||||||
itemName.text = product.title
|
|
||||||
rating.text = String.format("%.1f", product.rating)
|
|
||||||
|
|
||||||
// val current = product.price - (product.discount ?: 0.0)
|
|
||||||
val current = product.price
|
|
||||||
itemPrice.text = root.context.getString(R.string.item_price_txt, current)
|
|
||||||
|
|
||||||
root.setOnClickListener {
|
|
||||||
onClick(product)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.product
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
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.Store
|
||||||
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
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 _reviewProduct = MutableLiveData<List<ReviewsItem>>()
|
||||||
|
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
|
||||||
|
|
||||||
|
fun loadProductDetail(productId: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val result = repository.fetchProductDetail(productId)
|
||||||
|
_productDetail.value = result?.product
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadReviews(productId: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val reviews = repository.fetchProductReview(productId)
|
||||||
|
_reviewProduct.value = reviews ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun loadStoreDetail(storeId: Int){
|
||||||
|
// viewModelScope.launch {
|
||||||
|
// val storeResult = repository.fetchStoreDetail(storeId)
|
||||||
|
// _storeDetail.value = storeResult
|
||||||
|
// }
|
||||||
|
// }
|
@ -0,0 +1,79 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.product
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
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.ProductRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityReviewProductBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
|
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 {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val productRepository = ProductRepository(apiService)
|
||||||
|
ProductViewModel(productRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityReviewProductBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
|
||||||
|
val productId = intent.getIntExtra("PRODUCT_ID", -1) // Get the product ID
|
||||||
|
if (productId == -1) {
|
||||||
|
Log.e("ReviewProductActivity", "Invalid Product ID")
|
||||||
|
finish() // Close the activity if the product ID is invalid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setupRecyclerView()
|
||||||
|
viewModel.loadReviews(productId) // Fetch reviews using productId
|
||||||
|
|
||||||
|
observeReviews() // Observe review data
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeReviews() {
|
||||||
|
viewModel.reviewProduct.observe(this) { reviews ->
|
||||||
|
if (reviews.isNotEmpty()) {
|
||||||
|
reviewsAdapter?.setReviews(reviews)
|
||||||
|
binding.tvNoReviews.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.tvNoReviews.visibility = View.VISIBLE // Show "No Reviews" message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
reviewsAdapter = ReviewsAdapter(
|
||||||
|
reviewList = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.rvReviewsProduct.apply {
|
||||||
|
adapter = reviewsAdapter
|
||||||
|
layoutManager = LinearLayoutManager(
|
||||||
|
context,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.product
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
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 java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class ReviewsAdapter(
|
||||||
|
private var reviewList: List<ReviewsItem>
|
||||||
|
) : RecyclerView.Adapter<ReviewsAdapter.ReviewViewHolder>() {
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReviewViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_review, parent, false)
|
||||||
|
return ReviewViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ReviewViewHolder, position: Int) {
|
||||||
|
val review = reviewList[position]
|
||||||
|
|
||||||
|
with(holder) {
|
||||||
|
tvReviewerName.text = review.username
|
||||||
|
tvReviewRating.text = review.rating.toString()
|
||||||
|
tvReviewText.text = review.reviewText
|
||||||
|
tvDateReview.text = formatDate(review.reviewDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = reviewList.size
|
||||||
|
|
||||||
|
fun setReviews(reviews: List<ReviewsItem>) {
|
||||||
|
reviewList = reviews
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(dateString: String): String {
|
||||||
|
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
|
||||||
|
} catch (e: Exception) {
|
||||||
|
dateString // Return original if error occurs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val tvReviewRating: TextView = itemView.findViewById(R.id.tvReviewRating)
|
||||||
|
val tvReviewerName: TextView = itemView.findViewById(R.id.tvUsername)
|
||||||
|
val tvReviewText: TextView = itemView.findViewById(R.id.tvReviewText)
|
||||||
|
val tvDateReview: TextView = itemView.findViewById(R.id.date_review)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.profile
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
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
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityDetailProfileBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class DetailProfileActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityDetailProfileBinding
|
||||||
|
private lateinit var apiService: ApiService
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
private val viewModel: ProfileViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
ProfileViewModel(userRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityDetailProfileBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
|
||||||
|
viewModel.loadUserProfile()
|
||||||
|
|
||||||
|
viewModel.userProfile.observe(this){ user ->
|
||||||
|
user?.let { updateProfile(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.errorMessage.observe(this) { error ->
|
||||||
|
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProfile(user: UserProfile){
|
||||||
|
|
||||||
|
binding.tvNameUser.setText(user.name.toString())
|
||||||
|
binding.tvUsername.setText(user.username)
|
||||||
|
binding.tvEmailUser.setText(user.email)
|
||||||
|
binding.tvDateBirth.setText(formatDate(user.birthDate))
|
||||||
|
binding.tvNumberPhoneUser.setText(user.phone)
|
||||||
|
|
||||||
|
if (user.image != null && user.image is String) {
|
||||||
|
Glide.with(this)
|
||||||
|
.load(user.image)
|
||||||
|
.into(binding.profileImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(dateString: String): String {
|
||||||
|
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
|
||||||
|
} catch (e: Exception) {
|
||||||
|
dateString // Return original if error occurs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +1,95 @@
|
|||||||
package com.alya.ecommerce_serang.ui.profile
|
package com.alya.ecommerce_serang.ui.profile
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
import com.alya.ecommerce_serang.R
|
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.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.profile.mystore.TokoSayaActivity
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
class ProfileFragment : Fragment() {
|
class ProfileFragment : Fragment() {
|
||||||
|
|
||||||
companion object {
|
private var _binding: FragmentProfileBinding? = null
|
||||||
fun newInstance() = ProfileFragment()
|
private val binding get() = _binding!!
|
||||||
}
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
private val viewModel: ProfileViewModel by viewModels()
|
private val viewModel: ProfileViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
ProfileViewModel(userRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
|
||||||
// TODO: Use the ViewModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_profile, container, false)
|
_binding = FragmentProfileBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
observeUserProfile()
|
||||||
|
viewModel.loadUserProfile()
|
||||||
|
|
||||||
|
binding.cardBukaToko.setOnClickListener{
|
||||||
|
val intentBuka = Intent(requireContext(), TokoSayaActivity::class.java)
|
||||||
|
startActivity(intentBuka)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnDetailProfile.setOnClickListener{
|
||||||
|
val intentDetail = Intent(requireContext(), DetailProfileActivity::class.java)
|
||||||
|
startActivity(intentDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeUserProfile() {
|
||||||
|
viewModel.userProfile.observe(viewLifecycleOwner) { user ->
|
||||||
|
user?.let { updateUI(it) }
|
||||||
|
}
|
||||||
|
viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage ->
|
||||||
|
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(user: UserProfile) = with(binding){
|
||||||
|
val fullImageUrl = when (val img = user.image) {
|
||||||
|
is String -> {
|
||||||
|
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||||
|
}
|
||||||
|
else -> R.drawable.placeholder_image // Default image for null
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
|
||||||
|
|
||||||
|
tvName.text = user.name.toString()
|
||||||
|
tvUsername.text = user.username.toString()
|
||||||
|
|
||||||
|
// Load image using Glide
|
||||||
|
Glide.with(requireContext())
|
||||||
|
.load(fullImageUrl)
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
.into(profileImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,28 @@
|
|||||||
package com.alya.ecommerce_serang.ui.profile
|
package com.alya.ecommerce_serang.ui.profile
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ProfileViewModel : ViewModel() {
|
class ProfileViewModel(private val userRepository: UserRepository) : ViewModel() {
|
||||||
// TODO: Implement the ViewModel
|
private val _userProfile = MutableLiveData<UserProfile?>()
|
||||||
|
val userProfile: LiveData<UserProfile?> = _userProfile
|
||||||
|
|
||||||
|
private val _errorMessage = MutableLiveData<String>()
|
||||||
|
val errorMessage : LiveData<String> = _errorMessage
|
||||||
|
|
||||||
|
fun loadUserProfile(){
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = userRepository.fetchUserProfile()){
|
||||||
|
is Result.Success -> _userProfile.postValue(result.data)
|
||||||
|
is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error")
|
||||||
|
is Result.Loading -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.alya.ecommerce_serang.utils
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class AuthInterceptor(private val sessionManager: SessionManager) : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val token = sessionManager.getToken()
|
||||||
|
Log.d("AuthInterceptor", "Token: $token")
|
||||||
|
val request = if (!token.isNullOrEmpty()) {
|
||||||
|
chain.request().newBuilder()
|
||||||
|
.addHeader("Authorization", "Bearer $token")
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
chain.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
package com.alya.ecommerce_serang.utils
|
package com.alya.ecommerce_serang.utils
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.alya.ecommerce_serang.data.api.dto.Category
|
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ProductQuery (
|
data class ProductQuery (
|
||||||
val category: Category? = null,
|
val category: CategoryItem,
|
||||||
val search:String? = null,
|
val search:String? = null,
|
||||||
val range:Pair<Float,Float> = 0f to 10000f,
|
val range:Pair<Float,Float> = 0f to 10000f,
|
||||||
val rating:Int? = null,
|
val rating:Int? = null,
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.alya.ecommerce_serang.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
class SessionManager(context: Context) {
|
||||||
|
private var sharedPreferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREFS_NAME = "app_prefs"
|
||||||
|
private const val USER_TOKEN = "user_token"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveToken(token: String) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.putString(USER_TOKEN, token)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getToken(): String? {
|
||||||
|
val token = sharedPreferences.getString(USER_TOKEN, null)
|
||||||
|
Log.d("SessionManager", "Retrieved token: $token")
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearToken() {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.remove(USER_TOKEN)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
}
|
5
app/src/main/res/drawable/baseline_add_24.xml
Normal file
5
app/src/main/res/drawable/baseline_add_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/circle_background.xml
Normal file
9
app/src/main/res/drawable/circle_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="oval">
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
<stroke
|
||||||
|
android:width="3dp"
|
||||||
|
android:color="@color/blue_500" />
|
||||||
|
<size android:width="40dp" android:height="40dp"/>
|
||||||
|
</shape>
|
5
app/src/main/res/drawable/ic_address.xml
Normal file
5
app/src/main/res/drawable/ic_address.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="20dp" android:viewportHeight="15" android:viewportWidth="18" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@color/black" android:pathData="M9,0.5L0.667,8H3.167V14.667H14.833V8H17.333L9,0.5ZM9,5.292C9.497,5.292 9.974,5.489 10.326,5.841C10.677,6.192 10.875,6.669 10.875,7.167C10.875,7.664 10.677,8.141 10.326,8.492C9.974,8.844 9.497,9.042 9,9.042C8.503,9.042 8.026,8.844 7.674,8.492C7.323,8.141 7.125,7.664 7.125,7.167C7.125,6.669 7.323,6.192 7.674,5.841C8.026,5.489 8.503,5.292 9,5.292ZM9,10.5C10.25,10.5 12.75,11.125 12.75,12.375V13H5.25V12.375C5.25,11.125 7.75,10.5 9,10.5Z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_arrow_right.xml
Normal file
5
app/src/main/res/drawable/ic_arrow_right.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/black" android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_back_24.xml
Normal file
5
app/src/main/res/drawable/ic_back_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||||
|
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_delivery.xml
Normal file
9
app/src/main/res/drawable/ic_delivery.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="17dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="17">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0.5V2H14.25V13.25H9.633C9.299,11.961 8.139,11 6.75,11C5.361,11 4.201,11.961 3.867,13.25H3V9.5H1.5V14.75H3.867C4.201,16.039 5.361,17 6.75,17C8.139,17 9.299,16.039 9.633,14.75H15.867C16.201,16.039 17.361,17 18.75,17C20.139,17 21.299,16.039 21.633,14.75H24V8.633L23.953,8.515L22.453,4.015L22.29,3.5H15.75V0.5H0ZM0.75,3.5V5H7.5V3.5H0.75ZM15.75,5H21.211L22.5,8.844V13.25H21.633C21.299,11.961 20.139,11 18.75,11C17.361,11 16.201,11.961 15.867,13.25H15.75V5ZM1.5,6.5V8H6V6.5H1.5ZM6.75,12.5C7.588,12.5 8.25,13.162 8.25,14C8.25,14.838 7.588,15.5 6.75,15.5C5.912,15.5 5.25,14.838 5.25,14C5.25,13.162 5.912,12.5 6.75,12.5ZM18.75,12.5C19.588,12.5 20.25,13.162 20.25,14C20.25,14.838 19.588,15.5 18.75,15.5C17.912,15.5 17.25,14.838 17.25,14C17.25,13.162 17.912,12.5 18.75,12.5Z"
|
||||||
|
android:fillColor="@color/black"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_delivery_24.xml
Normal file
9
app/src/main/res/drawable/ic_delivery_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<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="M19,7c0,-1.1 -0.9,-2 -2,-2h-3v2h3v2.65L13.52,14H10V9H6c-2.21,0 -4,1.79 -4,4v3h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h4.48L19,10.35V7zM4,14v-1c0,-1.1 0.9,-2 2,-2h2v3H4zM7,17c-0.55,0 -1,-0.45 -1,-1h2C8,16.55 7.55,17 7,17z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M5,6h5v2h-5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,13c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,13 19,13zM19,17c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1s1,0.45 1,1C20,16.55 19.55,17 19,17z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_edit.xml
Normal file
5
app/src/main/res/drawable/ic_edit.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="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/ic_package.xml
Normal file
5
app/src/main/res/drawable/ic_package.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="20" android:viewportWidth="18" android:width="21.6dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@color/black" android:pathData="M18,14.5C18,14.88 17.79,15.21 17.47,15.38L9.57,19.82C9.41,19.94 9.21,20 9,20C8.79,20 8.59,19.94 8.43,19.82L0.53,15.38C0.37,15.296 0.235,15.169 0.142,15.014C0.048,14.859 -0.001,14.681 0,14.5V5.5C0,5.12 0.21,4.79 0.53,4.62L8.43,0.18C8.59,0.06 8.79,0 9,0C9.21,0 9.41,0.06 9.57,0.18L17.47,4.62C17.79,4.79 18,5.12 18,5.5V14.5ZM9,2.15L7.11,3.22L13,6.61L14.96,5.5L9,2.15ZM3.04,5.5L9,8.85L10.96,7.75L5.08,4.35L3.04,5.5ZM2,13.91L8,17.29V10.58L2,7.21V13.91ZM16,13.91V7.21L10,10.58V17.29L16,13.91Z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_wallet.xml
Normal file
5
app/src/main/res/drawable/ic_wallet.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="M18,4H6C3.79,4 2,5.79 2,8v8c0,2.21 1.79,4 4,4h12c2.21,0 4,-1.79 4,-4V8C22,5.79 20.21,4 18,4zM16.14,13.77c-0.24,0.2 -0.57,0.28 -0.88,0.2L4.15,11.25C4.45,10.52 5.16,10 6,10h12c0.67,0 1.26,0.34 1.63,0.84L16.14,13.77zM6,6h12c1.1,0 2,0.9 2,2v0.55C19.41,8.21 18.73,8 18,8H6C5.27,8 4.59,8.21 4,8.55V8C4,6.9 4.9,6 6,6z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/outline_store_24.xml
Normal file
5
app/src/main/res/drawable/outline_store_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/black" android:pathData="M18.36,9l0.6,3L5.04,12l0.6,-3h12.72M20,4L4,4v2h16L20,4zM20,7L4,7l-1,5v2h1v6h10v-6h4v6h2v-6h1v-2l-1,-5zM6,18v-4h6v4L6,18z"/>
|
||||||
|
|
||||||
|
</vector>
|
469
app/src/main/res/layout/activity_detail_product.xml
Normal file
469
app/src/main/res/layout/activity_detail_product.xml
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/white"
|
||||||
|
tools:context=".ui.product.DetailProductActivity">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/searchContainer"
|
||||||
|
layout="@layout/view_search"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<!-- Product Image -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivProductImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:contentDescription="@string/product_image"
|
||||||
|
tools:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
|
<!-- Product Price and Name -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPrice"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Rp65.000" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Keripik Ikan Tenggiri" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/soft_gray"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="@string/item_sold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/star_product"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:contentDescription="@string/rating"
|
||||||
|
android:src="@drawable/baseline_star_24"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRating"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="4.5" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<!-- Buyer Reviews Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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: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="@string/ulasan_pembeli"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvViewAllReviews"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/lihat_semua"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- RecyclerView for Reviews -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerViewReviews"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:itemCount="1"
|
||||||
|
tools:listitem="@layout/item_review" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<!-- Product Details Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/detail_produk"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/berat_produk"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvWeight"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="200 gram" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/stock_product"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStock"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="100 buah" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/kategori"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCategory"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Makanan Ringan" />
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/deskripsi_produk"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDescription"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Terbuat dari tepung dan ikan tenggiri asli Serang Banten. Tahan selama 25 hari." />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Product Description Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<!-- Seller Info Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/ivSellerImage"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:contentDescription="@string/seller_image"
|
||||||
|
tools:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSellerName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="SnackEnak" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSellerLocation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Jakarta Selatan" />
|
||||||
|
|
||||||
|
<RatingBar
|
||||||
|
android:id="@+id/ratingBarSeller"
|
||||||
|
style="?android:attr/ratingBarStyleSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:isIndicator="true"
|
||||||
|
android:numStars="5"
|
||||||
|
android:rating="5"
|
||||||
|
android:stepSize="0.1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSellerRating"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="5.0" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<!-- Other Products Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
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: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="@string/produk_lainnya"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvViewAllProducts"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/lihat_semua"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- RecyclerView for Other Products -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerViewOtherProducts"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:itemCount="3"
|
||||||
|
tools:listitem="@layout/item_related_product" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Bottom spacing -->
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom Action Bar -->
|
||||||
|
<com.google.android.material.bottomappbar.BottomAppBar
|
||||||
|
android:id="@+id/bottomAppBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:backgroundTint="@color/white"
|
||||||
|
app:contentInsetStart="0dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnChat"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
app:cornerRadius="4dp"
|
||||||
|
app:icon="@drawable/baseline_chat_24"
|
||||||
|
android:text="chat"
|
||||||
|
app:iconGravity="end"
|
||||||
|
app:iconTint="@color/blue_500" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnAddToCart"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:text="@string/add_to_cart"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
app:cornerRadius="4dp"
|
||||||
|
app:icon="@drawable/baseline_add_24"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconTint="@color/blue_500"
|
||||||
|
app:strokeColor="@color/blue_500" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnBuyNow"
|
||||||
|
style="@style/Widget.MaterialComponents.Button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:backgroundTint="@color/blue_500"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:text="@string/beli_sekarang"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:cornerRadius="4dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
184
app/src/main/res/layout/activity_detail_profile.xml
Normal file
184
app/src/main/res/layout/activity_detail_profile.xml
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<?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="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
tools:context=".ui.profile.DetailProfileActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/top_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_back"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_back_24"
|
||||||
|
android:contentDescription="back"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_profile_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Profil"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/card_profile"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_title"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- Profile Image -->
|
||||||
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/profile_image"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:src="@drawable/baseline_account_circle_24"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<!-- Edit Icon -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/edit_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/ic_edit"
|
||||||
|
android:background="@drawable/circle_background"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/profile_image"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/profile_image"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
app:tint="@color/blue_500" />
|
||||||
|
|
||||||
|
<!-- Edit Profile Button -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_ubah_profil"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ubah Profil"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/profile_image"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_nama"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/card_profile">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/tv_name_user"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Nama"
|
||||||
|
android:text="@string/users_name"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_nama">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/tv_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Username"
|
||||||
|
android:text="@string/username_profile"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_username">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/tv_email_user"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Email"
|
||||||
|
android:text="@string/users_email"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_nomor_handphone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_email">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/tv_number_phone_user"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Nomor Handphone"
|
||||||
|
android:text="@string/phone_number"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_tanggal_lahir"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_nomor_handphone">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/tv_date_birth"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Tanggal Lahir"
|
||||||
|
android:text="@string/date_birth"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
104
app/src/main/res/layout/activity_login.xml
Normal file
104
app/src/main/res/layout/activity_login.xml
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
tools:context=".ui.auth.LoginActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/login_email"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_login_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_login_email"
|
||||||
|
android:inputType="textEmailAddress"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/password"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_login_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_login_password"
|
||||||
|
android:inputType="textPassword"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_forgetPassword"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/forget_password"
|
||||||
|
android:textColor="@android:color/holo_red_light"
|
||||||
|
android:textAlignment="textEnd"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_login"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login"
|
||||||
|
app:cornerRadius="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/no_account"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/signup"
|
||||||
|
android:textColor="@color/blue1"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
250
app/src/main/res/layout/activity_register.xml
Normal file
250
app/src/main/res/layout/activity_register.xml
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView 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/white"
|
||||||
|
tools:context=".ui.auth.RegisterActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Buat Akun"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/email"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_email"
|
||||||
|
android:inputType="textEmailAddress"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/username"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_username"
|
||||||
|
android:inputType="text"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/full_name"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_fullname"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_fullname"
|
||||||
|
android:inputType="text"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/password"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_password"
|
||||||
|
android:inputType="textPassword"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/confirm_password"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_confirm_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_confirmation_password"
|
||||||
|
android:inputType="textPassword"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/birth_date"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/et_birth_date"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_birth_date"
|
||||||
|
android:inputType="date"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/gender"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/et_gender"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_gender"
|
||||||
|
android:inputType="textFilter"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:text="@string/number_phone"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_number_phone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/hint_number_phone"
|
||||||
|
android:inputType="phone"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_signup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/signup"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:cornerRadius="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/no_account"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_login_alt"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login"
|
||||||
|
android:textColor="@color/blue1"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBarOtp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
|
<!-- ProgressBar for Registration -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBarRegister"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
63
app/src/main/res/layout/activity_review_product.xml
Normal file
63
app/src/main/res/layout/activity_review_product.xml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?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.product.ReviewProductActivity">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/topAppBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar"
|
||||||
|
android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_back"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_back_24"
|
||||||
|
android:contentDescription="back"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_review_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ulasan Produk"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_reviews_product"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/topAppBar"
|
||||||
|
tools:itemCount="5"
|
||||||
|
tools:listitem="@layout/item_review" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_no_reviews"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="Tidak Ada Ulasan"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/rv_reviews_product"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
35
app/src/main/res/layout/dialog_otp.xml
Normal file
35
app/src/main/res/layout/dialog_otp.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/enter_otp"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etOtp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Enter OTP"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="8dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSubmit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Submit"
|
||||||
|
android:layout_marginTop="12dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
90
app/src/main/res/layout/fragment_detail_profile.xml
Normal file
90
app/src/main/res/layout/fragment_detail_profile.xml
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".ui.profile.DetailProfileFragment">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_nama"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/card_profile">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Nama"
|
||||||
|
android:text="@string/users_name"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_nama">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Username"
|
||||||
|
android:text="@string/username_profile"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_username">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Email"
|
||||||
|
android:text="@string/users_email"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_nomor_handphone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_email">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Nomor Handphone"
|
||||||
|
android:text="@string/phone_number"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_tanggal_lahir"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_nomor_handphone">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Tanggal Lahir"
|
||||||
|
android:text="@string/date_birth"
|
||||||
|
android:enabled="false"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -20,7 +20,7 @@
|
|||||||
layout="@layout/view_search"
|
layout="@layout/view_search"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="19dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
@ -39,12 +39,11 @@
|
|||||||
android:id="@+id/banners"
|
android:id="@+id/banners"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="132dp"
|
android:layout_height="132dp"
|
||||||
android:layout_marginTop="23dp"
|
android:layout_marginTop="4dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_constraintTop_toBottomOf="@id/searchContainer"
|
app:layout_constraintTop_toBottomOf="@id/searchContainer"
|
||||||
tools:layout_editor_absoluteX="16dp" />
|
tools:layout_editor_absoluteX="16dp" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/categoriesText"
|
android:id="@+id/categoriesText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -92,7 +91,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:text="@string/new_products_text"
|
android:text="@string/sold_product_text"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="22sp"
|
android:textSize="22sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -1,13 +1,331 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/white"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".ui.profile.ProfileFragment">
|
tools:context=".ui.profile.ProfileFragment">
|
||||||
|
|
||||||
<TextView
|
<!-- Profile Header -->
|
||||||
android:layout_width="match_parent"
|
<ImageView
|
||||||
android:layout_height="match_parent"
|
android:id="@+id/profileImage"
|
||||||
android:text="Hello" />
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/outline_account_circle_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</FrameLayout>
|
<TextView
|
||||||
|
android:id="@+id/tvName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="Gracia Hotmauli"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/profileImage"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/profileImage" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUsername"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="\@gracia34"
|
||||||
|
android:textColor="#757575"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tvName"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvName" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDetailProfile"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="Detail Profil"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/profileImage" />
|
||||||
|
|
||||||
|
<!-- Store Button -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/cardBukaToko"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/profileImage">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_marginHorizontal="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvBukaToko"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableStart="@drawable/outline_store_24"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:text="@string/open_store"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/ivStoreArrow"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/ivStoreArrow"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_arrow_right"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvBukaToko"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvBukaToko" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/cardPesanan"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/cardBukaToko">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="4dp"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPesananSaya"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:text="Pesanan Saya"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/tvLihatRiwayat"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:fontFamily="@font/dmsans_light"
|
||||||
|
android:text="Lihat Riwayat Pesanan"
|
||||||
|
android:textColor="#2196F3"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvPesananSaya" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutOrderStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:weightSum="3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvPesananSaya">
|
||||||
|
|
||||||
|
<!-- Status items (keeping LinearLayout for simplicity of equal width distribution) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ly_waiting"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_waiting"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/ic_wallet" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/waiting_payment" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ly_packages"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_packages"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/ic_package" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/packages" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ly_delivery"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_delivery"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/ic_delivery" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/delivery" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
<!-- Orders Section Header -->
|
||||||
|
|
||||||
|
<!-- Account Settings Header -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPengaturanAkun"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="Pengaturan Akun"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/cardPesanan" />
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivAddress"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@drawable/ic_address"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvAddress"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvAddress" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAddress"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="Alamat"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/ivAddressArrow"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/ivAddress"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvPengaturanAkun" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivAddressArrow"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:src="@drawable/ic_arrow_right"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvAddress"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvAddress" />
|
||||||
|
|
||||||
|
<!-- About -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivAbout"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvAbout"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvAbout" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAbout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="Tentang"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/ivAboutArrow"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/ivAbout"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvAddress" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivAboutArrow"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:src="@drawable/ic_arrow_right"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvAbout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvAbout" />
|
||||||
|
|
||||||
|
<!-- Logout -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivLogout"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvLogout"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvLogout" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLogout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="Keluar"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/ivLogoutArrow"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/ivLogout"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvAbout" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivLogoutArrow"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:src="@drawable/ic_arrow_right"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvLogout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvLogout" />
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
android:id="@+id/bottomNavigation"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -17,7 +17,7 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/image"
|
android:id="@+id/image_category"
|
||||||
android:src="@drawable/makanan_ringan"
|
android:src="@drawable/makanan_ringan"
|
||||||
android:scaleType="centerCrop" />
|
android:scaleType="centerCrop" />
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
app:strokeWidth="1dp">
|
app:strokeWidth="1dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image"
|
android:id="@+id/image_product"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
|
72
app/src/main/res/layout/item_related_product.xml
Normal file
72
app/src/main/res/layout/item_related_product.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="160dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<!-- Product Image -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imgRelatedProduct"
|
||||||
|
android:layout_width="140dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
|
<!-- Product Name -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRelatedProductName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Keripik Kulit Sapi"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="fill"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@color/black" />
|
||||||
|
|
||||||
|
<!-- Price -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRelatedProductPrice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rp45.000"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="fill"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/black" />
|
||||||
|
|
||||||
|
<!-- Rating -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_marginTop="4dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@drawable/baseline_star_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRelatedProductRating"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="5.0"
|
||||||
|
android:textColor="@color/black" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
72
app/src/main/res/layout/item_review.xml
Normal file
72
app/src/main/res/layout/item_review.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<!-- Review Content -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="12dp">
|
||||||
|
|
||||||
|
<!-- Username & Rating -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUsername"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="budi21"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/black" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:src="@drawable/baseline_star_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvReviewRating"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="5.0"
|
||||||
|
android:textColor="@color/black" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date_review"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/dmsans_light"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="@color/soft_gray"
|
||||||
|
android:text="22-03-2025"/>
|
||||||
|
<!-- Review Text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvReviewText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Enak sekali dan renyah. Sudah dua kali pesan. Terima kasih."
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
@ -12,7 +12,7 @@
|
|||||||
android:hint="@string/fragment_home_search"
|
android:hint="@string/fragment_home_search"
|
||||||
android:textColor="@color/soft_gray"
|
android:textColor="@color/soft_gray"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="8dp"
|
||||||
android:drawablePadding="8dp"
|
android:drawablePadding="8dp"
|
||||||
android:paddingHorizontal="25dp"
|
android:paddingHorizontal="25dp"
|
||||||
android:imeOptions="actionSearch"
|
android:imeOptions="actionSearch"
|
||||||
@ -37,7 +37,7 @@
|
|||||||
android:id="@+id/btn_cart"
|
android:id="@+id/btn_cart"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:backgroundTint="@color/white"
|
android:backgroundTint="@color/white"
|
||||||
android:src="@drawable/outline_shopping_cart_24"
|
android:src="@drawable/outline_shopping_cart_24"
|
||||||
|
@ -20,4 +20,14 @@
|
|||||||
android:name="com.alya.ecommerce_serang.ui.chat.ChatFragment"
|
android:name="com.alya.ecommerce_serang.ui.chat.ChatFragment"
|
||||||
android:label="fragment_chat"
|
android:label="fragment_chat"
|
||||||
tools:layout="@layout/fragment_chat" />
|
tools:layout="@layout/fragment_chat" />
|
||||||
|
<activity
|
||||||
|
android:id="@+id/registerActivity"
|
||||||
|
android:name="com.alya.ecommerce_serang.ui.auth.RegisterActivity"
|
||||||
|
android:label="activity_register"
|
||||||
|
tools:layout="@layout/activity_register" />
|
||||||
|
<activity
|
||||||
|
android:id="@+id/loginActivity"
|
||||||
|
android:name="com.alya.ecommerce_serang.ui.auth.LoginActivity"
|
||||||
|
android:label="activity_login"
|
||||||
|
tools:layout="@layout/activity_login" />
|
||||||
</navigation>
|
</navigation>
|
@ -13,4 +13,52 @@
|
|||||||
<string name="error_loading">Terdapat error...</string>
|
<string name="error_loading">Terdapat error...</string>
|
||||||
<string name="new_products_text">Produk Terbaru</string>
|
<string name="new_products_text">Produk Terbaru</string>
|
||||||
<string name="rating">4.5</string>
|
<string name="rating">4.5</string>
|
||||||
|
<string name="open_store">Buka Toko</string>
|
||||||
|
<string name="delivery">Dikirim</string>
|
||||||
|
<string name="packages">Dikemas</string>
|
||||||
|
<string name="waiting_payment">Belum Dibayar</string>
|
||||||
|
<string name="users_email">youremail@gmail.com</string>
|
||||||
|
<string name="phone_number">123456789123</string>
|
||||||
|
<string name="date_birth">24 Oktober 2024</string>
|
||||||
|
<string name="username_profile">User123</string>
|
||||||
|
<string name="users_name">User</string>
|
||||||
|
<string name="hint_confirmation_password">Konfirmasi Kata Sandi</string>
|
||||||
|
<string name="email">Email</string>
|
||||||
|
<string name="hint_email">Masukkan Email</string>
|
||||||
|
<string name="username">Nama Pengguna</string>
|
||||||
|
<string name="hint_username">Masukkan Nama Pengguna</string>
|
||||||
|
<string name="full_name">Nama Lengkap</string>
|
||||||
|
<string name="hint_fullname">Masukkan Nama Lengkap</string>
|
||||||
|
<string name="password">Kata Sandi</string>
|
||||||
|
<string name="hint_password">Masukkan Kata Sandi Baru</string>
|
||||||
|
<string name="confirm_password">Konfirmasi Kata Sandi</string>
|
||||||
|
<string name="birth_date">Tanggal Lahir</string>
|
||||||
|
<string name="hint_birth_date">Pilih Tanggal Lahir</string>
|
||||||
|
<string name="gender">Jenis Kelamin</string>
|
||||||
|
<string name="hint_gender">Pilih Jenis Kelamin</string>
|
||||||
|
<string name="number_phone">Nomor Telepon</string>
|
||||||
|
<string name="hint_number_phone">Masukkan Nomor Telepon</string>
|
||||||
|
<string name="no_account">Belum punya akun?</string>
|
||||||
|
<string name="login">Masuk</string>
|
||||||
|
<string name="signup">Daftar</string>
|
||||||
|
<string name="login_email">Email atau Nomor Telepon</string>
|
||||||
|
<string name="hint_login_email">Email atau No. Handphone</string>
|
||||||
|
<string name="hint_login_password">Kata Sandi</string>
|
||||||
|
<string name="forget_password">Lupa Kata Sandi?</string>
|
||||||
|
<string name="enter_otp">Masukkan Kode OTP</string>
|
||||||
|
<string name="sold_product_text">Produk Terlaris</string>
|
||||||
|
<string name="item_sold">Terjual 10 buah</string>
|
||||||
|
<string name="product_image">produc image</string>
|
||||||
|
<string name="ulasan_pembeli">Ulasan Pembeli</string>
|
||||||
|
<string name="lihat_semua">Lihat Semua</string>
|
||||||
|
<string name="detail_produk">Detail Produk</string>
|
||||||
|
<string name="berat_produk">Berat</string>
|
||||||
|
<string name="stock_product">Stok</string>
|
||||||
|
<string name="deskripsi_produk">Deskripsi Produk</string>
|
||||||
|
<string name="kategori">Kategori</string>
|
||||||
|
<string name="seller_image">Seller Profile Picture</string>
|
||||||
|
<string name="produk_lainnya">Produk Lainnya</string>
|
||||||
|
<string name="add_to_cart">Keranjang</string>
|
||||||
|
<string name="beli_sekarang">Beli Sekarang</string>
|
||||||
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
</resources>
|
</resources>
|
10
app/src/main/res/values/styles.xml
Normal file
10
app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="ButtonStatusDelivery" parent="@android:style/Widget.Button">
|
||||||
|
<!-- <item name="android:background">@drawable/custom_button_background</item>-->
|
||||||
|
<!-- <item name="android:textColor">@color/custom_button_text_color</item>-->
|
||||||
|
<item name="android:textSize">16sp</item>
|
||||||
|
<item name="android:padding">12dp</item>
|
||||||
|
<!-- Add more style attributes as needed -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
Reference in New Issue
Block a user