Merge branch 'master' into gracia

This commit is contained in:
Gracia Hotmauli
2025-03-29 01:51:21 +07:00
committed by GitHub
76 changed files with 4194 additions and 257 deletions

549
.idea/other.xml generated Normal file
View 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>

View File

@ -23,7 +23,7 @@ android {
buildTypes {
release {
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
buildConfigField("String", "BASE_URL", "\"http://192.168.1.4:3000/\"")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
@ -31,7 +31,7 @@ android {
)
}
debug {
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
buildConfigField("String", "BASE_URL", "\"http://192.168.1.4:3000/\"")
}
}
compileOptions {

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
@ -15,7 +16,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31" >
tools:targetApi="31">
<activity
android:name=".ui.product.ReviewProductActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.SellsActivity"
android:exported="false" />
@ -41,14 +45,26 @@
android:name=".ui.profile.mystore.TokoSayaActivity"
android:exported="false" />
<activity
android:name=".ui.MainActivity"
android:exported="true" >
android:name=".ui.product.DetailProductActivity"
android:exported="false" />
<activity
android:name=".ui.auth.RegisterActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".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>
</manifest>

View File

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

View File

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

View File

@ -14,7 +14,7 @@ data class DetailProduct(
val inStock: Int,
val price: Double,
val rating: Double,
val related: List<Product>,
val related: List<ProductsItem>,
val reviews: Int,
val title: String,
@SerializedName("free_delivery")

View File

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

View File

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

View File

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

View File

@ -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
data class Product(
data class ProductsItem(
@field:SerializedName("store_id")
val storeId: Int,
@ -19,23 +20,20 @@ data class Product(
@field:SerializedName("weight")
val weight: Int,
@field:SerializedName("product_name")
val productName: String,
@field:SerializedName("is_pre_order")
val isPreOrder: Boolean,
@field:SerializedName("duration")
val duration: Any,
@field:SerializedName("category_id")
val categoryId: Int,
@field:SerializedName("price")
val price: String,
@field:SerializedName("product_id")
val productId: Int,
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("min_order")
val minOrder: Int,
@ -46,6 +44,6 @@ data class Product(
@field:SerializedName("stock")
val stock: Int,
@field:SerializedName("product_category")
val productCategory: String
@field:SerializedName("status")
val status: String
)

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.google.gson.annotations.SerializedName
data class AllProductResponse(
@ -11,47 +12,4 @@ data class AllProductResponse(
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
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ data class Product(
val storeId: Int,
@field:SerializedName("image")
val image: String,
val image: String? = null,
@field:SerializedName("rating")
val rating: String,
@ -35,7 +35,7 @@ data class Product(
val isPreOrder: Boolean,
@field:SerializedName("duration")
val duration: Any,
val duration: Any?,
@field:SerializedName("category_id")
val categoryId: Int,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,52 @@
package com.alya.ecommerce_serang.data.api.retrofit
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.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class ApiConfig {
companion object{
fun getApiService(): ApiService {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
companion object {
fun getApiService(tokenManager: SessionManager): ApiService {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val authInterceptor = AuthInterceptor(tokenManager)
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.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)
}
}

View File

@ -1,22 +1,67 @@
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.CategoryResponse
import com.alya.ecommerce_serang.data.api.response.DetailStoreProductResponse
import com.alya.ecommerce_serang.data.api.response.LoginResponse
import com.alya.ecommerce_serang.data.api.response.OtpResponse
import com.alya.ecommerce_serang.data.api.response.ProductResponse
import com.alya.ecommerce_serang.data.api.response.ProfileResponse
import com.alya.ecommerce_serang.data.api.response.RegisterResponse
import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse
import com.alya.ecommerce_serang.data.api.response.StoreResponse
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
interface ApiService {
@GET("product")
fun getAllProduct(
@Header("Authorization") token: String = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzEsIm5hbWUiOiJhbHlhIiwiZW1haWwiOiJha3VuYmVsYWphci5hbHlhQGdtYWlsLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzM4NDg0OTc0LCJleHAiOjE3NDEwNzY5NzR9.0JyXJQ_6CKiZEi0gvk1gcn-0ILu3a9lOr3HqjhJXbBE"
): Call<AllProductResponse>
@POST("registeruser")
suspend fun register (
@Body registerRequest: RegisterRequest
): 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}")
fun getDetailProduct (
@Header("Authorization") token: String,
suspend fun getDetailProduct (
@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>
}

View File

@ -1,7 +1,10 @@
package com.alya.ecommerce_serang.data.repository
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -11,25 +14,84 @@ class ProductRepository(private val apiService: ApiService) {
withContext(Dispatchers.IO) {
try {
Log.d("ProductRepository", "Attempting to fetch products")
val response = apiService.getAllProduct().execute()
Log.d("ProductRepository", "Response received. Success: ${response.isSuccessful}")
Log.d("ProductRepository", "Response code: ${response.code()}")
Log.d("ProductRepository", "Response message: ${response.message()}")
val response = apiService.getAllProduct()
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 {
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) {
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>>
// fun getRecentSearchs(): Flow<List<String>>
// suspend fun clearRecents()
// suspend fun addRecents(search:String)
// suspend fun getProduct(id:String):DetailProduct
}
// null
// }
// } catch (e: Exception) {
// null
// }
// }

View File

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

View File

@ -6,11 +6,16 @@ import androidx.core.view.isVisible
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
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.utils.SessionManager
//@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
@ -19,6 +24,9 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager) // Inject SessionManager
setupBottomNavigation()
observeDestinationChanges()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,19 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
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.bumptech.glide.Glide
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.
private val onClick:(category:Category) -> Unit
private val onClick:(category:CategoryItem) -> Unit
): 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.
*/
inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
fun bind(category: Category) = with(binding){
Glide.with(root).load(category.image).into(image)
name.text = category.title
root.setOnClickListener{
onClick(category)
fun bind(category: CategoryItem) = with(binding) {
Log.d("CategoriesAdapter", "Binding category: ${category.name}, Image: ${category.image}")
val fullImageUrl = if (category.image.startsWith("/")) {
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) {
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)
}
}

View File

@ -1,21 +1,28 @@
package com.alya.ecommerce_serang.ui.home
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
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.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
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.repository.ProductRepository
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.HorizontalMarginItemDecoration
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.setLightStatusBar
import kotlinx.coroutines.launch
@ -24,30 +31,33 @@ class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: HomeViewModel
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(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
return _binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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()
setupRecyclerView()
observeData()
@ -59,6 +69,11 @@ class HomeFragment : Fragment() {
onClick = { product -> handleProductClick(product) }
)
categoryAdapter = HomeCategoryAdapter(
categories = emptyList(),
onClick = { category -> handleCategoryProduct(category)}
)
binding.newProducts.apply {
adapter = productAdapter
layoutManager = LinearLayoutManager(
@ -67,37 +82,59 @@ class HomeFragment : Fragment() {
false
)
}
binding.categories.apply {
adapter = categoryAdapter
layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
}
}
private fun observeData() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loading.root.isVisible = true
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = false
binding.home.isVisible = true
productAdapter?.updateProducts(state.products)
}
is HomeUiState.Error -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = true
binding.home.isVisible = false
binding.error.errorMessage.text = state.message
binding.error.retryButton.setOnClickListener {
viewModel.retry()
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loading.root.isVisible = true
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = false
binding.home.isVisible = true
productAdapter?.updateLimitedProducts(state.products) // Ensure productAdapter is initialized
}
is HomeUiState.Error -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = true
binding.home.isVisible = false
binding.error.errorMessage.text = state.message
binding.error.retryButton.setOnClickListener {
viewModel.retry()
}
}
}
}
}
}
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() {
// For LightStatusBar
setLightStatusBar()
@ -124,22 +161,23 @@ class HomeFragment : Fragment() {
private fun handleProductClick(product: ProductsItem) {
// Navigate to product detail
// findNavController().navigate(
// HomeFragmentDirections.actionHomeToDetail(product.id)
// )
val intent = Intent(requireContext(), DetailProductActivity::class.java)
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
startActivity(intent)
}
private fun handleCategoryProduct(category: CategoryItem) {
}
override fun onDestroyView() {
super.onDestroyView()
productAdapter = null
categoryAdapter = null
_binding = null
}
private fun showLoading(isLoading: Boolean) {
if (isLoading) {
binding.progressBar.visibility = View.VISIBLE
} else {
binding.progressBar.visibility = View.GONE
}
binding.progressBar.isVisible = isLoading
}
}

View File

@ -1,13 +1,12 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
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.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -18,37 +17,56 @@ class HomeViewModel (
): ViewModel() {
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
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 {
loadProducts()
loadCategories()
}
private fun loadProducts() {
viewModelScope.launch {
_uiState.value = HomeUiState.Loading
productRepository.getAllProducts()
.onSuccess { products ->
_uiState.value = HomeUiState.Success(products)
}
.onFailure { error ->
_uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
Log.e("ProductViewModel", "Products fetch failed", error)
}
when (val result = productRepository.getAllProducts()) {
is Result.Success -> _uiState.value = HomeUiState.Success(result.data)
is Result.Error -> _uiState.value = HomeUiState.Error(result.exception.message ?: "Unknown error")
is Result.Loading -> {}
}
}
}
fun retry() {
loadProducts()
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 toggleWishlist(product: Product) = viewModelScope.launch {
// try {
// productRepository.toggleWishlist(product.id,product.wishlist)
// }catch (e:Exception){
//
fun retry() {
loadProducts()
loadCategories()
}
// private fun fetchUserData() {
// viewModelScope.launch {
// try {
// 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) {
// Log.e("HomeFragment", "Exception: ${e.message}")
// }
// }
// }
}
@ -57,4 +75,4 @@ sealed class HomeUiState {
object Loading : HomeUiState()
data class Success(val products: List<ProductsItem>) : HomeUiState()
data class Error(val message: String) : HomeUiState()
}
}

View File

@ -1,9 +1,13 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
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.bumptech.glide.Glide
@ -16,17 +20,24 @@ class HorizontalProductAdapter(
RecyclerView.ViewHolder(binding.root) {
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
itemPrice.text = product.price
rating.text = product.rating
// productSold.text = "${product.totalSold} sold"
// Load image using Glide
Glide.with(itemView)
// .load("${BuildConfig.BASE_URL}/product/${product.image}")
// .load("${BuildConfig.BASE_URL}/${product.image}")
.load(product.image)
.into(image)
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(imageProduct)
root.setOnClickListener { onClick(product) }
}
@ -46,7 +57,32 @@ class HorizontalProductAdapter(
}
fun updateProducts(newProducts: List<ProductsItem>) {
val diffCallback = ProductDiffCallback(products, newProducts)
val diffResult = DiffUtil.calculateDiff(diffCallback)
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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,31 +1,95 @@
package com.alya.ecommerce_serang.ui.profile
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
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() {
companion object {
fun newInstance() = ProfileFragment()
}
private var _binding: FragmentProfileBinding? = null
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?) {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(requireContext())
// TODO: Use the ViewModel
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): 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)
}
}

View File

@ -1,7 +1,28 @@
package com.alya.ecommerce_serang.ui.profile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
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() {
// TODO: Implement the ViewModel
class ProfileViewModel(private val userRepository: UserRepository) : 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
}
}
}
}

View File

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

View File

@ -1,12 +1,12 @@
package com.alya.ecommerce_serang.utils
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
@Parcelize
data class ProductQuery (
val category: Category? = null,
val category: CategoryItem,
val search:String? = null,
val range:Pair<Float,Float> = 0f to 10000f,
val rating:Int? = null,

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@ -20,7 +20,7 @@
layout="@layout/view_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="19dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
@ -39,12 +39,11 @@
android:id="@+id/banners"
android:layout_width="match_parent"
android:layout_height="132dp"
android:layout_marginTop="23dp"
android:layout_marginTop="4dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/searchContainer"
tools:layout_editor_absoluteX="16dp" />
<TextView
android:id="@+id/categoriesText"
android:layout_width="wrap_content"
@ -92,7 +91,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="24dp"
android:text="@string/new_products_text"
android:text="@string/sold_product_text"
android:textColor="@color/black"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"

View File

@ -1,13 +1,331 @@
<?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"
android:layout_width="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">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello" />
<!-- Profile Header -->
<ImageView
android:id="@+id/profileImage"
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>

View File

@ -17,7 +17,7 @@
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/image"
android:id="@+id/image_category"
android:src="@drawable/makanan_ringan"
android:scaleType="centerCrop" />
</com.google.android.material.card.MaterialCardView>

View File

@ -17,7 +17,7 @@
app:strokeWidth="1dp">
<ImageView
android:id="@+id/image"
android:id="@+id/image_product"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"

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

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

View File

@ -12,7 +12,7 @@
android:hint="@string/fragment_home_search"
android:textColor="@color/soft_gray"
android:textSize="16sp"
android:layout_marginStart="16dp"
android:layout_marginStart="8dp"
android:drawablePadding="8dp"
android:paddingHorizontal="25dp"
android:imeOptions="actionSearch"
@ -37,7 +37,7 @@
android:id="@+id/btn_cart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:backgroundTint="@color/white"
android:src="@drawable/outline_shopping_cart_24"

View File

@ -20,4 +20,14 @@
android:name="com.alya.ecommerce_serang.ui.chat.ChatFragment"
android:label="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>

View File

@ -13,4 +13,52 @@
<string name="error_loading">Terdapat error...</string>
<string name="new_products_text">Produk Terbaru</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>

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