This commit is contained in:
2025-07-18 16:04:59 +07:00
parent 69f872f1c4
commit 3fa4622feb
67 changed files with 6361 additions and 48680 deletions

6
.env
View File

@@ -1,3 +1,3 @@
EXPO_HOME=D:/bestpc_mobile/.expo
EXPO_CACHE_DIR=D:/bestpc_mobile/.expo/cache
EXPO_PROJECT_DIR=D:/bestpc_mobile
EXPO_HOME=C:/Users/tiepb/Downloads/Company/work_2025/bestpc_mobile/.expo
EXPO_CACHE_DIR=C:/Users/tiepb/Downloads/Company/work_2025/bestpc_mobile/.expo/cache
EXPO_PROJECT_DIR=C:/Users/tiepb/Downloads/Company/work_2025/bestpc_mobile

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

835
.idea/caches/deviceStreaming.xml generated Normal file
View File

@@ -0,0 +1,835 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceStreaming">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="Sony" />
<option name="codename" value="A402SO" />
<option name="id" value="A402SO" />
<option name="labId" value="google" />
<option name="manufacturer" value="Sony" />
<option name="name" value="Xperia 10" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2520" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="labId" value="google" />
<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="OP535DL1" />
<option name="id" value="OP535DL1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2409" />
<option name="screenDensity" value="401" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OnePlus" />
<option name="codename" value="OP5552L1" />
<option name="id" value="OP5552L1" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="samsung" />
<option name="codename" value="a14m" />
<option name="id" value="a14m" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A145R" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2408" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a15" />
<option name="id" value="a15" />
<option name="labId" value="google" />
<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="a15x" />
<option name="id" value="a15x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15 5G" />
<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="a16x" />
<option name="id" value="a16x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A16 5G" />
<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="labId" value="google" />
<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="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="labId" value="google" />
<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="35" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="34" />
<option name="brand" value="samsung" />
<option name="codename" value="b6q" />
<option name="id" value="b6q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Flip 6" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1080" />
<option name="screenY" value="2640" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="35" />
<option name="brand" value="google" />
<option name="codename" value="caiman" />
<option name="id" value="caiman" />
<option name="labId" value="google" />
<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="default" value="true" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<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="35" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="default" value="true" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="motorola" />
<option name="codename" value="dubai" />
<option name="id" value="dubai" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="edge 30" />
<option name="screenDensity" value="405" />
<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="e1q" />
<option name="default" value="true" />
<option name="id" value="e1q" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="35" />
<option name="brand" value="motorola" />
<option name="codename" value="eqe" />
<option name="id" value="eqe" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="edge 50 pro" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1220" />
<option name="screenY" value="2712" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="motorola" />
<option name="codename" value="fogos" />
<option name="id" value="fogos" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g34 5G" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts7lwifi" />
<option name="id" value="gts7lwifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-T870" />
<option name="screenDensity" value="340" />
<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="gts7xllite" />
<option name="id" value="gts7xllite" />
<option name="labId" value="google" />
<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="formFactor" value="Tablet" />
<option name="id" value="gts8uwifi" />
<option name="labId" value="google" />
<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="formFactor" value="Tablet" />
<option name="id" value="gts8wifi" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="samsung" />
<option name="codename" value="gts9wifi" />
<option name="id" value="gts9wifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X710" />
<option name="screenDensity" value="340" />
<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="husky" />
<option name="id" value="husky" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="35" />
<option name="brand" value="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="labId" value="google" />
<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="34" />
<option name="brand" value="motorola" />
<option name="codename" value="lion" />
<option name="id" value="lion" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g04" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1612" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="labId" value="google" />
<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="34" />
<option name="brand" value="motorola" />
<option name="codename" value="lyriq" />
<option name="id" value="lyriq" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="edge 40" />
<option name="screenDensity" value="400" />
<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="manaus" />
<option name="id" value="manaus" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="edge 40 neo" />
<option name="screenDensity" value="400" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="35" />
<option name="brand" value="samsung" />
<option name="codename" value="pa3q" />
<option name="id" value="pa3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S25 Ultra" />
<option name="screenDensity" value="600" />
<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="panther" />
<option name="id" value="panther" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="formFactor" value="Wear OS" />
<option name="id" value="r11" />
<option name="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="labId" value="google" />
<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="formFactor" value="Tablet" />
<option name="id" value="tangorpro" />
<option name="labId" value="google" />
<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="35" />
<option name="brand" value="google" />
<option name="codename" value="tegu" />
<option name="id" value="tegu" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<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="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<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="34" />
<option name="brand" value="samsung" />
<option name="codename" value="xcover7" />
<option name="id" value="xcover7" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-G556B" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2408" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>

13
.idea/deviceManager.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

47
App.tsx
View File

@@ -1,53 +1,14 @@
import { StatusBar } from "expo-status-bar";
import { StyleSheet, ScrollView } from "react-native";
import { SafeAreaProvider } from "react-native-safe-area-context";
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import {
createStackNavigator,
StackScreenProps,
} from "@react-navigation/stack";
import {
createDrawerNavigator,
DrawerItemList,
DrawerItem,
DrawerContentScrollView,
} from "@react-navigation/drawer";
import Header from "./src/components/header/Header";
import Footer from "./src/components/footer/Footer";
import HomePage from "./src/screens/HomeScreen";
import { SafeAreaProvider } from "react-native-safe-area-context";
import AppNavigator from "./src/navigation/AppNavigator";
export default function App() {
return (
<SafeAreaProvider>
<Header />
<NavigationContainer>
<AllPage />
<AppNavigator />
</NavigationContainer>
{/* <Footer /> */}
</SafeAreaProvider>
);
}
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
const AllPage = () => {
return (
<Stack.Navigator
initialRouteName="homepage"
screenOptions={{
headerShown: false, // Ẩn header cho tất cả màn hình
}}
>
<Stack.Screen name="homepage" component={HomePage} />
</Stack.Navigator>
);
};
const styles = StyleSheet.create({
container: {
marginLeft: "auto",
marginRight: "auto",
maxWidth: 480,
},
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
assets/images/avartar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

BIN
assets/images/icon_map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

18
babel.config.js Normal file
View File

@@ -0,0 +1,18 @@
module.exports = {
presets: ['babel-preset-expo'],
plugins: [
[
'module-resolver',
{
root: ['./src'],
alias: {
'@components': './src/components',
'@layouts': './src/layouts',
'@screens': './src/screens',
'@assets': './src/assets',
'@navigation': './src/navigation',
},
},
],
],
};

View File

@@ -6,3 +6,4 @@ import App from './App';
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

39049
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,30 +14,40 @@
"@expo/vector-icons": "^14.1.0",
"@react-native-picker/picker": "^2.11.0",
"@react-navigation/drawer": "^7.3.12",
"@react-navigation/native": "^7.1.9",
"@react-navigation/native": "^7.1.14",
"@react-navigation/native-stack": "^7.3.13",
"@react-navigation/stack": "^7.3.2",
"dotenv": "^16.5.0",
"expo-cli": "^4.9.1",
"expo-image-picker": "~16.1.4",
"expo-linear-gradient": "~14.1.4",
"expo-status-bar": "~2.2.3",
"hermes-engine": "^0.11.0",
"lodash": "^4.17.21",
"metro-runtime": "^0.82.3",
"react": "19.0.0",
"react-dom": "^19.0.0",
"react-native": "^0.79.2",
"react-native-collapsible": "^1.6.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
"react-native-reanimated-carousel": "^4.0.2",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "~4.10.0",
"react-native-swiper": "^1.6.0",
"react-native-web": "^0.20.0",
"react-native-webview": "13.13.5",
"react-navigation": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@types/lodash": "^4.17.20",
"@types/react": "~19.0.10",
"@types/react-native": "^0.73.0",
"babel-plugin-module-resolver": "^5.0.2",
"expo": "^53.0.9",
"metro-react-native-babel-preset": "^0.77.0",
"typescript": "~5.8.3"
},
"private": true

View File

@@ -0,0 +1,76 @@
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { NavigationProp } from "@react-navigation/native";
type Props = {
navigation: NavigationProp<any>;
activeTab?: string;
};
const navTabs = [
{ label: "Trang chủ", icon: "home-outline", route: "homepage" },
{ label: "Sản phẩm", icon: "cube-outline", route: "productlist" },
{ label: "Sửa chữa", icon: "settings-outline", route: "Repair" },
{ label: "Hỏi đáp", icon: "help-circle-outline", route: "FAQ" },
{ label: "Tài khoản", icon: "person-outline", route: "Account" },
];
const ButtonFooter: React.FC<Props> = ({ navigation, activeTab }) => {
return (
<View style={styles.footerNav}>
{navTabs.map((tab, index) => {
const isActive = activeTab === tab.route;
return (
<TouchableOpacity
key={index}
style={styles.navItem}
onPress={() => navigation?.navigate(tab.route)}
>
<Ionicons
name={tab.icon}
size={22}
color={isActive ? "#4a00e0" : "#666"}
/>
<Text style={[styles.navLabel, isActive && styles.navLabelActive]}>
{tab.label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
};
const styles = StyleSheet.create({
footerNav: {
flexDirection: "row",
borderTopWidth: 1,
borderColor: "#ddd",
backgroundColor: "#fff",
paddingVertical: 8,
justifyContent: "space-around",
position: "absolute",
bottom: 0,
width: "100%",
},
navItem: {
alignItems: "center",
flex: 1,
},
navLabel: {
fontSize: 12,
color: "#666",
marginTop: 2,
},
navLabelActive: {
color: "#4a00e0",
fontWeight: "bold",
},
});
export default ButtonFooter;

View File

@@ -1,101 +1,148 @@
import * as React from "react";
import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
Linking,
Dimensions,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import Collapsible from "react-native-collapsible";
import { NavigationProp } from "@react-navigation/native";
type FooterItemProps = {
label: string;
icon: string;
var winWidth = Dimensions.get("window").width; //full width
const accordionData = [
{
title: "Về chúng tôi",
links: [
{ title: "Độc quyền", route: "" },
{ title: "Mua online", route: "" },
{ title: "Bộ sưu tập", route: "" },
{ title: "Tin tức", route: "article" },
],
},
{
title: "Tuyển dụng",
links: [
{ title: "Liên hệ", route: "contact" },
{ title: "Điều khoản", route: "" },
{ title: "Chính sách bảo mật", route: "" },
{ title: "Trở thành đối tác", route: "" },
],
},
{
title: "Liên kết",
links: [{ title: "Hurasoft.com", route: "https://www.hurasoft.vn" }],
},
{
title: "Giới thiệu",
content: "Chào mừng bạn đến với ứng dụng của chúng tôi.",
},
];
type Props = {
navigation: NavigationProp<any>;
activeTab?: string;
};
const FooterItem = ({ label, icon }: FooterItemProps) => (
<TouchableOpacity style={styles.footerItem}>
<Text style={styles.footerItemText}>{label}</Text>
<Image
source={{ uri: icon }}
style={styles.footerItemIcon}
resizeMode="contain"
const Footer: React.FC<Props> = ({ navigation, activeTab = "homepage" }) => {
const [activeIndex, setActiveIndex] = useState(null);
const toggleAccordion = (index: any) => {
setActiveIndex(index === activeIndex ? null : index);
};
const handleLinkPress = (route: string) => {
if (route.startsWith("http")) {
// Mở link ngoài nếu là URL
Linking.openURL(route);
} else {
navigation?.navigate(route);
}
};
return (
<View style={styles.footer}>
<View style={styles.spacer} />
<ScrollView style={styles.accordionContainer}>
{accordionData.map((item, index) => (
<View key={index}>
<TouchableOpacity
style={styles.accordionHeader}
onPress={() => toggleAccordion(index)}
>
<Text style={styles.accordionTitle}>{item.title}</Text>
<Ionicons
name={activeIndex === index ? "chevron-up" : "chevron-down"}
size={20}
color="#333"
/>
</TouchableOpacity>
);
const Footer = () => {
return (
<View style={styles.container}>
<View style={styles.spacer} />
<View style={styles.content}>
<FooterItem
label="Về chúng tôi"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/0f5cda0be55412240422e2274330e3f6e64023d3?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<FooterItem
label="Tuyển dụng"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/0f5cda0be55412240422e2274330e3f6e64023d3?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<FooterItem
label="Liên kết"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/0f5cda0be55412240422e2274330e3f6e64023d3?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<FooterItem
label="Giới thiệu"
icon="https://cdn.builder.io/api/v1/image/assets/TEMP/5263024b89e68e21be091071a684ede939b2cbf7?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756"
/>
<Image
source={{
uri: "https://cdn.builder.io/api/v1/image/assets/TEMP/e7727a56c76a2e7c02cd1646e68e8b20cefb30e1?placeholderIfAbsent=true&apiKey=1fa9f06a1e81406d92148011750a3756",
}}
style={styles.logo}
resizeMode="contain"
/>
<Collapsible collapsed={activeIndex !== index}>
{item.links?.map((link, linkIndex) => (
<TouchableOpacity
key={linkIndex}
style={styles.linkItem}
onPress={() => handleLinkPress(link.route)}
>
<Text style={styles.linkText}>{link.title}</Text>
</TouchableOpacity>
))}
</Collapsible>
</View>
))}
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
alignSelf: "stretch",
marginTop: 28,
footer: {
width: "100%",
paddingBottom: 21,
marginTop: 20,
},
spacer: {
display: "flex",
minHeight: 10,
width: "100%",
backgroundColor: "#1c039b",
},
content: {
marginTop: 17,
width: "100%",
accordionContainer: {
paddingLeft: 10,
paddingRight: 10,
paddingTop: 16,
marginBottom: 60,
width: winWidth - 10,
},
footerItem: {
borderRadius: 4,
display: "flex",
paddingLeft: 9,
paddingRight: 9,
paddingTop: 6,
paddingBottom: 13,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
accordionHeader: {
backgroundColor: "#f5f5f5",
padding: 14,
borderRadius: 8,
marginBottom: 8,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
footerItemText: {
color: "#444",
fontFamily: "Shopee Display, -apple-system, Roboto, Helvetica, sans-serif",
fontSize: 13,
fontWeight: "700",
accordionTitle: {
fontSize: 16,
color: "#333",
fontWeight: 700,
},
footerItemIcon: {
width: 11,
height: 6,
marginTop: 7,
accordionContent: {
backgroundColor: "#fff",
padding: 12,
marginBottom: 10,
color: "#555",
},
logo: {
width: 95,
height: 40,
marginTop: 17,
marginLeft: 10,
linkItem: {
paddingVertical: 8,
paddingLeft: 20,
},
linkText: {
fontSize: 14,
fontWeight: 500,
},
});

View File

@@ -223,20 +223,22 @@ const styles = StyleSheet.create({
borderRadius: 4,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 6,
paddingBottom: 6,
height: 32,
lineHeight: 32,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "#FFFFFF",
},
searchInput: {
flex: 1,
fontSize: 12,
fontFamily: "Roboto, -apple-system, Roboto, Helvetica, sans-serif",
fontWeight: "500",
color: "#000000",
height: 32,
lineHeight: 32,
paddingTop: 7,
marginTop: 5,
width: winWidth - 60,
},
searchIconContainer: {
flexDirection: "row",

View File

@@ -0,0 +1,108 @@
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
} from "react-native";
import { Feather } from "@expo/vector-icons";
type Props = {
options: string[];
selected: string;
onSelect: (val: string) => void;
placeholder?: string;
};
const FilterDropdown = ({
options,
selected,
onSelect,
placeholder,
}: Props) => {
const [open, setOpen] = useState(false);
return (
<View style={styles.dropdownContainer}>
<TouchableOpacity
style={styles.dropdownHeader}
onPress={() => setOpen(!open)}
>
<Text style={styles.dropdownText}>
{selected || placeholder || "Chọn"}
</Text>
<Feather
name={open ? "chevron-up" : "chevron-down"}
size={16}
color="#333"
/>
</TouchableOpacity>
{open && (
<View style={styles.dropdownList}>
<FlatList
data={options}
keyExtractor={(item) => item}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.option}
onPress={() => {
onSelect(item);
setOpen(false);
}}
>
<Text style={styles.optionText}>{item}</Text>
</TouchableOpacity>
)}
/>
</View>
)}
</View>
);
};
export default FilterDropdown;
const styles = StyleSheet.create({
dropdownContainer: {
width: 150,
marginTop: 10,
marginBottom: 10,
position: "relative",
marginRight: 10,
},
dropdownHeader: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 6,
paddingHorizontal: 10,
paddingVertical: 8,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
backgroundColor: "#fff",
},
dropdownText: {
fontSize: 14,
},
dropdownList: {
position: "absolute",
top: 37,
width: "100%",
borderWidth: 1,
borderColor: "#ccc",
borderTopWidth: 0,
borderRadius: 6,
overflow: "hidden",
maxHeight: 150,
backgroundColor: "#fff",
zIndex: 1000,
},
option: {
padding: 10,
},
optionText: {
fontSize: 14,
},
});

View File

@@ -0,0 +1,182 @@
import React, { useState } from "react";
import {
Modal,
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
Image,
Pressable,
ScrollView,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
type Props = {
visible: boolean;
onClose: () => void;
};
const FormReview = ({ visible, onClose }: Props) => {
const [selectedRating, setSelectedRating] = useState<number>(0);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const handleRating = (value: number) => {
setSelectedRating(value);
};
const renderStars = () => {
return (
<View style={{ flexDirection: "row" }}>
{[1, 2, 3, 4, 5].map((i) => (
<TouchableOpacity key={i} onPress={() => handleRating(i)}>
<Ionicons
name="star"
size={28}
color={i <= selectedRating ? "#ff7a00" : "#d9d9d9"}
style={{ marginHorizontal: 4 }}
/>
</TouchableOpacity>
))}
</View>
);
};
return (
<Modal visible={visible} animationType="fade" transparent>
<View style={styles.overlay}>
<View style={styles.container}>
{/* Title */}
<View style={styles.header}>
<Text style={styles.headerText}>Đánh giá sản phẩm</Text>
<TouchableOpacity onPress={onClose}>
<Text style={styles.closeBtn}>x</Text>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={{ alignItems: "center" }}>
{/* Product info */}
<Image
source={require("../../../assets/images/small-product-detail.png")}
style={styles.productImg}
/>
<Text style={styles.productName}>
Laptop Gaming Asus TUF FX505GE-BQ037T Core i7-8750H/Win10 (15.6"
FHD) - Hàng Chính Hãng
</Text>
{/* Rating */}
{renderStars()}
{/* Title input */}
<TextInput
style={styles.input}
placeholder="Mời bạn nhập tiêu đ..."
value={title}
onChangeText={setTitle}
/>
{/* Content input */}
<TextInput
style={[styles.input, { height: 100, textAlignVertical: "top" }]}
placeholder="Mời bạn chia sẻ cảm nhận..."
value={content}
onChangeText={setContent}
multiline
/>
{/* Upload placeholder */}
<TouchableOpacity style={styles.uploadArea}>
<Ionicons name="image-outline" size={22} color="#1877F2" />
<Text style={styles.uploadText}>Gửi ảnh thực tế</Text>
<Text style={styles.uploadNote}>(tối đa 3 ảnh)</Text>
</TouchableOpacity>
{/* Submit */}
<TouchableOpacity style={styles.submitBtn}>
<Text style={styles.submitText}>Gửi đánh giá</Text>
</TouchableOpacity>
</ScrollView>
</View>
</View>
</Modal>
);
};
export default FormReview;
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: "#00000080",
justifyContent: "center",
alignItems: "center",
},
container: {
width: "95%",
backgroundColor: "white",
borderRadius: 12,
padding: 20,
maxHeight: "90%",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
borderBottomWidth: 1,
borderColor: "#E4E4E4",
paddingBottom: 15,
},
headerText: {
fontSize: 20,
fontWeight: "bold",
textAlign: "center",
flex: 1,
},
closeBtn: {
fontSize: 22,
color: "#808080",
},
productImg: {
width: 100,
height: 100,
marginVertical: 10,
},
productName: {
textAlign: "center",
marginBottom: 15,
},
input: {
width: "100%",
borderWidth: 1,
borderColor: "#B1B1B1",
borderRadius: 4,
padding: 10,
marginVertical: 10,
},
uploadArea: {
flexDirection: "row",
alignItems: "center",
marginTop: 10,
},
uploadText: {
color: "#1877F2",
marginLeft: 5,
},
uploadNote: {
paddingHorizontal: 5,
},
submitBtn: {
marginTop: 15,
height: 45,
width: 500,
backgroundColor: "#C8B7FF",
borderRadius: 4,
justifyContent: "center",
alignItems: "center",
},
submitText: {
color: "white",
fontWeight: "bold",
},
});

View File

@@ -9,10 +9,14 @@ import {
Dimensions,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
var winWidth = Dimensions.get("window").width; //full width
var winHeight = Dimensions.get("window").height;
const ItemProduct = ({ product }: { product: any }) => {
const navigation = useNavigation();
function formatCurrency(a: number | string): string {
let b = parseFloat(a.toString())
.toFixed(2)
@@ -25,7 +29,10 @@ const ItemProduct = ({ product }: { product: any }) => {
return (
<View style={styles.productItem}>
<TouchableOpacity style={styles.productImage}>
<TouchableOpacity
onPress={() => navigation.navigate("productdetail" as never)}
style={styles.productImage}
>
<Image
source={{ uri: product.productImage.large }}
style={styles.productImg}
@@ -37,7 +44,9 @@ const ItemProduct = ({ product }: { product: any }) => {
</View>
</TouchableOpacity>
<View>
<TouchableOpacity>
<TouchableOpacity
onPress={() => navigation.navigate("productdetail" as never)}
>
<Text numberOfLines={2} style={styles.productName}>
{product.productName}
</Text>
@@ -53,7 +62,7 @@ const ItemProduct = ({ product }: { product: any }) => {
<Image
source={
product.review.rate !== undefined
? require(`../../../assets/images/icon_star_${product.review.rate}.png`)
? require(`../../../assets/images/icon_star_5.png`)
: require("../../../assets/images/icon_star_0.png")
}
style={styles.iconReviewRating}
@@ -83,19 +92,18 @@ const ItemProduct = ({ product }: { product: any }) => {
const styles = StyleSheet.create({
productItem: {
width: winWidth / 2 - 20,
marginRight: 10,
marginBottom: 10,
padding: 12,
padding: 10,
borderWidth: 1,
borderColor: "#d3d3d3",
borderRadius: 8,
backgroundColor: "#fff",
},
productImage: {
width: winWidth / 2 - 20,
width: winWidth / 2 - 44,
height: 150,
position: "relative",
marginBottom: 5,
borderWidth: 1,
borderColor: "#d3d3d3",
backgroundColor: "#fff",
borderRadius: 8,
},
productImg: {
width: "100%",
@@ -189,7 +197,7 @@ const styles = StyleSheet.create({
height: 18,
},
storeName: {
fontSize: 13,
fontSize: 12,
fontWeight: "bold",
color: "#000",
},

View File

@@ -0,0 +1,115 @@
import React, { useRef, useState, useCallback } from "react";
import {
View,
Image,
FlatList,
TouchableOpacity,
Dimensions,
StyleSheet,
} from "react-native";
import Carousel from "react-native-reanimated-carousel";
const { width } = Dimensions.get("window");
const THUMB_SIZE = 64;
const SPACING = 10;
const images = [
require("../../../assets/images/big-product-detail.png"),
require("../../../assets/images/big-product-detail.jpg"),
require("../../../assets/images/big-product-detail-2.jpg"),
require("../../../assets/images/big-product-detail-3.jpg"),
require("../../../assets/images/big-product-detail-4.jpg"),
];
const ProductGallery = () => {
const [activeIndex, setActiveIndex] = useState(0);
const carouselRef = useRef<Carousel<any>>(null);
const thumbnailListRef = useRef<FlatList>(null);
const scrollToThumbnail = useCallback((index: number) => {
const offset = index * (THUMB_SIZE + SPACING) - width / 2 + THUMB_SIZE / 2;
thumbnailListRef.current?.scrollToOffset({
offset: Math.max(0, offset),
animated: true,
});
}, []);
const onSnapToItem = useCallback(
(index: number) => {
setActiveIndex(index);
scrollToThumbnail(index);
},
[scrollToThumbnail]
);
const onThumbnailPress = useCallback((index: number) => {
carouselRef.current?.scrollTo({ index, animated: true });
}, []);
const renderThumbnail = useCallback(
({ item, index }: { item: any; index: number }) => (
<TouchableOpacity onPress={() => onThumbnailPress(index)}>
<Image
source={item}
style={[
styles.thumbnail,
activeIndex === index && styles.activeThumbnail,
]}
/>
</TouchableOpacity>
),
[activeIndex, onThumbnailPress]
);
return (
<View style={styles.container}>
<Carousel
ref={carouselRef}
loop={false}
width={width}
height={300}
data={images}
scrollAnimationDuration={400}
renderItem={({ item }) => (
<Image source={item} style={styles.bigImage} resizeMode="contain" />
)}
onSnapToItem={onSnapToItem}
/>
<FlatList
ref={thumbnailListRef}
data={images}
horizontal
keyExtractor={(_, i) => i.toString()}
renderItem={renderThumbnail}
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.thumbList}
/>
</View>
);
};
export default ProductGallery;
const styles = StyleSheet.create({
container: {},
bigImage: {
width: width - 40,
height: 280,
borderWidth: 1,
borderColor: "#b1b1b1",
borderRadius: 12,
},
thumbnail: {
width: THUMB_SIZE,
height: THUMB_SIZE,
marginRight: SPACING,
borderRadius: 6,
borderWidth: 1,
borderColor: "#b1b1b1",
},
activeThumbnail: {
borderColor: "#1877F2",
},
thumbList: {},
});

View File

@@ -0,0 +1,98 @@
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
LayoutChangeEvent,
Dimensions,
StyleSheet,
} from "react-native";
import RenderHTML from "react-native-render-html";
import { Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
const MAX_HEIGHT = 300;
const ProductHTMLContent = ({ htmlContent }: { htmlContent: string }) => {
const [showFull, setShowFull] = useState(false);
const [contentHeight, setContentHeight] = useState(0);
const [shouldShowToggle, setShouldShowToggle] = useState(false);
const onContentLayout = (event: LayoutChangeEvent) => {
const height = event.nativeEvent.layout.height;
setContentHeight(height);
if (height > MAX_HEIGHT) {
setShouldShowToggle(true);
}
};
return (
<View style={styles.container}>
<View
style={[
!showFull && shouldShowToggle
? { maxHeight: MAX_HEIGHT, overflow: "hidden" }
: null,
]}
onLayout={onContentLayout}
>
<RenderHTML
contentWidth={Dimensions.get("window").width - 32}
source={{ html: htmlContent }}
/>
</View>
{!showFull && shouldShowToggle && (
<LinearGradient
colors={["transparent", "#ffffff"]}
style={styles.fadeGradient}
pointerEvents="none"
/>
)}
{shouldShowToggle && (
<TouchableOpacity
onPress={() => setShowFull(!showFull)}
style={styles.button}
>
<Text style={styles.buttonText}>
{showFull ? "Thu gọn" : "Xem thêm"}
</Text>
<Ionicons
name={showFull ? "chevron-up-outline" : "chevron-down-outline"}
size={18}
color="#ff7a00"
style={{ marginLeft: 4 }}
/>
</TouchableOpacity>
)}
</View>
);
};
export default ProductHTMLContent;
const styles = StyleSheet.create({
container: {
backgroundColor: "#fff",
position: "relative",
},
fadeGradient: {
position: "absolute",
bottom: 30,
left: 0,
right: 0,
height: 80,
zIndex: 1,
},
button: {
marginTop: 12,
alignSelf: "center",
flexDirection: "row",
alignItems: "center",
},
buttonText: {
color: "#ff7a00",
fontSize: 14,
},
});

View File

@@ -0,0 +1,178 @@
// components/SupplierItem.tsx
import React from "react";
import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native";
import { Ionicons } from "@expo/vector-icons";
interface SupplierItemProps {
name: string;
distance: string;
rating: number;
logo: any; // require('...') dùng để import hình ảnh cục bộ
shippingPolicy: string;
priceRange: string;
hasVAT: boolean;
products: string[];
}
const SupplierItem: React.FC<SupplierItemProps> = ({
name,
distance,
rating,
logo,
shippingPolicy,
priceRange,
hasVAT,
products,
}) => {
return (
<View style={styles.ItemSupplier}>
{/* Logo + info */}
<View style={{ flexDirection: "row", alignItems: "center" }}>
<View
style={{
marginRight: 10,
alignItems: "center",
}}
>
<View
style={{
width: 50,
height: 50,
padding: 10,
borderWidth: 1,
borderColor: "#F3F3F3",
borderRadius: 4,
}}
>
<Image
source={logo}
style={{ width: "100%", height: "100%", resizeMode: "contain" }}
/>
</View>
</View>
<View
style={{
borderLeftWidth: 1,
borderColor: "#b3b3b3",
paddingLeft: 10,
}}
>
<Text>
<Ionicons name="star" size={14} color="#ff7a00" />
<Ionicons name="star" size={14} color="#ff7a00" />
<Ionicons name="star" size={14} color="#ff7a00" />
<Ionicons name="star" size={14} color="#ff7a00" />
<Ionicons name="star" size={14} color="#D9D9D9" />
</Text>
<Text style={{ color: "#1877F2", fontWeight: "bold" }}>{name}</Text>
</View>
<View
style={{
flexDirection: "column",
marginLeft: 10,
borderLeftWidth: 1,
borderColor: "#b3b3b3",
paddingLeft: 10,
}}
>
<Text
style={{
color: "#E85933",
}}
>
Xem 7 cửa hàng
</Text>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Image
source={require("../../../assets/images/icon_map.png")}
alt="icon store"
style={{ width: 10 }}
resizeMode="contain"
/>
<Text style={{ marginLeft: 5 }}>3km</Text>
</View>
</View>
</View>
{/* Giá */}
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 5,
marginTop: 5,
}}
>
<Text style={{ flexDirection: "row", alignItems: "center" }}>
<Text>Giá: </Text>
<Text style={{ color: "#D80A00", fontWeight: "bold" }}>
{priceRange}
</Text>
</Text>
{hasVAT && (
<Text style={{ marginLeft: 10, fontWeight: "bold" }}>Đã VAT</Text>
)}
</View>
{/* Sản phẩm */}
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 5,
flexWrap: "wrap",
}}
>
{products.map((item, index) => (
<Text
key={index}
style={{
paddingRight: 10,
borderRightWidth: index < products.length - 1 ? 1 : 0,
borderColor: "#D9D9D9",
marginRight: 10,
}}
>
{item}
</Text>
))}
</View>
{/* Chính sách giao hàng */}
<View style={{ flex: 1, marginBottom: 10 }}>
<Text>{shippingPolicy}</Text>
</View>
{/* Liên hệ */}
<View
style={{ flex: 1, justifyContent: "center", alignItems: "flex-end" }}
>
<TouchableOpacity
style={{
width: 130,
height: 40,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F97316",
borderRadius: 6,
}}
>
<Text style={{ color: "#fff", fontWeight: "500" }}>Liên hệ ngay</Text>
</TouchableOpacity>
</View>
</View>
);
};
export default SupplierItem;
const styles = StyleSheet.create({
ItemSupplier: {
flex: 1,
marginTop: 10,
paddingBottom: 10,
paddingTop: 10,
borderBottomWidth: 1,
borderColor: "#e3e3e3",
},
});

View File

@@ -0,0 +1,171 @@
import React, { useState } from "react";
import {
View,
StyleSheet,
Image,
Text,
TextInput,
TouchableOpacity,
Dimensions,
} from "react-native";
import { globalStyles } from "../../styles/globalStyles";
var winWidth = Dimensions.get("window").width; //full width
var winHeight = Dimensions.get("window").height;
const ItemProductSave = ({ product }: { product: any }) => {
function formatCurrency(a: number | string): string {
let b = parseFloat(a.toString())
.toFixed(2)
.replace(/(\d)(?=(\d{3})+\.)/g, "$1.")
.toString();
var len = b.length;
b = b.substring(0, len - 3);
return b;
}
return (
<View style={styles.productItem}>
<TouchableOpacity style={styles.productImage}>
<Image
source={{ uri: product.productImage.large }}
style={styles.productImg}
alt={product.productName}
/>
<View style={styles.iconSave}>
<Image
source={require("../../../assets/images/icon_heart.png")}
style={styles.imgsave}
alt="icon-heart"
/>
</View>
</TouchableOpacity>
<View>
<TouchableOpacity>
<Text numberOfLines={2} style={styles.productName}>
{product.productName}
</Text>
</TouchableOpacity>
<View style={styles.BoxPriceRating}>
<View style={globalStyles.boxFlex}>
<Text style={styles.price}>{formatCurrency(product.price)}đ</Text>
<Text style={styles.oldPrice}>
{formatCurrency(product.oldPrice)}đ
</Text>
</View>
</View>
<View style={styles.boxStore}>
<View style={styles.boxIconStore}>
<Image
source={require("../../../assets/images/icon_store_white.png")}
style={styles.iconStore}
alt="icon store"
resizeMode="contain"
/>
</View>
<Text style={styles.storeName}>
{product.toltalStore} cửa hàng bán
</Text>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
productItem: {
width: winWidth / 2 - 20,
},
productImage: {
width: winWidth / 2 - 20,
height: 200,
position: "relative",
marginBottom: 5,
borderWidth: 1,
borderColor: "#d3d3d3",
backgroundColor: "#fff",
borderRadius: 8,
},
productImg: {
width: "100%",
height: "100%",
objectFit: "contain",
},
iconSave: {
position: "absolute",
right: 5,
top: 5,
width: 16,
height: 13,
zIndex: 9,
},
imgsave: {
width: "100%",
objectFit: "contain",
margin: "auto",
},
productName: {
fontWeight: 700,
fontSize: 14,
color: "#000",
marginBottom: 5,
},
summary: {
marginTop: 5,
color: "#595959",
},
locahostPro: {
flexDirection: "row",
alignItems: "center",
},
iconMap: {},
BoxPriceRating: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-end",
},
price: {
fontSize: 15,
fontWeight: "bold",
color: "#d80a00",
marginRight: 10,
},
oldPrice: {
fontSize: 13,
textDecorationLine: "line-through",
color: "#595959",
fontWeight: "bold",
},
iconReviewRating: {
width: 58,
height: 11,
marginBottom: 2,
},
boxStore: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
marginTop: 10,
},
boxIconStore: {
width: 24,
height: 24,
borderRadius: 25,
backgroundColor: "#FF7A00",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
marginRight: 5,
},
iconStore: {
width: 18,
height: 18,
},
storeName: {
fontSize: 13,
fontWeight: "bold",
color: "#000",
},
});
export default ItemProductSave;

View File

@@ -39,4 +39,24 @@ export const categories = [
name: "Pc văn phòng",
image: "../../assets/images/category-pc.png",
},
{
id: 9,
name: "Pc văn phòng",
image: "../../assets/images/category-pc.png",
},
{
id: 10,
name: "Pc văn phòng",
image: "../../assets/images/category-pc.png",
},
{
id: 11,
name: "Pc văn phòng",
image: "../../assets/images/category-pc.png",
},
{
id: 12,
name: "Pc văn phòng",
image: "../../assets/images/category-pc.png",
},
];

41
src/layouts/AppLayout.tsx Normal file
View File

@@ -0,0 +1,41 @@
import React from "react";
import { View, StyleSheet, ScrollView } from "react-native";
import {
useNavigation,
NavigationProp,
NavigationContainer,
} from "@react-navigation/native";
import Header from "../components/header/Header";
import ButtonFooter from "../components/footer/ButtonFooter";
type Props = {
children: React.ReactNode;
activeTab: string;
};
const AppLayout = ({ children, activeTab }: Props) => {
const navigation = useNavigation<NavigationProp<any>>();
return (
<View style={styles.container}>
<Header />
<View style={styles.content}>{children}</View>
<ButtonFooter navigation={navigation} activeTab={activeTab} />
</View>
);
};
export default AppLayout;
const styles = StyleSheet.create({
container: {
marginLeft: "auto",
marginRight: "auto",
maxWidth: 480,
flex: 1,
backgroundColor: "#fff",
},
content: {
flex: 1,
},
});

View File

@@ -3,17 +3,23 @@ import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";
import HomeScreen from "../screens/HomeScreen";
import ProductListBig from "../screens/product/ProductListBig";
import ProductList from "../screens/product/ProductList";
import ProductDetail from "../screens/product/ProductDetail";
const Stack = createStackNavigator();
const AppNavigator: React.FC = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" />
<Stack.Navigator
initialRouteName="homepage"
screenOptions={{ headerShown: false }}
>
<Stack.Screen name="homepage" component={HomeScreen} />
<Stack.Screen name="productlist" component={ProductListBig} />
<Stack.Screen name="productlistmain" component={ProductList} />
<Stack.Screen name="productdetail" component={ProductDetail} />
</Stack.Navigator>
</NavigationContainer>
);
};

View File

@@ -1,5 +1,6 @@
// src/screens/HomeScreen.tsx
import React, { useState } from "react";
import { useNavigation, NavigationProp } from "@react-navigation/native";
import {
View,
Text,
@@ -16,21 +17,37 @@ import Swiper from "react-native-swiper";
import { Ionicons } from "@expo/vector-icons";
import { products } from "../data/product";
import ProductItem from "../components/product/ItemProduct";
import ProductItemSave from "../components/product/itemProductSave";
import { categories } from "../data/category";
import chunk from "lodash/chunk";
import { globalAgent } from "http";
import { WebView } from "react-native-webview";
import AppLayout from "../layouts/AppLayout";
import Footer from "../components/footer/Footer";
var winWidth = Dimensions.get("window").width; //full width
var winHeight = Dimensions.get("window").height; //full height
const ratio = winWidth / 930;
const HomeScreen: React.FC = () => {
const navigation = useNavigation<NavigationProp<any>>();
return (
<SafeAreaView style={styles.container}>
<AppLayout activeTab="homepage">
<ScrollView>
<SliderHome />
<BoxMenuHome />
<BoxProductReviewTop />
<BoxCategoryHome />
<BoxBusiness />
<BoxProductSaveHome />
<BoxArticleBranch />
<BoxArticleSale />
<BoxArticleClassifieds />
<BoxBannerSaleHome />
<BoxPromotionHome />
<Footer navigation={navigation} />
</ScrollView>
</SafeAreaView>
</AppLayout>
);
};
@@ -64,28 +81,28 @@ const SliderHome = () => {
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
resizeMode="cover"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
resizeMode="cover"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
resizeMode="cover"
/>
</View>
<View style={styles.imgContainer}>
<Image
style={styles.imgSlider}
source={require("../../assets/images/banner_slider.png")}
resizeMode="contain"
resizeMode="cover"
/>
</View>
</Swiper>
@@ -230,11 +247,30 @@ const BoxProductReviewTop = () => {
};
const BoxCategoryHome = () => {
const chunkedItems = chunk(categories, 8);
return (
<View style={globalStyles.boxCategoryHome}>
<Text style={globalStyles.textBoxCategoryHome}>Tìm theo danh mục</Text>
<View style={globalStyles.listCategoryBox}>
{categories.map((item) => (
<Swiper
style={globalStyles.listCategoryBox}
loop={false}
showsPagination={true}
dot={<View style={globalStyles.dot} />}
activeDot={<View style={globalStyles.activeDot} />}
paginationStyle={{
bottom: 10,
}}
>
{chunkedItems.map((group, slideIndex) => (
<View
key={`slide-${slideIndex}`}
style={{
flexDirection: "row",
flexWrap: "wrap",
}}
>
{group.map((item) => (
<TouchableOpacity key={item.id} style={globalStyles.categoryItem}>
<View style={globalStyles.boxImageCategory}>
<Image
@@ -246,6 +282,604 @@ const BoxCategoryHome = () => {
</TouchableOpacity>
))}
</View>
))}
</Swiper>
</View>
);
};
const BoxBusiness = () => {
return (
<View style={globalStyles.BoxBusiness}>
<Text style={globalStyles.textBoxBusiness}>DOANH NGHIỆP NỔI BẬT</Text>
<Swiper
style={globalStyles.sliderBusinesses}
autoplay={false}
showsPagination={true}
dot={<View style={globalStyles.dot} />}
activeDot={<View style={globalStyles.activeDot} />}
paginationStyle={{
bottom: 10,
}}
>
<View
style={{
flexDirection: "row",
paddingHorizontal: 10,
}}
>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-anphat.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-gearvn.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
</View>
<View
style={{
flexDirection: "row",
paddingHorizontal: 10,
}}
>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-hacom.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-anphat.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.logoItem}>
<Image
source={require("../../assets/images/logo-gearvn.png")}
style={globalStyles.logoImage}
/>
</TouchableOpacity>
</View>
</Swiper>
</View>
);
};
const BoxProductSaveHome = () => {
return (
<View style={globalStyles.BoxProductSaveHome}>
<Text style={globalStyles.textBoxProductSaveHome}>SẢN PHẨM ĐÃ LƯU</Text>
<View style={globalStyles.listProduct}>
{products.map((item) => (
<ProductItemSave key={item.id} product={item} />
))}
</View>
</View>
);
};
const BoxArticleBranch = () => {
return (
<View style={globalStyles.BoxArticleBranch}>
<Text style={globalStyles.textBoxArticleBranche}>THÔNG TIN NGÀNH</Text>
<View style={globalStyles.boxListArticle}>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Xu hướng công nghệ nhìn từ CES 2025
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={2}>
Loạt sản phẩm trưng bày tại CES 2025 cho thấy AI xu hướng chính
nhưng đưc...
</Text>
<View style={globalStyles.itemArtTime}>
<View style={globalStyles.flex}>
<Ionicons name="time" size={16} color="#5a5a5a" />
<Text style={globalStyles.itemArtDate}>12/03/2025</Text>
</View>
<View style={globalStyles.flex}>
<Ionicons
name="link"
size={16}
style={{ marginLeft: 10 }}
color="#5a5a5a"
/>
<Text style={globalStyles.itemArtSource}>
Nguồn tin: vnexpress
</Text>
</View>
</View>
</View>
</View>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Xu hướng công nghệ nhìn từ CES 2025
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={2}>
Loạt sản phẩm trưng bày tại CES 2025 cho thấy AI xu hướng chính
nhưng đưc...
</Text>
<View style={globalStyles.itemArtTime}>
<View style={globalStyles.flex}>
<Ionicons name="time" size={16} color="#5a5a5a" />
<Text style={globalStyles.itemArtDate}>12/03/2025</Text>
</View>
<View style={globalStyles.flex}>
<Ionicons
name="link"
size={16}
style={{ marginLeft: 10 }}
color="#5a5a5a"
/>
<Text style={globalStyles.itemArtSource}>
Nguồn tin: vnexpress
</Text>
</View>
</View>
</View>
</View>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Xu hướng công nghệ nhìn từ CES 2025
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={2}>
Loạt sản phẩm trưng bày tại CES 2025 cho thấy AI xu hướng chính
nhưng đưc...
</Text>
<View style={globalStyles.itemArtTime}>
<View style={globalStyles.flex}>
<Ionicons name="time" size={16} color="#5a5a5a" />
<Text style={globalStyles.itemArtDate}>12/03/2025</Text>
</View>
<View style={globalStyles.flex}>
<Ionicons
name="link"
size={16}
style={{ marginLeft: 10 }}
color="#5a5a5a"
/>
<Text style={globalStyles.itemArtSource}>
Nguồn tin: vnexpress
</Text>
</View>
</View>
</View>
</View>
</View>
<TouchableOpacity>
<Text style={globalStyles.btnMore}>Xem tất cả</Text>
</TouchableOpacity>
</View>
);
};
const BoxArticleSale = () => {
return (
<View style={globalStyles.BoxArticleBranch}>
<Text style={globalStyles.textBoxArticleBranche}>
THÔNG TIN khuyến mại
</Text>
<View style={globalStyles.boxListArticle}>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Xu hướng công nghệ nhìn từ CES 2025
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={2}>
Loạt sản phẩm trưng bày tại CES 2025 cho thấy AI xu hướng chính
nhưng đưc...
</Text>
<View style={globalStyles.itemArtTime}>
<View style={globalStyles.flex}>
<Ionicons name="time" size={16} color="#5a5a5a" />
<Text style={globalStyles.itemArtDate}>12/03/2025</Text>
</View>
<View style={globalStyles.flex}>
<Ionicons
name="link"
size={16}
style={{ marginLeft: 10 }}
color="#5a5a5a"
/>
<Text style={globalStyles.itemArtSource}>
Nguồn tin: vnexpress
</Text>
</View>
</View>
</View>
</View>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Xu hướng công nghệ nhìn từ CES 2025
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={2}>
Loạt sản phẩm trưng bày tại CES 2025 cho thấy AI xu hướng chính
nhưng đưc...
</Text>
<View style={globalStyles.itemArtTime}>
<View style={globalStyles.flex}>
<Ionicons name="time" size={16} color="#5a5a5a" />
<Text style={globalStyles.itemArtDate}>12/03/2025</Text>
</View>
<View style={globalStyles.flex}>
<Ionicons
name="link"
size={16}
style={{ marginLeft: 10 }}
color="#5a5a5a"
/>
<Text style={globalStyles.itemArtSource}>
Nguồn tin: vnexpress
</Text>
</View>
</View>
</View>
</View>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Xu hướng công nghệ nhìn từ CES 2025
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={2}>
Loạt sản phẩm trưng bày tại CES 2025 cho thấy AI xu hướng chính
nhưng đưc...
</Text>
<View style={globalStyles.itemArtTime}>
<View style={globalStyles.flex}>
<Ionicons name="time" size={16} color="#5a5a5a" />
<Text style={globalStyles.itemArtDate}>12/03/2025</Text>
</View>
<View style={globalStyles.flex}>
<Ionicons
name="link"
size={16}
style={{ marginLeft: 10 }}
color="#5a5a5a"
/>
<Text style={globalStyles.itemArtSource}>
Nguồn tin: vnexpress
</Text>
</View>
</View>
</View>
</View>
</View>
<TouchableOpacity>
<Text style={globalStyles.btnMore}>Xem tất cả</Text>
</TouchableOpacity>
</View>
);
};
const BoxArticleClassifieds = () => {
return (
<View style={globalStyles.BoxArticleBranch}>
<Text style={globalStyles.textBoxArticleBranche}>THÔNG TIN rao vặt</Text>
<View style={globalStyles.boxListArticle}>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Mac air màu xám 256GB like new
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={1}>
Intel Core i5 / 8GB / 256GB / SSD
</Text>
<Text style={globalStyles.itemArtPrice}>19.999.000đ</Text>
<View style={globalStyles.itemArtlocal}>
<Ionicons name="location-sharp" size={16} color="#5a5a5a" />
<Text style={globalStyles.textArtlocal}>Quận thanh xuân</Text>
</View>
<View style={globalStyles.itemArtUploadtime}>
<Ionicons
name="time"
size={16}
style={{ marginRight: 3 }}
color="#5a5a5a"
/>
<Text>30 phút trước </Text>
<Text>|</Text>
<Text> Còn 05 ngày</Text>
</View>
<View style={globalStyles.itemArtUser}>
<Image
source={require("../../assets/images/avartar.png")}
style={globalStyles.imgAvatar}
alt="anh tin tuc"
/>
<Text style={globalStyles.itemArtNameUser}>Thanh mai</Text>
</View>
</View>
</View>
<View style={globalStyles.itemArticle}>
<TouchableOpacity style={globalStyles.itemArticleImg}>
<Image
source={require("../../assets/images/image-article.png")}
style={globalStyles.imgArticle}
alt="anh tin tuc"
/>
</TouchableOpacity>
<View style={globalStyles.infoArticle}>
<TouchableOpacity>
<Text style={globalStyles.itemArtName} numberOfLines={1}>
Mac air màu xám 256GB like new
</Text>
</TouchableOpacity>
<Text style={globalStyles.itemArtSummary} numberOfLines={1}>
Intel Core i5 / 8GB / 256GB / SSD
</Text>
<Text style={globalStyles.itemArtPrice}>19.999.000đ</Text>
<View style={globalStyles.itemArtlocal}>
<Ionicons name="location-sharp" size={16} color="#5a5a5a" />
<Text style={globalStyles.textArtlocal}>Quận thanh xuân</Text>
</View>
<View style={globalStyles.itemArtUploadtime}>
<Ionicons
name="time"
size={16}
style={{ marginRight: 3 }}
color="#5a5a5a"
/>
<Text>30 phút trước </Text>
<Text>|</Text>
<Text> Còn 05 ngày</Text>
</View>
<View style={globalStyles.itemArtUser}>
<Image
source={require("../../assets/images/avartar.png")}
style={globalStyles.imgAvatar}
alt="anh tin tuc"
/>
<Text style={globalStyles.itemArtNameUser}>Thanh mai</Text>
</View>
</View>
</View>
</View>
<TouchableOpacity>
<Text style={globalStyles.btnMore}>Xem tất cả</Text>
</TouchableOpacity>
</View>
);
};
const BoxBannerSaleHome = () => {
return (
<View style={globalStyles.BoxBannerSaleHome}>
<Swiper
style={globalStyles.sliderBannerSale}
autoplay={false}
showsPagination={true}
dot={<View style={globalStyles.dot} />}
activeDot={<View style={globalStyles.activeDot} />}
paginationStyle={{
bottom: 10,
}}
>
<View style={{ flexDirection: "row" }}>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-1.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-2.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
<View style={{ flexDirection: "row" }}>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-2.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-3.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
<View style={{ flexDirection: "row" }}>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-3.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemBannerSale}>
<Image
source={require("../../assets/images/banner-sale-1.png")}
style={globalStyles.imgBannerSale}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
</Swiper>
</View>
);
};
const BoxPromotionHome = () => {
const [showVideo, setShowVideo] = useState(false);
const videoId = "LcQBXGLWX8I";
const embedUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
return (
<View style={globalStyles.BoxPromotionHome}>
<Text style={globalStyles.textBoxArticleBranche}>Sự kiện</Text>
<Text style={globalStyles.textBoxArticleBranche}>
chương trình khuyến mãi
</Text>
<View style={globalStyles.containerVideo}>
{showVideo ? (
<WebView
source={{ uri: embedUrl }}
style={globalStyles.boxVideo}
allowsFullscreenVideo
/>
) : (
<TouchableOpacity
onPress={() => setShowVideo(true)}
style={globalStyles.touchArea}
>
<View style={globalStyles.overlay} />
<Image
source={require("../../assets/images/banner-sale-2.png")}
style={globalStyles.imageYoutobe}
resizeMode="cover"
/>
<Ionicons
name="logo-youtube"
size={34}
color="red"
style={globalStyles.iconPlay}
/>
<View style={globalStyles.infoVideo}>
<View style={globalStyles.flex}>
<Ionicons name="time-outline" size={20} color="white" />
<Text style={{ color: "#fff", marginLeft: 5 }}>
10pm 25/02/2025
</Text>
</View>
<Text style={{ color: "#fff", marginTop: 4, fontWeight: 700 }}>
Showroom laptop 2025 tại Nội{" "}
</Text>
</View>
</TouchableOpacity>
)}
</View>
<View style={globalStyles.listEvenBanner}>
<TouchableOpacity style={globalStyles.itemEven}>
<Image
source={require("../../assets/images/promotions-1.png")}
style={globalStyles.imgEven}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemEven}>
<Image
source={require("../../assets/images/promotions-2.png")}
style={globalStyles.imgEven}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemEven}>
<Image
source={require("../../assets/images/promotions-3.png")}
style={globalStyles.imgEven}
alt="anh tin tuc"
/>
</TouchableOpacity>
<TouchableOpacity style={globalStyles.itemEven}>
<Image
source={require("../../assets/images/promotions-4.png")}
style={globalStyles.imgEven}
alt="anh tin tuc"
/>
</TouchableOpacity>
</View>
</View>
);
};
@@ -253,33 +887,33 @@ const BoxCategoryHome = () => {
export default HomeScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff",
},
homePage: {
backgroundColor: "#fff",
},
slider: {
width: winWidth,
padding: 10,
width: winWidth - 20,
overflow: "hidden",
borderRadius: 10,
height: ratio * 430,
margin: 10,
},
sliderSwipper: {
height: 210,
width: "100%",
overflow: "hidden",
borderRadius: 10,
},
imgContainer: {
width: winWidth,
height: 220,
width: "100%",
borderRadius: 10,
overflow: "hidden",
objectFit: "cover",
},
imgSlider: {
width: winWidth,
height: "100%",
objectFit: "cover",
overflow: "hidden",
borderRadius: 10,
},
buttonNext: {
color: "#fff", // Màu chữ cho các nút chuyển

View File

@@ -0,0 +1,446 @@
import React, { useState } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
Image,
TouchableOpacity,
ScrollView,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import ReviewModal from "@components/product/FormReview";
const reviews = [
{
id: "1",
name: "Dino",
time: "10:00pm 20/02/2025",
title: "Laptop Gaming Asus ROG 16GB",
content:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
images: [
require("../../../assets/images/small-product-detail.png"),
require("../../../assets/images/small-product-detail.png"),
require("../../../assets/images/small-product-detail.png"),
],
star: 5,
},
{
id: "2",
name: "Dino",
time: "10:00pm 20/02/2025",
title: "Laptop Gaming Asus ROG 16GB",
content:
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
images: [
require("../../../assets/images/small-product-detail.png"),
require("../../../assets/images/small-product-detail.png"),
],
star: 4,
},
];
const renderStars = (count: number) => {
return (
<View style={{ flexDirection: "row" }}>
{Array.from({ length: 5 }).map((_, index) => (
<Ionicons
key={index}
name="star"
size={16}
color={index < count ? "#ff7a00" : "#d9d9d9"}
/>
))}
</View>
);
};
const ReviewSection = () => {
const [showReviewModal, setShowReviewModal] = useState(false);
return (
<View>
<View style={styles.BoxReview}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Thành viên BestPC đánh giá</Text>
</View>
<View style={styles.content}>
{/* Left */}
<View style={styles.left}>
<Text style={styles.totalReview}>54 đánh giá</Text>
<View style={styles.ratingRow}>
<Text style={styles.star}>
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#d9d9d9" />
</Text>
<Text style={styles.ratingText}>4.8</Text>
</View>
{/* Rating breakdown */}
<View style={styles.ratingBreakdown}>
{[5, 4, 3, 2, 1].map((star) => (
<View key={star} style={styles.ratingLine}>
{renderStars(star)}
<Text>{50} đánh giá</Text>
</View>
))}
</View>
{/* Button */}
<View style={styles.reviewBtnArea}>
<Text style={styles.reviewPrompt}>
Đánh giá của bạn về sản phẩm
</Text>
<TouchableOpacity
style={styles.reviewBtn}
onPress={() => setShowReviewModal(true)}
>
<Text style={styles.reviewBtnText}>Gửi đánh giá của bạn</Text>
</TouchableOpacity>
</View>
</View>
{/* Right */}
<View style={styles.right}>
<FlatList
data={reviews}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.reviewItem}>
<View style={styles.avatarBox}>
<Image
source={require("../../../assets/images/avartar-review-1.png")}
style={styles.avatar}
/>
<View>
<View
style={{ flexDirection: "row", alignItems: "center" }}
>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.time}>{item.time}</Text>
</View>
<Text style={styles.star}>{renderStars(item.star)}</Text>
</View>
</View>
<View style={styles.reviewContent}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.contentText}>{item.content}</Text>
<View style={styles.imageList}>
{item.images.map((img, index) => (
<Image
key={index}
source={img}
style={styles.reviewImage}
/>
))}
</View>
<View style={styles.btnRow}>
<TouchableOpacity>
<Text style={styles.btnLink}>
Bình luận <Text style={styles.black}>(23)</Text>
</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text style={styles.btnLink}>
Thích <Text style={styles.black}>(23)</Text>
</Text>
</TouchableOpacity>
</View>
</View>
</View>
)}
/>
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
}}
>
<Text style={styles.moreBtn}>
Xem thêm <Text style={styles.moreBold}>23</Text> bình luận khác
</Text>
<Ionicons
name="chevron-down"
style={{ marginLeft: 5 }}
size={16}
color="#FF7A00"
/>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.BoxReview}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Đánh giá trên internet</Text>
</View>
<View style={styles.content}>
{/* Left */}
<View style={styles.left}>
<Text style={styles.totalReview}>54 đánh giá</Text>
<View style={styles.ratingRow}>
<Text style={styles.star}>
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#ff7a00" />
<Ionicons name="star" size={20} color="#d9d9d9" />
</Text>
<Text style={styles.ratingText}>4.8</Text>
</View>
{/* Rating breakdown */}
<View style={styles.ratingBreakdown}>
{[5, 4, 3, 2, 1].map((star) => (
<View key={star} style={styles.ratingLine}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Text>Hanoicomputer</Text>
<Text style={{ marginLeft: 5, color: "#1877f2" }}>
({50})
</Text>
</View>
{renderStars(star)}
</View>
))}
</View>
{/* Button */}
<View style={styles.reviewBtnArea}>
<Text style={styles.reviewPrompt}>
Đánh giá của bạn về sản phẩm
</Text>
<TouchableOpacity
style={styles.reviewBtn}
onPress={() => setShowReviewModal(true)}
>
<Text style={styles.reviewBtnText}>Gửi đánh giá của bạn</Text>
</TouchableOpacity>
</View>
</View>
{/* Right */}
<View style={styles.right}>
<FlatList
data={reviews}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.reviewItem}>
<View style={styles.avatarBox}>
<View>
<View
style={{ flexDirection: "row", alignItems: "center" }}
>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.time}>{item.time}</Text>
</View>
<Text style={styles.star}>{renderStars(item.star)}</Text>
</View>
</View>
<View style={styles.reviewContent}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.contentText}>{item.content}</Text>
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
}}
>
<Text style={{ marginRight: 5 }}>Xem link nguồn</Text>
<Ionicons name="share-social" size={24} color="black" />
</TouchableOpacity>
</View>
</View>
)}
/>
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
}}
>
<Text style={styles.moreBtn}>
Xem thêm <Text style={styles.moreBold}>23</Text> bình luận khác
</Text>
<Ionicons
name="chevron-down"
style={{ marginLeft: 5 }}
size={16}
color="#FF7A00"
/>
</TouchableOpacity>
</View>
</View>
</View>
<ReviewModal
visible={showReviewModal}
onClose={() => setShowReviewModal(false)}
/>
</View>
);
};
const styles = StyleSheet.create({
BoxReview: {
padding: 15,
backgroundColor: "#fff",
marginTop: 20,
borderRadius: 8,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
borderBottomWidth: 1,
borderBottomColor: "#B1B1B1",
paddingBottom: 15,
},
headerTitle: {
fontSize: 16,
fontWeight: "bold",
},
sortBox: {
borderWidth: 1,
borderColor: "#B1B1B1",
paddingHorizontal: 10,
height: 45,
justifyContent: "center",
borderRadius: 4,
},
content: {
gap: 25,
marginTop: 20,
},
left: {
width: "100%",
},
totalReview: {
textAlign: "center",
fontSize: 22,
fontWeight: "bold",
paddingBottom: 5,
},
ratingRow: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
marginBottom: 10,
},
star: {
fontSize: 16,
},
ratingText: {
marginLeft: 5,
fontSize: 16,
fontWeight: 700,
},
ratingBreakdown: {
borderTopWidth: 1,
borderTopColor: "#E4E4E4",
paddingTop: 10,
},
ratingLine: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 10,
},
reviewBtnArea: {
borderTopWidth: 1,
borderTopColor: "#E4E4E4",
paddingTop: 10,
marginTop: 20,
},
reviewPrompt: {
textAlign: "center",
marginTop: 10,
},
reviewBtn: {
marginTop: 10,
height: 40,
backgroundColor: "#FF7A00",
justifyContent: "center",
alignItems: "center",
borderRadius: 4,
},
reviewBtnText: {
color: "#fff",
},
right: {
width: "100%",
},
reviewItem: {
marginBottom: 15,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: "rgba(228, 228, 228, 1)",
},
avatarBox: {
flexDirection: "row",
alignContent: "center",
width: "100%",
alignItems: "center",
marginRight: 20,
},
avatar: {
width: 30,
height: 30,
borderRadius: 40,
marginRight: 10,
},
name: {
fontWeight: "bold",
marginRight: 10,
},
time: {
fontSize: 12,
textAlign: "center",
},
reviewContent: {
flex: 1,
},
title: {
fontWeight: "600",
marginBottom: 5,
},
contentText: {
marginBottom: 5,
},
imageList: {
flexDirection: "row",
marginBottom: 10,
},
reviewImage: {
width: 80,
height: 80,
marginRight: 10,
},
btnRow: {
flexDirection: "row",
},
btnLink: {
color: "#1877F2",
marginRight: 15,
},
black: {
color: "#000",
},
moreBtn: {
color: "#FF7A00",
},
moreBold: {
fontWeight: "bold",
},
});
export default ReviewSection;

View File

@@ -0,0 +1,364 @@
import React, { useState } from "react";
import { useNavigation, NavigationProp } from "@react-navigation/native";
import AppLayout from "@layouts/AppLayout";
import {
View,
Text,
StyleSheet,
Image,
FlatList,
TouchableOpacity,
ScrollView,
Dimensions,
} from "react-native";
import { globalStyles } from "styles/globalStyles";
import { Ionicons } from "@expo/vector-icons";
import Feather from "@expo/vector-icons/Feather";
import Swiper from "react-native-swiper";
const { width } = Dimensions.get("window");
import ProductGallery from "@components/product/ProductGallery";
import SupplierList from "./SupplierList";
import ReviewSection from "./BoxReview";
import ProductInformation from "./ProductInformation";
import ProductSpecification from "./ProductSpecification";
import { products } from "../../data/product";
import ProductItem from "@components/product/ItemProduct";
import Footer from "@components/footer/Footer";
const ProductDetail = () => {
const navigation = useNavigation<NavigationProp<any>>();
return (
<AppLayout activeTab="productdetail">
<ScrollView>
<View style={styles.container}>
{/* Breadcrumb */}
<View style={globalStyles.breadcrumb}>
<TouchableOpacity
style={globalStyles.breadcrumbItem}
onPress={() => navigation.navigate("homepage" as never)}
>
<Ionicons name="home-outline" size={20} color="#637381" />
</TouchableOpacity>
<Ionicons name="chevron-forward-outline" size={14} color="#999" />
<Text style={[globalStyles.breadcrumbText, { fontWeight: "600" }]}>
Màn hình máy tính
</Text>
</View>
<View
style={{
padding: 10,
backgroundColor: "#fff",
marginTop: 15,
borderRadius: 4,
}}
>
<View style={styles.top}>
<ProductGallery />
</View>
<View style={styles.bottom}>
<Text style={styles.productTitle}>
Laptop Gaming Asus TUF FX505GE-BQ037T Core i7-8750H/Win10(15.6"
FHD) - Hàng Chính Hãng
</Text>
<View style={styles.row}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name="star"
style={{ marginRight: 3, paddingTop: 3 }}
size={15}
color="#ff7a00"
/>
<Text style={styles.review}>5/5</Text>
</View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name="eye-outline"
style={{ marginRight: 3 }}
size={15}
color="black"
/>
<Text style={styles.view}>120</Text>
</View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name="time-outline"
style={{ marginRight: 3 }}
size={15}
color="black"
/>
<Text style={styles.date}>12/03/2025</Text>
</View>
<TouchableOpacity
style={{ flexDirection: "row", alignItems: "center" }}
>
<Ionicons
style={{ marginRight: 3 }}
name="share-social-outline"
size={15}
color="black"
/>
<Text style={styles.share}> Chia sẻ</Text>
</TouchableOpacity>
</View>
<View style={styles.row}>
<Text style={{ fontSize: 13 }}>
Reviews trên: <Text style={styles.link}>Internet</Text> - 1233
đánh giá
</Text>
<Text style={{ fontSize: 13 }}>
| <Text style={styles.link}>BestPC</Text> - 12003 đánh giá
</Text>
</View>
<View style={styles.priceBox}>
<Text style={styles.priceLabel}>Giá: </Text>
<Text style={styles.price}>9.000.000đ - 12.000.000đ</Text>
</View>
<View style={styles.boxStore}>
<View style={styles.boxIconStore}>
<Image
source={require("../../../assets/images/icon_store_white.png")}
style={styles.iconStore}
alt="icon store"
resizeMode="contain"
/>
</View>
<Text style={styles.storeName}>Có 12 cửa hàng bán</Text>
</View>
<View style={styles.summaryBox}>
<Text>
• CPU: Intel Core i7-8750H (2.2GHz - 4.1GHz / 9MB / 6 nhân, 12
luồng)
</Text>
<Text>• Màn hình: 15.6" (1920 x 1080), không cảm ng</Text>
<Text> RAM: 1 x 8GB DDR4 2666MHz</Text>
<Text> GPU: Intel UHD 630 / NVIDIA GTX 1050Ti 4GB</Text>
<Text> Lưu trữ: 128GB SSD M.2 NVMe / 1TB HDD</Text>
<Text> OS: Windows 10 Home SL 64-bit</Text>
<Text> Pin: 4 cell 64Wh, liền</Text>
<Text> Nặng: 2.5kg</Text>
<Text> Cổng: 1x USB 2.0,...</Text>
</View>
<TouchableOpacity style={styles.saveButton}>
<Feather name="file-plus" size={24} color="black" />
<Text style={styles.saveText}>Lưu sản phẩm lại xem sau</Text>
</TouchableOpacity>
</View>
</View>
{/* nhà cung cấp */}
<SupplierList />
{/* đánh giá */}
<ReviewSection />
{/* mô tả sản phẩm */}
<ProductInformation />
{/* thông số sản phẩm */}
<ProductSpecification />
{/* sản phẩm tương tự */}
<View style={styles.BoxSimilarProduct}>
<Text style={styles.textSimilarProduct}>Sản phẩm tương tự</Text>
<View style={styles.listProductSimilar}>
{products.map((item) => (
<ProductItem key={item.id} product={item} />
))}
</View>
<TouchableOpacity style={{ marginTop: 10, marginBottom: 10 }}>
<Text style={globalStyles.moreAll}>Xem tất cả</Text>
</TouchableOpacity>
</View>
{/* sản phẩm bạn thích */}
<View style={styles.BoxLikeProduct}>
<Text style={styles.textLikeProduct}>
Sản phẩm thể bạn thích
</Text>
<View style={styles.listProductLike}>
{products.map((item) => (
<ProductItem key={item.id} product={item} />
))}
</View>
<TouchableOpacity style={{ marginTop: 10, marginBottom: 10 }}>
<Text style={globalStyles.moreAll}>Xem tất cả</Text>
</TouchableOpacity>
</View>
</View>
<Footer navigation={navigation} />
</ScrollView>
</AppLayout>
);
};
export default ProductDetail;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 10,
backgroundColor: "#efefef",
paddingBottom: 10,
},
top: {},
bigSwiper: {
height: 250,
},
slide: {
justifyContent: "center",
alignItems: "center",
height: 250,
},
bigImage: {
width: "100%",
height: "100%",
},
thumbnailWrapper: {
marginTop: 12,
flexDirection: "row",
},
thumbItem: {
marginRight: 10,
},
thumbImage: {
width: 60,
height: 60,
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 8,
},
bottom: {
marginTop: 20,
},
productTitle: {
fontSize: 16,
fontWeight: "bold",
color: "#000",
marginBottom: 10,
},
row: {
flexDirection: "row",
flexWrap: "wrap",
marginBottom: 5,
gap: 10,
},
review: { marginRight: 10 },
view: { marginRight: 10, color: "#1877f2" },
date: { marginRight: 10 },
share: {},
link: {
color: "#1877F2",
fontWeight: "600",
},
priceBox: {
flexDirection: "row",
alignItems: "center",
marginVertical: 10,
},
priceLabel: { fontSize: 16 },
price: {
fontSize: 24,
color: "#D80A00",
fontWeight: "bold",
},
storeBox: {
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
},
summaryBox: {
marginTop: 10,
},
saveButton: {
flexDirection: "row",
alignItems: "center",
marginTop: 15,
padding: 10,
borderWidth: 1,
borderColor: "#D3D3D3",
borderRadius: 4,
justifyContent: "center",
},
saveIcon: {
width: 18,
height: 24,
marginRight: 10,
},
saveText: {
fontSize: 16,
fontWeight: "600",
},
boxStore: {
flexDirection: "row",
alignItems: "center",
marginTop: 5,
},
boxIconStore: {
width: 24,
height: 24,
borderRadius: 25,
backgroundColor: "#FF7A00",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
marginRight: 5,
},
iconStore: {
width: 18,
height: 18,
},
storeName: {
fontSize: 14,
fontWeight: 400,
color: "#000",
},
BoxSimilarProduct: {
backgroundColor: "#fff",
marginTop: 15,
borderRadius: 8,
},
textSimilarProduct: {
flexDirection: "row",
justifyContent: "space-between",
borderBottomWidth: 1,
borderBottomColor: "#B1B1B1",
padding: 10,
fontSize: 16,
fontWeight: "bold",
},
listProductSimilar: {
flexDirection: "row",
flexWrap: "wrap",
gap: 5,
marginLeft: 7,
marginTop: 10,
},
BoxLikeProduct: {
backgroundColor: "#fff",
marginTop: 15,
borderRadius: 8,
},
textLikeProduct: {
flexDirection: "row",
justifyContent: "space-between",
borderBottomWidth: 1,
borderBottomColor: "#B1B1B1",
padding: 10,
fontSize: 16,
fontWeight: "bold",
},
listProductLike: {
flexDirection: "row",
flexWrap: "wrap",
gap: 5,
marginLeft: 7,
marginTop: 10,
},
});

View File

@@ -0,0 +1,252 @@
import React, { useState } from "react";
import {
View,
Text,
ScrollView,
Image,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import ProductHTMLContent from "@components/product/ProductHTMLContent";
const productHtml = `
<p style="text-align: justify;"><strong>Laptop HP Victus 16 R0376TX AY8Z2PA
</strong> sở
hữu chip xử lý Intel Core i7
13700HX cùng VGA RTX 3050 6GB để có được trải nghiệm chơi game, làm việc hiệu
quả.
Mẫu <a href="https://cellphones.com.vn/laptop/hp/victus.html" title="HP Victus"
target="_blank"><strong>HP
Victus</strong></a> này còn được trang bị màn hình 16 inch FHD 165Hz
giúp
tái tạo hình ảnh sắc nét, mượt mà.
Kèm theo đó là thiết kế bền bỉ, hiện đại để có thể sử dụng ở nhiều không gian.
</p>
<h2 style="scroll-margin : 154px;"
id="laptop-hp-victus-16-r0376tx-ay8z2pa-hieu-nang-manh-me-hien-thi-an-tuong">
<strong>
Laptop HP Victus 16 R0376TX AY8Z2PA - Hiệu năng mạnh mẽ, hiển thị ấn tượng
</strong>
</h2>
<p style="text-align: justify;">HP Victus 16 R0376TX AY8Z2PA với bộ vi xử lý và card
đồ
họa rời mạnh mẽ sẽ mang tới trải
nghiệm chơi game, làm việc mượt mà, hiệu quả. Hãy cùng tìm hiểu thêm những điểm
nổi
bật mà mẫu laptop gaming của
<strong> HP Victus </strong> này sở hữu trong bài viết sau.
</p>
<h3 style="scroll-margin : 154px;"
id="nang-cao-hieu-suat-xu-ly-voi-chip-core-i7-13700hx"><strong> Nâng cao hiệu
suất
xử
lý với chip&nbsp;Core i7 13700HX</strong></h3>
<p style="text-align: justify;">Laptop HP Victus 16 R0376TX AY8Z2PA được trang bị
con
chip xử lý Core i7 13700HX của
Intel. Con chip Core i7 16 lõi, 24 luồng này sở hữu khả năng ép xung cực đại lên
tới
5GHz để luôn có thể xử lý dữ
liệu trong thời gian ngắn. Với bộ nhớ đệm 30MB, hiệu suất xử lý các tác vụ phức
tạp
của laptop sẽ được giảm đi đáng
kể.</p>
<p style="text-align: justify;"><img
src="https://cdn2.cellphones.com.vn/insecure/rs:fill:0:0/q:90/plain/https://cellphones.com.vn/media/wysiwyg/laptop/HP/Victus/Laptop-hp-victus-16-r0376tx-ay8z2pa-1.jpg"
alt="Cấu hình laptop HP Victus 16 R0376TX AY8Z2PA" loading="lazy"></p>
<p style="text-align: justify;">Để nâng cao khả năng xử lý đồ hoạ, HP còn trang bị
cho
phiên bản <a href="https://cellphones.com.vn/laptop/hp/victus/victus-16.html"
title="laptop Victus 16" target="_blank"><strong>laptop Victus
16</strong></a>
này card đồ hoạ rời NVIDIA GeForce RTX 3050 6GB GDDR6. Nhờ
vậy mà người dùng có thể tự tin trải nghiệm các tựa game với mức cấu hình cao.
Không
dừng ở đó, các nhu cầu chỉnh
sửa hình ảnh, dựng phim, tạo hình 3D,...chuyên nghiệp cũng được thực hiện mượt
mà.
</p>
<h3 style="scroll-margin : 154px;"
id="tich-hop-o-cung-toc-do-cao-cung-dung-luong-ram-lon"><strong> Tích hợp ổ cứng
tốc
độ cao cùng dung lượng RAM lớn </strong></h3>
<p style="text-align: justify;">HP Victus 16 R0376TX AY8Z2PA được trang bị sẵn 2
thanh
RAM chuẩn DDR5 có tốc độ bus lên
tới 4800MT/s. Với tổng dung lượng RAM 16GB, mẫu laptop gaming HP này sẽ đáp ứng
tốt
nhu cầu đa nhiệm. Không chỉ các
tác vụ cơ bản mà nhu cầu đa nhiệm nâng cao trong công việc cũng sẽ được thực
hiện
mượt mà mà không cần phải nâng cấp
cấu hình. Ngoài ra, laptop HP Victus 16 R0376TX AY8Z2PA còn sở hữu một ổ cứng
SSD
tốc độ cao có dung lượng
512GB.&nbsp;</p>
<p style="text-align: justify;"><img
src="https://cdn2.cellphones.com.vn/insecure/rs:fill:0:0/q:90/plain/https://cellphones.com.vn/media/wysiwyg/laptop/HP/Victus/Laptop-hp-victus-16-r0376tx-ay8z2pa-2.jpg"
alt="Cấu hình laptop HP Victus 16 R0376TX AY8Z2PA" loading="lazy"></p>
<h3 style="scroll-margin : 154px;" id="thiet-ke-hien-dai-ben-bi"><strong> Thiết kế
hiện
đại, bền bỉ </strong></h3>
<p style="text-align: justify;">Laptop Victus 16 R0376TX AY8Z2PA được HP thiết kế
hướng
tới phong cách hiện đại, tối
giản. Tuy vậy nhưng sản phẩm vẫn mang đậm dấu ấn gaming với tông màu đen thu hút
mạnh mẽ. Kèm theo đó là một tổng
thể chắc chắn, bền bỉ cùng phần bản lề kích thước lớn.</p>
<p style="text-align: justify;">Với trọng lượng khoảng 2.31kg, người dùng vẫn có thể
tiện lợi mang theo phiên bản laptop
HP Victus này mà không gặp bất tiện. So với các mẫu laptop gaming khác, đây được
coi
là một điểm cộng giúp sản phẩm
đáp ứng nhu cầu chơi game ở nhiều không gian của người dùng.</p>
<p style="text-align: justify;"><img
src="https://cdn2.cellphones.com.vn/insecure/rs:fill:0:0/q:90/plain/https://cellphones.com.vn/media/wysiwyg/laptop/HP/Victus/Laptop-hp-victus-16-r0376tx-ay8z2pa-3.jpg"
alt="Thiết kế laptop HP Victus 16 R0376TX AY8Z2PA" loading="lazy"></p>
<p style="text-align: justify;">Để hỗ trợ tốt cả nhu cầu chơi game và làm việc, HP
đã
trang bị cho laptop Victus 16
R0376TX AY8Z2PA bộ bàn phím Full-size. Với phần bàn phím số riêng biệt, người
dùng
sẽ cải thiện được tốc độ nhập
liệu trong khi làm việc. Bàn phím của laptop còn được tích hợp hệ thống đèn nền
RGB
1 để hỗ trợ việc gõ phím trong
điều kiện thiếu sáng.</p>
<h3 style="scroll-margin : 154px;" id="hien-thi-sac-net-va-muot-ma"><strong> Hiển
thị
sắc nét và mượt mà </strong></h3>
<p style="text-align: justify;">Laptop HP Victus 16 R0376TX AY8Z2PA sở hữu không
giản
hiển thị cực lớn khi được trang bị
màn hình có kích thước 16.1 inch. Kèm theo đó là độ phân giải FHD (1920 x 1080p)
giúp nội dung hiển thị luôn có được
độ rõ nét. Nhờ việc sử dụng tấm nền IPS, người dùng còn có thể điều chỉnh góc
nghiêng linh hoạt mà không gây ảnh
hưởng tới chất lượng hình ảnh.</p>
<p style="text-align: justify;"><img
src="https://cdn2.cellphones.com.vn/insecure/rs:fill:0:0/q:90/plain/https://cellphones.com.vn/media/wysiwyg/laptop/HP/Victus/Laptop-hp-victus-16-r0376tx-ay8z2pa-4.jpg"
alt="Hiển thị sắc nét và mượt mà" loading="lazy"></p>
<p style="text-align: justify;">Điểm nổi bật có trên màn hình HP Victus 16 R0376TX
AY8Z2PA chính là tần số quét và độ
phủ màu. Với độ phủ màu 100% sRGB, mẫu laptop gaming HP này sẽ hỗ trợ người dùng
làm
các công việc liên quan tới
sáng tạo hiệu quả. Tần số quét ở mức 165Hz sẽ giúp màn hình tái hiện các chuyển
động
một cách uyển chuyển, mượt mà.
</p>
<p style="text-align: justify;">Ngoài ra, màn hình HP Victus 16 R0376TX AY8Z2PA còn
được
phủ lớp chống chói và có độ
sáng 300 nits để thoải mái sử dụng ở những nơi có ánh sáng mạnh. Với chuẩn Low
Blue
Light, màn hình còn giảm thiểu
lượng ánh sáng xanh phát ra để hạn chế tình trạng mỏi mắt khi sử dụng laptop
trong
thời gian dài.</p>
<h3 style="scroll-margin : 154px;"
id="thoi-luong-pin-dai-ket-noi-phong-phu-va-on-dinh">
<strong> Thời lượng pin dài, kết
nối phong phú và ổn định </strong>
</h3>
<p style="text-align: justify;">Laptop HP Victus 16 R0376TX AY8Z2PA được trang bị
viên
pin Li-ion Polymer 4-cell có dung
lượng ở mức 70Wh. Với dung lượng này, viên pin có thể duy trì trạng thái hoạt
động
cho laptop lên tới nhiều giờ với
các tác vụ hỗn hợp. Laptop còn được trang bị bộ nguồn 200W Smart AC power
adapter để
hỗ trợ sạc pin nhanh 50% trong
khoảng 30 phút.</p>
<p style="text-align: justify;"><img
src="https://cdn2.cellphones.com.vn/insecure/rs:fill:0:0/q:90/plain/https://cellphones.com.vn/media/wysiwyg/laptop/HP/Victus/Laptop-hp-victus-16-r0376tx-ay8z2pa-5.jpg"
alt="Thời lượng pin dài, kết nối phong phú và ổn định" loading="lazy"></p>
<p style="text-align: justify;">Để hỗ trợ việc liên kết có dây và chia sẻ dữ liệu,
HP
còn trang bị cho mẫu laptop Victus
của hàng đầy đủ các cổng kết nối thông dụng. Hai cạnh bên laptop được tích hợp
các
cổng USB Type-A, USB Type-C,
RJ-45, HDMI 2.1, jack audio 3.5mm và bộ chuyển đổi điện xoay chiều thông minh.
</p>
<p style="text-align: justify;">Laptop Victus 16 R0376TX AY8Z2PA còn được trang bị
card
wireless Bluetooth 5.3 và Intel
Wi-Fi 6E AX211 (2x2). Điều này sẽ đảm bảo được khả năng kết nối với các thiết bị
ngoại vi, kết nối mạng ổn định,
nhanh chóng cho laptop.</p>
<h2 style="scroll-margin : 154px;"
id="mua-laptop-hp-victus-16-r0376tx-ay8z2pa-gia-tot-tai-cellphones"><strong> Mua
laptop HP Victus 16 R0376TX AY8Z2PA giá tốt tại CellphoneS </strong></h2>
`;
const ProductInformation = () => {
return (
<View style={styles.boxDesciption}>
<Text style={styles.titleDesciption}>Thông tin sản phẩm</Text>
<View style={styles.content}>
<ProductHTMLContent htmlContent={productHtml} />
</View>
</View>
);
};
export default ProductInformation;
const styles = StyleSheet.create({
boxDesciption: {
backgroundColor: "#fff",
marginTop: 15,
borderRadius: 8,
},
titleDesciption: {
flexDirection: "row",
justifyContent: "space-between",
borderBottomWidth: 1,
borderBottomColor: "#B1B1B1",
padding: 10,
fontSize: 16,
fontWeight: "bold",
},
content: {
padding: 10,
},
heading: {
fontSize: 18,
fontWeight: "bold",
marginTop: 20,
marginBottom: 8,
},
subheading: {
fontSize: 16,
fontWeight: "600",
marginTop: 16,
marginBottom: 4,
},
paragraph: {
fontSize: 14,
textAlign: "justify",
marginBottom: 12,
},
image: {
width: "100%",
height: 200,
resizeMode: "contain",
marginBottom: 12,
},
button: {
marginTop: 10,
alignSelf: "center",
},
buttonText: {
color: "#007AFF",
fontSize: 14,
},
});

View File

@@ -0,0 +1,109 @@
import React, { useState } from "react";
import { useNavigation, NavigationProp } from "@react-navigation/native";
import AppLayout from "@layouts/AppLayout";
import {
View,
Text,
StyleSheet,
Image,
FlatList,
TouchableOpacity,
ScrollView,
Dimensions,
} from "react-native";
import { globalStyles } from "styles/globalStyles";
import { Ionicons } from "@expo/vector-icons";
import { Picker } from "@react-native-picker/picker";
import ProductItem from "@components/product/ItemProduct";
import { products } from "../../data/product";
import FilterDropdown from "@components/product/FilterDropdown";
import Footer from "@components/footer/Footer";
const ProductList = () => {
const navigation = useNavigation<NavigationProp<any>>();
const [selectedAddress, setSelectedAddress] = useState("");
const [selectedBrand, setSelectedBrand] = useState("");
return (
<AppLayout activeTab="productlist">
<ScrollView>
<View style={styles.container}>
{/* Breadcrumb */}
<View style={globalStyles.breadcrumb}>
<TouchableOpacity
style={globalStyles.breadcrumbItem}
onPress={() => navigation.navigate("Home" as never)}
>
<Ionicons name="home-outline" size={20} color="#637381" />
</TouchableOpacity>
<Ionicons name="chevron-forward-outline" size={14} color="#999" />
<Text style={[globalStyles.breadcrumbText, { fontWeight: "600" }]}>
Màn hình máy tính
</Text>
</View>
<Text style={styles.nameCategory}>
Máy tính đ bàn, máy tính xách tay linh phụ kiện
</Text>
<View style={styles.boxFilter}>
<Text>Bộ lọc</Text>
<View style={styles.listFilter}>
<FilterDropdown
placeholder="Địa chỉ"
options={["Hà Nội", "TP.HCM", "Đà Nẵng"]}
selected={selectedAddress}
onSelect={setSelectedAddress}
/>
<FilterDropdown
placeholder="Thương hiệu"
options={["Dell", "HP", "Lenovo"]}
selected={selectedBrand}
onSelect={setSelectedBrand}
/>
</View>
</View>
<View style={styles.listProduct}>
{products.map((item) => (
<ProductItem key={item.id} product={item} />
))}
</View>
</View>
<Footer navigation={navigation} />
</ScrollView>
</AppLayout>
);
};
export default ProductList;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 10,
marginBottom: 60,
},
nameCategory: {
fontSize: 16,
fontWeight: 700,
},
listFilter: {
flexDirection: "row",
flexWrap: "wrap",
marginRight: -5,
},
boxFilter: {
marginTop: 10,
paddingLeft: 10,
paddingTop: 10,
backgroundColor: "#f5f5f5",
},
listProduct: {
flexDirection: "row",
flexWrap: "wrap",
paddingTop: 10,
gap: 10,
paddingBottom: 10,
},
});

View File

@@ -0,0 +1,111 @@
import React from "react";
import {
View,
Text,
StyleSheet,
Image,
FlatList,
TouchableOpacity,
ScrollView,
Dimensions,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useNavigation, NavigationProp } from "@react-navigation/native";
import AppLayout from "@layouts/AppLayout";
import { globalStyles } from "styles/globalStyles";
import Footer from "@components/footer/Footer";
const screenWidth = Dimensions.get("window").width;
const numColumns = 2;
const categories = Array.from({ length: 12 }, (_, index) => ({
id: index + 1,
title: "Máy chủ",
image: require("../../../assets/images/category-avatar.png"),
link: "productlistmain",
}));
const ProductListBig = () => {
const navigation = useNavigation<NavigationProp<any>>();
const renderItem = ({ item }: { item: (typeof categories)[0] }) => (
<TouchableOpacity
style={styles.card}
onPress={() => navigation.navigate(item.link as never)}
>
<Image source={item.image} style={styles.image} resizeMode="contain" />
<Text style={styles.title} numberOfLines={1}>
{item.title}
</Text>
</TouchableOpacity>
);
return (
<AppLayout activeTab="productlist">
<ScrollView>
<View style={styles.container}>
{/* Breadcrumb */}
<View style={globalStyles.breadcrumb}>
<TouchableOpacity
style={globalStyles.breadcrumbItem}
onPress={() => navigation.navigate("Home" as never)}
>
<Ionicons name="home-outline" size={20} color="#637381" />
</TouchableOpacity>
<Ionicons name="chevron-forward-outline" size={14} color="#999" />
<Text style={[globalStyles.breadcrumbText, { fontWeight: "600" }]}>
Màn hình máy tính
</Text>
</View>
<FlatList
data={categories}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
numColumns={numColumns}
contentContainerStyle={styles.grid}
columnWrapperStyle={styles.columnWrapper}
showsVerticalScrollIndicator={false}
/>
</View>
<Footer navigation={navigation} />
</ScrollView>
</AppLayout>
);
};
export default ProductListBig;
const itemWidth = screenWidth / numColumns - 17;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 10,
},
grid: {
paddingVertical: 10,
},
columnWrapper: {
justifyContent: "space-between",
marginBottom: 10,
},
card: {
width: itemWidth,
borderWidth: 1,
borderColor: "#c0c0c0",
borderRadius: 8,
padding: 10,
alignItems: "center",
backgroundColor: "#fff",
},
image: {
width: itemWidth,
height: 100,
marginBottom: 10,
},
title: {
textAlign: "center",
fontSize: 16,
fontWeight: "600",
},
});

View File

@@ -0,0 +1,110 @@
import React, { useState } from "react";
import {
View,
Text,
TouchableOpacity,
FlatList,
StyleSheet,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
const MAX_VISIBLE_ROWS = 6;
const specificationData = [
{ label: "Mã sản phẩm", value: "VA3416WC" },
{ label: "Màu sắc", value: "Đen" },
{ label: "Độ sáng", value: "Max 300cd/m²" },
{ label: "Độ tương phản", value: "3000:1" },
{ label: "Màu sắc hiển thị", value: "16.7M" },
{ label: "Màu sắc hỗ trợ", value: "6500K" },
{ label: "Loại màn hình", value: "Cong R1500" },
{ label: "Kích cỡ màn hình", value: '34"' },
{ label: "Tấm nền", value: "VA" },
{ label: "Góc nhìn", value: "89°/89° (H/V)" },
{
label: "Tính năng đặc biệt",
value: `1. Flicker-Free Backlight adjustment\n2. Dynamic Contrast Ratio: DCR\n3. Adaptive Sync\n4. FPS/RTS\n5. PIP/PBP\n6. PQ\n7. GAME PLUS`,
},
];
const ProductSpecification = () => {
const [showAll, setShowAll] = useState(false);
const visibleData = showAll
? specificationData
: specificationData.slice(0, MAX_VISIBLE_ROWS);
return (
<View style={styles.container}>
<Text style={styles.title}>Thông tin chi tiết</Text>
<FlatList
data={visibleData}
keyExtractor={(item, index) => `${item.label}-${index}`}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.label}>{item.label}</Text>
<Text style={styles.value}>{item.value}</Text>
</View>
)}
/>
{specificationData.length > MAX_VISIBLE_ROWS && (
<TouchableOpacity
onPress={() => setShowAll(!showAll)}
style={styles.toggleButton}
>
<Text style={styles.toggleText}>
{showAll ? "Thu gọn" : "Xem thêm thông số"}{" "}
<Ionicons
name={showAll ? "chevron-up-outline" : "chevron-down-outline"}
size={16}
color="#FF7A00"
/>
</Text>
</TouchableOpacity>
)}
</View>
);
};
export default ProductSpecification;
const styles = StyleSheet.create({
container: {
backgroundColor: "#fff",
borderRadius: 8,
padding: 12,
marginTop: 15,
},
title: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 12,
borderBottomWidth: 1,
borderColor: "#B1B1B1",
paddingBottom: 8,
},
row: {
flexDirection: "row",
marginBottom: 8,
},
label: {
flex: 1,
fontWeight: "600",
color: "#333",
},
value: {
flex: 1,
color: "#555",
},
toggleButton: {
marginTop: 12,
alignItems: "center",
},
toggleText: {
color: "#FF7A00",
fontWeight: "600",
fontSize: 14,
flexDirection: "row",
},
});

View File

@@ -0,0 +1,133 @@
import React from "react";
import {
View,
ScrollView,
Text,
StyleSheet,
TextInput,
Dimensions,
} from "react-native";
import SupplierItem from "@components/product/SupplierItem";
import { Ionicons } from "@expo/vector-icons";
import Feather from "@expo/vector-icons/Feather";
const { width } = Dimensions.get("window");
const SupplierList = () => {
const suppliers = [
{
name: "Hanoicomputer",
distance: "3km",
rating: 4.7,
logo: require("../../../assets/images/logo-hacom.png"),
shippingPolicy:
"Miễn phí giao hàng\nNhận giao hàng và lắp đặt từ 8h00-21h30 các ngày trong tuần kể cả Thứ 7, CN",
priceRange: "9.000.000đ - 20.000.000đ",
hasVAT: true,
products: ["8GB - 9.000.000đ new White", "8GB - 9.000.000đ new White"],
},
{
name: "Hanoicomputer",
distance: "3km",
rating: 4.7,
logo: require("../../../assets/images/logo-hacom.png"),
shippingPolicy:
"Miễn phí giao hàng\nNhận giao hàng và lắp đặt từ 8h00-21h30 các ngày trong tuần kể cả Thứ 7, CN",
priceRange: "9.000.000đ - 20.000.000đ",
hasVAT: true,
products: ["8GB - 9.000.000đ new White", "8GB - 9.000.000đ new White"],
},
{
name: "Hanoicomputer",
distance: "3km",
rating: 4.7,
logo: require("../../../assets/images/logo-hacom.png"),
shippingPolicy:
"Miễn phí giao hàng\nNhận giao hàng và lắp đặt từ 8h00-21h30 các ngày trong tuần kể cả Thứ 7, CN",
priceRange: "9.000.000đ - 20.000.000đ",
hasVAT: true,
products: ["8GB - 9.000.000đ new White", "8GB - 9.000.000đ new White"],
},
];
return (
<ScrollView
style={{
padding: 10,
backgroundColor: "#fff",
marginTop: 15,
borderRadius: 4,
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingBottom: 15,
}}
>
<Text style={{ fontSize: 16, fontWeight: "bold" }}>
Nhà cung cấp trên BestPC - Tại Nội
</Text>
</View>
<View style={styles.Form}>
<Feather name="map-pin" size={20} color="#a6a6a6" />{" "}
<TextInput
placeholder="Nhập địa chỉ của bạn để tìm NCC gần nhất"
style={styles.inputMap}
placeholderTextColor="#666"
/>
</View>
{/* List */}
{suppliers.map((item, index) => (
<SupplierItem key={index} {...item} />
))}
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
marginTop: 10,
}}
>
<Text
style={{
textAlign: "center",
color: "#FF7A00",
fontSize: 16,
}}
>
Xem thêm
</Text>
<Ionicons
name="chevron-down"
style={{ marginLeft: 5 }}
size={16}
color="#FF7A00"
/>
</View>
</ScrollView>
);
};
export default SupplierList;
const styles = StyleSheet.create({
Form: {
flexDirection: "row",
alignItems: "center",
position: "relative",
borderWidth: 1,
borderColor: "#b1b1b1",
marginRight: 10,
width: width - 40,
height: 45,
paddingHorizontal: 10,
borderRadius: 4,
},
inputMap: {
width: "100%",
},
});

View File

@@ -1,6 +1,7 @@
// src/styles/globalStyles.ts
import { StyleSheet, Dimensions } from 'react-native';
var winWidth = Dimensions.get("window").width;
const itemWidth = (winWidth - 40) / 4;
export const globalStyles = StyleSheet.create({
boxProductReviewTop: {
@@ -50,7 +51,7 @@ export const globalStyles = StyleSheet.create({
marginBottom: 10,
flexDirection: 'row',
flexWrap: 'wrap',
marginLeft: -10,
gap: 10
},
moreAll: {
width: 110,
@@ -80,9 +81,12 @@ export const globalStyles = StyleSheet.create({
},
listCategoryBox: {
marginTop: 10,
padding: 10,
height: 300
},
categoryItem: {
width: '100%',
width: '25%',
marginBottom: 25
},
boxImageCategory: {
width: 70,
@@ -103,6 +107,283 @@ export const globalStyles = StyleSheet.create({
fontWeight: '500',
marginTop: 5,
textAlign: 'center',
}
},
dot: {
backgroundColor: 'rgba(0,0,0,.2)',
width: 8,
height: 8,
borderRadius: 4,
margin: 3,
},
activeDot: {
backgroundColor: '#ff7a00',
width: 40,
height: 10,
borderRadius: 5,
margin: 3,
},
BoxBusiness: {
marginTop: 25,
},
textBoxBusiness: {
fontSize: 20,
fontWeight: "bold",
color: "#0d0d9c",
marginBottom: 8,
textTransform: "uppercase",
textAlign: "center",
},
sliderBusinesses: {
marginTop: 20,
height: 85
},
logoItem: {
width: itemWidth,
height: 40,
borderRadius: 8,
backgroundColor: '#fff',
justifyContent: 'center',
alignItems: 'center',
marginRight: 10,
shadowColor: '#727272',
shadowOffset: { width: 1, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
},
logoImage: {
width: 80,
height: 30,
resizeMode: 'contain',
},
BoxProductSaveHome: {
marginTop: 20,
width: winWidth,
paddingLeft: 10,
paddingRight: 10,
},
textBoxProductSaveHome: {
fontSize: 20,
fontWeight: "bold",
color: "#0d0d9c",
marginBottom: 8,
textTransform: "uppercase",
textAlign: "center",
},
BoxArticleBranch: {
width: winWidth,
marginTop: 25
},
textBoxArticleBranche: {
fontSize: 20,
fontWeight: "bold",
color: "#111189ff",
marginBottom: 8,
textTransform: "uppercase",
textAlign: "center",
},
boxFlex: {
alignItems: "center",
flexDirection: 'row',
justifyContent: 'center',
margin: 'auto',
},
boxListArticle: {
marginTop: 5,
paddingLeft: 10,
paddingRight: 10,
},
flex: {
alignItems: "center",
flexDirection: 'row',
},
itemArtTime: {
alignItems: "center",
flexDirection: 'row',
},
itemArticle: {
flexDirection: 'row',
marginBottom: 15,
paddingBottom: 15,
borderBottomWidth: 1,
borderColor: '#efefef'
},
itemArticleImg: {
width: 90,
height: 90,
marginRight: 10,
},
imgArticle: {
width: '100%',
height: "100%",
objectFit: 'cover',
},
infoArticle: {
width: winWidth - 120
},
itemArtName: {
fontSize: 15,
fontWeight: 700,
marginBottom: 3,
},
itemArtSummary: {
fontSize: 13,
color: '#161616',
},
itemArtDate: {
fontSize: 13,
marginLeft: 3
},
itemArtSource: {
fontSize: 12,
marginLeft: 3
},
btnMore: {
margin: 'auto',
width: 90,
height: 28,
lineHeight: 28,
textAlign: 'center',
borderWidth: 1,
borderColor: '#ff7a00',
borderRadius: 4,
color: '#ff7a00',
fontSize: 13,
fontWeight: 700
},
itemArtlocal: {
flexDirection: 'row',
alignItems: "center"
},
textArtlocal: {
marginLeft: 3,
fontSize: 13,
paddingBottom: 2
},
itemArtUploadtime: {
flexDirection: 'row',
alignItems: "center",
marginBottom: 3
},
itemArtUser: {
flexDirection: 'row',
alignItems: "center",
},
imgAvatar: {
width: 20,
height: 20,
objectFit: 'contain',
marginRight: 3
},
itemArtNameUser: {
fontWeight: "bold",
},
itemArtPrice: {
fontSize: 18,
fontWeight: 'bold',
color: '#d80a00',
},
BoxBannerSaleHome: {
margin: 10,
marginTop: 30,
},
imgBannerSale: {
width: '100%',
height: '100%',
objectFit: 'cover',
borderRadius: 12
},
sliderBannerSale: {
flexDirection: 'row',
alignItems: 'center',
height: 200
},
itemBannerSale: {
width: winWidth - 50,
height: 160,
objectFit: 'cover',
marginRight: 10
},
BoxPromotionHome: {
marginTop: 20
},
touchArea: {
width: winWidth,
height: 200,
position: 'relative',
overflow: 'hidden',
borderRadius: 12
},
imageYoutobe: {
width: winWidth - 20,
height: 200,
objectFit: 'cover',
overflow: 'hidden',
borderRadius: 12
},
boxVideo: {
width: winWidth - 20,
height: 200,
},
containerVideo: {
paddingLeft: 10,
paddingRight: 10
},
iconPlay: {
position: 'absolute',
left: winWidth / 2 - 20,
top: 90,
zIndex: 10,
},
infoVideo: {
position: 'absolute',
bottom: 0,
padding: 12,
zIndex: 10,
borderBottomLeftRadius: 16,
borderBottomRightRadius: 16,
},
listEvenBanner: {
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: 10,
paddingLeft: 10,
paddingRight: 10,
marginRight: -10
},
itemEven: {
width: winWidth / 2 - 20,
height: 100,
marginRight: 10,
marginBottom: 10
},
overlay: {
position: 'absolute',
bottom: 0,
height: '100%',
width: winWidth - 20,
backgroundColor: 'rgba(0,0,0,0.4)',
zIndex: 1,
borderRadius: 20
},
imgEven: {
width: '100%',
height: 100,
objectFit: 'cover',
borderRadius: 8
},
breadcrumb: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
},
breadcrumbItem: {
flexDirection: "row",
alignItems: "center",
marginRight: 6,
},
breadcrumbText: {
marginLeft: 4,
color: "#637381",
fontSize: 14,
},
});

View File

@@ -4,7 +4,16 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"baseUrl": "src",
"paths": {
"@components/*": ["components/*"],
"@layouts/*": ["layouts/*"],
"@screens/*": ["screens/*"],
"@assets/*": ["assets/*"],
"@navigation/*": ["navigation/*"]
}
},
"include": ["src/**/*", "assets/**/*"]
}

11284
yarn.lock

File diff suppressed because it is too large Load Diff