This commit is contained in:
KP9lK 2025-06-11 14:30:16 +03:00
commit 18de6efe6a
112 changed files with 5135 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

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

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="FurniturePreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="FurnitureTextFieldPreview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="PasswordFurnitureTextField">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="LoginScreen">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="MainActivity">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,57 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" />
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

794
.idea/misc.xml Normal file
View File

@ -0,0 +1,794 @@
<?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="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="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="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="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="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="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>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

4
.idea/vcs.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings" defaultProject="true" />
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

80
app/build.gradle.kts Normal file
View File

@ -0,0 +1,80 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.1.20"
}
android {
namespace = "com.example.furintiture"
compileSdk = 35
defaultConfig {
applicationId = "com.example.furintiture"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.retrofit)
implementation(libs.converter.kotlinx.serialization)
implementation(libs.okhttp)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime)
implementation (libs.androidx.datastore.preferences)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
implementation (libs.androidx.navigation.compose)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.example.furintiture
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.furintiture", appContext.packageName)
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.Furintiture"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Furintiture">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,89 @@
package com.example.furintiture
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.rememberNavController
import com.example.furintiture.data.DataStoreSettings
import com.example.furintiture.ui.common.BottomNavigation
import com.example.furintiture.ui.common.Destination
import com.example.furintiture.ui.common.FurnitureBottomNavigation
import com.example.furintiture.ui.screen.Cart
import com.example.furintiture.ui.screen.FurnitureGlobalNavigation
import com.example.furintiture.ui.screen.Login
import com.example.furintiture.ui.screen.Main
import com.example.furintiture.ui.screen.Order
import com.example.furintiture.ui.screen.Registration
import com.example.furintiture.ui.screen.Splash
import com.example.furintiture.ui.theme.FurintitureTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
val dataStoreSettings = DataStoreSettings(applicationContext)
setContent {
FurintitureTheme {
val navController = rememberNavController()
val bottomBarIsVisible = remember { mutableStateOf(false) }
val userUuid = remember { mutableStateOf("") }
Scaffold(
bottomBar = {
if(bottomBarIsVisible.value) FurnitureBottomNavigation(){ destination ->
when(destination.route){
BottomNavigation.Home -> navController.navigate(Main(userUuid.value))
BottomNavigation.Cart -> navController.navigate(Cart(userUuid.value))
BottomNavigation.Order -> {
navController.navigate(Order(userUuid.value))
}
}
}
}
) { paddingValues ->
paddingValues
FurnitureGlobalNavigation(dataStoreSettings, navController){
bottomBarIsVisible.value = it
}
}
LaunchedEffect(Unit) {
lifecycleScope.launch {
navController.clearBackStack(Splash)
dataStoreSettings.userUuidFlow.collect{
if(it.isEmpty()){
navController.navigate(Login){
popUpTo(Splash){
inclusive = true
}
}
}else{
userUuid.value = it
navController.navigate(Main(it)){
popUpTo(Splash){
inclusive = true
}
popUpTo(Registration){
inclusive = true
}
popUpTo(Login){
inclusive = true
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,34 @@
package com.example.furintiture.configure
import androidx.navigation.NavType
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.math.BigDecimal
import java.util.UUID
object BigDecimalSerializer: KSerializer<BigDecimal> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): BigDecimal {
return BigDecimal(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: BigDecimal) {
encoder.encodeString(value.toString())
}
}
object UuidSerializer: KSerializer<UUID> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): UUID {
return UUID.fromString(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeString(value.toString())
}
}

View File

@ -0,0 +1,25 @@
package com.example.furintiture.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.UUID
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
class DataStoreSettings(private val context: Context){
private val UUID_KEY = stringPreferencesKey("user_uuid")
val userUuidFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[UUID_KEY] ?: ""
}
suspend fun setUuid(uuid: UUID){
context.dataStore.edit { preferences ->
preferences[UUID_KEY] = uuid.toString()
}
}
}

View File

@ -0,0 +1,21 @@
package com.example.furintiture.data
import com.example.furintiture.data.api.FurnitureApi
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
private const val BASE_URL = "http://185.207.0.137:8090"
object RetrofitClient {
private val mediaType = "application/json".toMediaType()
private val client by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(Json.asConverterFactory(mediaType))
.build()
}
val api by lazy {
client.create(FurnitureApi::class.java)
}
}

View File

@ -0,0 +1,88 @@
package com.example.furintiture.data.api
import com.example.furintiture.model.request.AddAddressRequest
import com.example.furintiture.model.request.AddToCartRequest
import com.example.furintiture.model.request.AddToWishlistRequest
import com.example.furintiture.model.request.ChangeCountFromCartRequest
import com.example.furintiture.model.request.CreateOrderRequest
import com.example.furintiture.model.request.LoginRequest
import com.example.furintiture.model.request.RegisterRequest
import com.example.furintiture.model.request.RemoveFromCartRequest
import com.example.furintiture.model.request.RemoveFromWishlistRequest
import com.example.furintiture.model.response.AllShopCategoryResponse
import com.example.furintiture.model.response.CartResponse
import com.example.furintiture.model.response.FurnitureCategoryResponse
import com.example.furintiture.model.response.FurnitureResponse
import com.example.furintiture.model.response.OrderResponse
import com.example.furintiture.model.response.SaleResponse
import com.example.furintiture.model.response.UserResponse
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import java.util.UUID
import kotlin.uuid.Uuid
interface FurnitureApi {
@POST("/auth/login")
suspend fun auth(@Body loginRequest: LoginRequest): UserResponse
@POST("/auth/register")
suspend fun register(@Body registerRequest: RegisterRequest): UserResponse
@GET("/user/{uuid}/wishlist")
suspend fun getWithListByUuid(@Path("uuid") uuid: UUID): List<FurnitureResponse>
@GET("/user/{uuid}/cart")
suspend fun getCartByUuid(@Path("uuid") uuid: UUID): List<CartResponse>
@GET("/user/{uuid}/order")
suspend fun getOrdersByUuid(@Path("uuid") uuid: UUID): List<OrderResponse>
@GET("/user/{uuid}/profile")
suspend fun getProfileByUuid(@Path("uuid") uuid: UUID): UserResponse
@POST("/user/{uuid}/address")
suspend fun addAddressByUuid(@Path("uuid") uuid: UUID, @Body addAddressRequest: AddAddressRequest)
@POST("/wishlist")
suspend fun addToWishlist(addToWishlistRequest: AddToWishlistRequest)
@DELETE("/wishlist")
suspend fun addToWishlist(removeFromWishlistRequest: RemoveFromWishlistRequest)
@GET("/shop_category")
suspend fun getAllShopCategory(): List<AllShopCategoryResponse>
@GET("/shop_category/{shopCategoryId}/furniture")
suspend fun getFurnitureByShopCategory(@Path("shopCategoryId") shopCategoryId: Long): List<FurnitureResponse>
@GET("/sale")
suspend fun getAllSales(): List<SaleResponse>
@POST("/order")
suspend fun createOrder(@Body createOrderRequest: CreateOrderRequest)
@GET("/furniture")
suspend fun getAllFurniture(): List<FurnitureResponse>
@GET("/furniture/{furnitureId}")
suspend fun getFurnitureById(@Path("furnitureId") furnitureId: Long): FurnitureResponse
@GET("/category")
suspend fun getAllFurnitureCategory(): List<FurnitureCategoryResponse>
@GET("/category/{id}/furniture")
suspend fun getFurnitureByCategory(@Path("id") categoryId: Long): List<FurnitureResponse>
@POST("/cart")
suspend fun addToCartByUuid(@Body addToCartRequest: AddToCartRequest)
@PUT("/cart")
suspend fun changeCountFromCartByUuid(@Body changeCountFromCartRequest: ChangeCountFromCartRequest)
@DELETE("/cart")
suspend fun removeFromCartByUuid(@Body removeFromCartRequest: RemoveFromCartRequest)
}

View File

@ -0,0 +1,12 @@
package com.example.furintiture.model.request
import kotlinx.serialization.Serializable
@Serializable
data class AddAddressRequest(
var address: String = "",
var entrance: Int? = null,
var floor: Int? = null,
var apartment: Int? = null,
var comment: String? = null
)

View File

@ -0,0 +1,12 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class AddToCartRequest(
@Serializable(with = UuidSerializer::class)
val uuid: UUID,
val furnitureId: Long,
val count: Int,
)

View File

@ -0,0 +1,11 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class AddToWishlistRequest(
@Serializable(with = UuidSerializer::class)
val uuid: UUID,
val furnitureId: Long
)

View File

@ -0,0 +1,12 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class ChangeCountFromCartRequest(
@Serializable(with = UuidSerializer::class)
val uuid: UUID,
val furnitureId: Long,
val count: Int,
)

View File

@ -0,0 +1,12 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.BigDecimalSerializer
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
data class CreateOrderItemRequest(
val furnitureId : Long,
@Serializable(with = BigDecimalSerializer::class)
val furniturePrice: BigDecimal,
val count: Int
)

View File

@ -0,0 +1,18 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.BigDecimalSerializer
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.math.BigDecimal
import java.util.UUID
@Serializable
data class CreateOrderRequest(
@Serializable(with = UuidSerializer::class)
val userUuid : UUID,
val addressId: Long,
val orderStatus : Int,
@Serializable(with = BigDecimalSerializer::class)
val orderTotalSum : BigDecimal,
val orderSet: List<CreateOrderItemRequest>
)

View File

@ -0,0 +1,6 @@
package com.example.furintiture.model.request
import kotlinx.serialization.Serializable
@Serializable
data class LoginRequest(val email: String, val password: String)

View File

@ -0,0 +1,11 @@
package com.example.furintiture.model.request
import kotlinx.serialization.Serializable
@Serializable
data class RegisterRequest(
val firstName: String,
val lastName: String,
val email: String,
val password: String
)

View File

@ -0,0 +1,11 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class RemoveFromCartRequest(
@Serializable(with = UuidSerializer::class)
val uuid: UUID,
val furnitureId: Long,
)

View File

@ -0,0 +1,11 @@
package com.example.furintiture.model.request
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.util.UUID
@Serializable
data class RemoveFromWishlistRequest(
@Serializable(with = UuidSerializer::class)
val uuid: UUID,
val furnitureId: Long
)

View File

@ -0,0 +1,13 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class AddressResponse(
val addressId: Int,
val entrance: Int,
val floor: Int,
val apartment: Int,
val comment: String?,
val address: String
)

View File

@ -0,0 +1,10 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class AllShopCategoryResponse(
val id: Long,
val name: String,
var furnitureList: List<FurnitureResponse>
)

View File

@ -0,0 +1,9 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class CartResponse(
val count: Int,
val furnitureResponse: FurnitureResponse
)

View File

@ -0,0 +1,9 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class FurnitureCategoryResponse(
val id: Int,
val name: String,
)

View File

@ -0,0 +1,19 @@
package com.example.furintiture.model.response
import com.example.furintiture.configure.BigDecimalSerializer
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
data class FurnitureResponse(
val id: Long,
val name: String,
val description: String,
val url: String,
val category: FurnitureCategoryResponse,
@Serializable(with = BigDecimalSerializer::class)
val price: BigDecimal,
@Serializable(with = BigDecimalSerializer::class)
val sale: BigDecimal,
var shopCategories: List<ShopCategoryResponse>,
)

View File

@ -0,0 +1,14 @@
package com.example.furintiture.model.response
import com.example.furintiture.configure.BigDecimalSerializer
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
data class OrderItemResponse(
val furnitureId : Long,
val orderId : Long,
@Serializable(with = BigDecimalSerializer::class)
val furniturePrice: BigDecimal,
val count: Int,
val furnitureResponse: FurnitureResponse
)

View File

@ -0,0 +1,21 @@
package com.example.furintiture.model.response
import com.example.furintiture.configure.BigDecimalSerializer
import com.example.furintiture.configure.UuidSerializer
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
import java.math.BigDecimal
import java.util.*
@Serializable
data class OrderResponse(
val id : Long,
@Serializable(with = UuidSerializer::class)
val userUuid : UUID,
val addressId: Long,
val addressResponse: AddressResponse,
val dateTime: LocalDateTime,
val orderStatus : OrderStatusResponse,
@Serializable(with = BigDecimalSerializer::class)
val orderTotalSum : BigDecimal,
var orderSet: List<OrderItemResponse>
)

View File

@ -0,0 +1,9 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class OrderStatusResponse(
val id: Int,
val name: String,
)

View File

@ -0,0 +1,10 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class SaleResponse(
val id: Int,
val name: String,
val url: String,
)

View File

@ -0,0 +1,9 @@
package com.example.furintiture.model.response
import kotlinx.serialization.Serializable
@Serializable
data class ShopCategoryResponse(
val id: Long,
val name: String,
)

View File

@ -0,0 +1,15 @@
package com.example.furintiture.model.response
import com.example.furintiture.configure.UuidSerializer
import kotlinx.serialization.Serializable
import java.util.*
@Serializable
data class UserResponse(
@Serializable(with = UuidSerializer::class)
val userUuid: UUID,
val email: String,
val firstName: String,
val lastName: String,
val imageUrl: String? = null,
val address: AddressResponse? = null,
)

View File

@ -0,0 +1,173 @@
package com.example.furintiture.ui.common
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.furintiture.R
import com.example.furintiture.ui.theme.Neutral_40
import com.example.furintiture.ui.theme.Neutral_70
import com.example.furintiture.ui.theme.Neutral_90
@Composable
fun BasieFurnitureTextField(
value: String,
onValueChange: (String) -> Unit,
textColor: Color,
label: @Composable () -> Unit,
modifier: Modifier = Modifier,
leadingIcon: @Composable (() -> Unit)? = null,
visualTransformation: VisualTransformation = VisualTransformation.None,
trailingIcon: @Composable (() -> Unit)? = null,
){
BasicTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier
.fillMaxWidth()
.background(
color = Color.White,
shape = RoundedCornerShape(18.dp))
.border(
width = 1.dp, color = Neutral_40,
shape = RoundedCornerShape(18.dp)
),
singleLine = true,
textStyle = TextStyle(
color = Neutral_90,
fontSize = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
visualTransformation = visualTransformation,
){ innerTextField ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 18.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (leadingIcon != null) {
leadingIcon()
}
Spacer(modifier = Modifier.width(10.dp))
Column {
label()
Spacer(modifier = Modifier.height(4.dp))
innerTextField()
}
Spacer(modifier = Modifier.weight(1f))
trailingIcon?.let {
it()
}
}
}
}
@Composable
fun FurnitureTextField(
value: String,
onValueChange: (String) -> Unit,
textLabel: String,
leadingResourceId: Int,
tintColor: Color = Neutral_70,
textColor: Color = Neutral_90,
){
BasieFurnitureTextField(
value = value,
textColor = textColor,
onValueChange = onValueChange,
label = {
Text(
textLabel,
color = textColor,
style = TextStyle(
color = Neutral_90,
fontSize = 15.sp,
fontFamily = FontFamily(Font(R.font.manrope))
)
)
},
leadingIcon = { Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(leadingResourceId),
tint = tintColor,
contentDescription = null)
}
)
}
@Composable
fun FurniturePasswordTextField(
value: String,
onValueChange: (String) -> Unit,
tintColor: Color = Neutral_70,
textColor: Color = Neutral_90,
){
val passwordIsVisible = remember { mutableStateOf(false) }
BasieFurnitureTextField(
value = value,
textColor = textColor,
onValueChange = onValueChange,
label = {
Text("Password",
color = textColor,
style = TextStyle(
color = Neutral_90,
fontSize = 15.sp,
fontFamily = FontFamily(Font(R.font.manrope))
))
},
visualTransformation = if(passwordIsVisible.value) VisualTransformation.None else PasswordVisualTransformation('*'),
leadingIcon = { Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.lock_icon),
tint = tintColor,
contentDescription = null)
},
trailingIcon = { Icon(
modifier =
Modifier.size(20.dp).clickable {
passwordIsVisible.value = !passwordIsVisible.value
},
tint = tintColor,
painter = painterResource(R.drawable.fi_sr_eye_1),
contentDescription = null)
}
)
}

View File

@ -0,0 +1,81 @@
package com.example.furintiture.ui.common
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import com.example.furintiture.ui.theme.Neutral_40
import com.example.furintiture.ui.theme.Primary
public enum class Destination(
val route: BottomNavigation,
val label: String,
val icon: ImageVector,
val contentDescription: String
) {
HOME(BottomNavigation.Home, "Основная", Icons.Default.Home, "Songs"),
CART(BottomNavigation.Cart, "Корзина", Icons.Default.ShoppingCart, "Album"),
ORDER(BottomNavigation.Order, "Заказы", Icons.Default.Menu, "Playlist")
}
sealed class BottomNavigation{
data object Home: BottomNavigation()
data object Cart: BottomNavigation()
data object Order: BottomNavigation()
}
@Composable
fun FurnitureBottomNavigation(
onChangeBottomItem: (Destination) -> Unit
) {
val startDestination = Destination.HOME
var selectedDestination by rememberSaveable { mutableIntStateOf(startDestination.ordinal) }
NavigationBar(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
containerColor = Color.White
) {
Destination.entries.forEachIndexed { index, destination ->
NavigationBarItem(
selected = selectedDestination == index,
onClick = {
selectedDestination = index
onChangeBottomItem(destination)
},
icon = {
Icon(
destination.icon,
contentDescription = destination.contentDescription
)
},
label = { Text(destination.label) },
colors = NavigationBarItemColors(
selectedIconColor = Primary,
selectedTextColor = Primary,
selectedIndicatorColor = Color.Transparent,
unselectedIconColor = Neutral_40,
unselectedTextColor = Neutral_40,
disabledIconColor = Neutral_40,
disabledTextColor = Neutral_40
)
)
}
}
}

View File

@ -0,0 +1,65 @@
package com.example.furintiture.ui.common
import android.content.res.Resources.Theme
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.furintiture.R
import com.example.furintiture.ui.theme.FurintitureTheme
import com.example.furintiture.ui.theme.Primary
@Composable
fun FurnitureButton(
text: String,
modifier: Modifier = Modifier,
onClick : () -> Unit,
){
Button(
modifier = modifier
.fillMaxWidth()
.height(43.dp)
.clip(shape = RoundedCornerShape(8.dp))
,
colors = ButtonDefaults.buttonColors().copy(
containerColor = Primary,
contentColor = Color.White,
disabledContainerColor = Primary,
disabledContentColor = Color.White
),
onClick = onClick
) {
Text(
text = text,
style = TextStyle(
fontFamily = FontFamily(Font(R.font.manrope)),
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
)
}
}
@Preview
@Composable
fun FurniturePreview(){
FurnitureButton(
"Войти"
){
}
}

View File

@ -0,0 +1,142 @@
package com.example.furintiture.ui.common
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.example.furintiture.R
import com.example.furintiture.model.response.FurnitureCategoryResponse
import com.example.furintiture.model.response.FurnitureResponse
import com.example.furintiture.ui.theme.Neutral_10
import com.example.furintiture.ui.theme.Neutral_20
import java.math.BigDecimal
@Composable
fun FurnitureCard(
furnitureResponse: FurnitureResponse,
onFurnitureClick: (FurnitureResponse) -> Unit
){
Column(
modifier = Modifier
.wrapContentHeight()
.width(150.dp)
.background(color = Neutral_10, shape = RoundedCornerShape(14.dp))
.padding(16.dp)
.clickable {
onFurnitureClick(furnitureResponse)
},
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier.wrapContentSize()
) {
AsyncImage(
modifier = Modifier
.size(120.dp, 120.dp)
,
model = ImageRequest
.Builder(LocalContext.current)
.data(furnitureResponse.url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.images),
contentScale = ContentScale.FillWidth,
contentDescription = null
)
Text(
text = "${furnitureResponse.sale.multiply(BigDecimal(100)).toInt()}% OFF",
modifier = Modifier
.align(Alignment.BottomStart)
.background(
color = Color.Red,
shape = RoundedCornerShape(10.dp)
)
.padding(
vertical = 4.dp,
horizontal = 6.dp
),
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 10.sp
)
)
}
Spacer(modifier = Modifier.height(4.dp))
Column(
modifier = Modifier.wrapContentHeight()
) {
Text(
text = furnitureResponse.name,
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 14.sp
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = "${furnitureResponse.price}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
)
}
}
}
@Composable
@Preview
fun FurnitureCardPreview(){
val furnitureResponse = FurnitureResponse(
id = 1,
name = "Стул",
price = BigDecimal("1000.5"),
description = "!@3",
sale = BigDecimal("0.40"),
url = "https://laurendanger.com/wp-content/uploads/sites/33/2024/10/IMG_3826.jpeg",
category = FurnitureCategoryResponse(
id = 1,
name = "!@3"
),
shopCategories = emptyList()
)
FurnitureCard(furnitureResponse){
}
}

View File

@ -0,0 +1,114 @@
package com.example.furintiture.ui.common
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.example.furintiture.R
import com.example.furintiture.model.response.CartResponse
import com.example.furintiture.ui.theme.Neutral_10
import java.math.BigDecimal
@Composable
fun FurnitureCartCard(cartResponse: CartResponse) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(color = Neutral_10, shape = RoundedCornerShape(16.dp))
.padding(16.dp)
) {
AsyncImage(
modifier = Modifier
.size(100.dp, 100.dp)
,
model = ImageRequest
.Builder(LocalContext.current)
.data(cartResponse.furnitureResponse.url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.images),
contentScale = ContentScale.FillWidth,
contentDescription = null
)
Spacer(Modifier.width(16.dp))
Column(
modifier = Modifier
.fillMaxSize()
.fillMaxHeight()
) {
Text(
text = cartResponse.furnitureResponse.name,
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 24.sp
)
)
Text(
text = "${cartResponse.furnitureResponse.price}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 36.sp,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "${cartResponse.furnitureResponse.sale.multiply(BigDecimal(100)).toInt()}% OFF",
modifier = Modifier
.background(
color = Color.Red,
shape = RoundedCornerShape(10.dp)
)
.padding(
vertical = 4.dp,
horizontal = 6.dp
),
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 16.sp
)
)
Text(
text = "Количество: ${cartResponse.count}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 24.sp
)
)
}
}
}

View File

@ -0,0 +1,79 @@
package com.example.furintiture.ui.common
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.furintiture.R
import com.example.furintiture.model.response.FurnitureResponse
import com.example.furintiture.model.response.ShopCategoryResponse
import com.example.furintiture.ui.theme.Primary
@Composable
fun FurnitureHorizontalList(
categoryName: String, categoryFurnitureList: List<FurnitureResponse>,
onAllFurnitureClick: () -> Unit,
onFurnitureClick: (FurnitureResponse) -> Unit,
) {
Column(
modifier = Modifier.height(300.dp).fillMaxWidth()
) {
Row(
modifier =
Modifier.height(30.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f),
text = categoryName.replaceFirstChar { it.titlecaseChar() },
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 20.sp,
fontWeight = FontWeight.Bold)
)
Text(
modifier = Modifier.clickable {
onAllFurnitureClick()
},
text = "Больше товаров",
style = TextStyle(
color = Primary,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 14.sp,
fontWeight = FontWeight.Bold
)
)
}
Spacer(modifier = Modifier.height(16.dp))
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
items(categoryFurnitureList.size){ item ->
FurnitureCard(categoryFurnitureList[item]){ furniture ->
onFurnitureClick(furniture)
}
}
}
}
}

View File

@ -0,0 +1,100 @@
package com.example.furintiture.ui.common
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.furintiture.R
import com.example.furintiture.model.response.OrderResponse
import com.example.furintiture.ui.theme.Primary
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format
import kotlinx.datetime.format.DateTimeComponents
import kotlinx.datetime.format.DateTimeFormat
import kotlinx.datetime.format.DateTimeFormatBuilder
import kotlinx.datetime.format.byUnicodePattern
@Composable
fun FurnitureOrderCard(orderResponse: OrderResponse) {
Column(
modifier = Modifier
.fillMaxWidth()
) {
Row(
modifier =
Modifier.height(30.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f),
text = "Номер заказа: ${orderResponse.id}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 20.sp,
fontWeight = FontWeight.Bold)
)
Text(
text = "Статус заказа: ${orderResponse.orderStatus.name}",
style = TextStyle(
color = Primary,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 14.sp,
fontWeight = FontWeight.Bold
)
)
}
val format = LocalDateTime.Format {
byUnicodePattern("uuuu-MM-dd HH:mm")
}
Text(
text = "Дата заказа: ${format.format(orderResponse.dateTime)}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 14.sp
)
)
Text(
text = "Итого: ${orderResponse.orderTotalSum}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 14.sp
)
)
Text(
text = "По адресу: ${orderResponse.addressResponse.address}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 14.sp
)
)
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
items(orderResponse.orderSet){ orderSet ->
FurnitureCard(orderSet.furnitureResponse) {
}
}
}
}
}

View File

@ -0,0 +1,132 @@
package com.example.furintiture.ui.screen
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.example.furintiture.configure.UuidSerializer
import com.example.furintiture.data.DataStoreSettings
import com.example.furintiture.ui.screen.cart.CartScreen
import com.example.furintiture.ui.screen.detail.DetailScreen
import com.example.furintiture.ui.screen.furniture.FurnitureScreen
import com.example.furintiture.ui.screen.login.LoginScreen
import com.example.furintiture.ui.screen.main.MainScreen
import com.example.furintiture.ui.screen.order.OrderScreen
import com.example.furintiture.ui.screen.registration.RegistrationScreen
import com.example.furintiture.ui.screen.splashscreen.SplashScreen
import kotlinx.serialization.Serializable
import java.util.UUID
@Composable
fun FurnitureGlobalNavigation(
dataStoreSettings: DataStoreSettings,
navHostController: NavHostController,
onBottomBarVisibleEvent: (Boolean) -> Unit
) {
NavHost(
modifier = Modifier,
navController = navHostController,
startDestination = Splash
){
composable<Splash> {
SplashScreen()
onBottomBarVisibleEvent(false)
}
composable<Registration> {
RegistrationScreen(dataStoreSettings){
navHostController.navigate(Login)
}
onBottomBarVisibleEvent(false)
}
composable<Login> {
LoginScreen(dataStoreSettings){
navHostController.navigate(Registration)
}
onBottomBarVisibleEvent(false)
}
composable<Detail>{ navBackStackEntry ->
val detail: Detail = navBackStackEntry.toRoute()
DetailScreen(detail){
navHostController.navigateUp()
}
onBottomBarVisibleEvent(false)
}
composable<Furniture>{navBackStackEntry ->
val furniture: Furniture = navBackStackEntry.toRoute()
FurnitureScreen(uuid = furniture.uuid, onClosePressed = {
navHostController.navigateUp()
}) {
navHostController.navigate(it)
}
onBottomBarVisibleEvent(false)
}
composable<Cart>{ navBackStackEntry ->
val cart: Cart = navBackStackEntry.toRoute()
CartScreen(cart.uuid)
}
composable<Order>{ navBackStackEntry ->
val order: Order = navBackStackEntry.toRoute()
OrderScreen(order.uuid)
}
composable<Main> { navBackStackEntry ->
val main: Main = navBackStackEntry.toRoute()
MainScreen(
main.uuid,
onNavigateToFurniture = { furniture ->
navHostController.navigate(furniture)
}
){ detail ->
navHostController.navigate(detail)
}
onBottomBarVisibleEvent(true)
}
}
}
@Serializable
data class Main(
val uuid: String
)
@Serializable
data class Detail(
val furnitureId: Long,
val uuid: String
)
@Serializable
data class Cart(
val uuid: String
)
@Serializable
data class Order(
val uuid: String
)
@Serializable
object Login
@Serializable
data class Furniture(
val uuid: String
)
@Serializable
object Registration
@Serializable
object Splash

View File

@ -0,0 +1,135 @@
package com.example.furintiture.ui.screen.bottomsheet
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.furintiture.R
import com.example.furintiture.model.request.AddAddressRequest
import com.example.furintiture.model.response.AddressResponse
import com.example.furintiture.ui.common.BasieFurnitureTextField
import com.example.furintiture.ui.common.FurnitureButton
import com.example.furintiture.ui.theme.Neutral_90
import kotlinx.coroutines.flow.MutableStateFlow
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FurnitureAddressBottomSheet(
addressResponse: AddressResponse?,
viewModel: FurnitureAddressBottomSheetViewModel,
createAddressCallback: (AddAddressRequest) -> Unit,
) {
val state = viewModel.furnitureAddressState.collectAsState()
LaunchedEffect(addressResponse) {
if (addressResponse != null){
viewModel.setAddress(address = addressResponse.address)
viewModel.setFloor(floor = addressResponse.floor.toString())
viewModel.setApartment(apartment = addressResponse.apartment.toString())
viewModel.setComment(comment = addressResponse.comment ?: "")
}
}
ModalBottomSheet(
onDismissRequest = {
createAddressCallback(state.value)
}
){
Column(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
BasieFurnitureTextField(
value = state.value.address,
onValueChange = {viewModel.setAddress(it)},
textColor = Neutral_90,
label = {
Text(
"Адрес",
color = Neutral_90,
style = TextStyle(
color = Neutral_90,
fontSize = 15.sp,
fontFamily = FontFamily(Font(R.font.manrope))
)
)
}
)
Spacer(Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth()
){
BasieFurnitureTextField(
modifier = Modifier.weight(0.5f),
value = if(state.value.floor == null) "" else state.value.floor.toString(),
onValueChange = {viewModel.setFloor(it)},
textColor = Neutral_90,
label = {
Text(
"Этаж",
color = Neutral_90,
style = TextStyle(
color = Neutral_90,
fontSize = 15.sp,
fontFamily = FontFamily(Font(R.font.manrope))
)
)
}
)
BasieFurnitureTextField(
modifier = Modifier.weight(0.5f),
value = if(state.value.apartment == null) "" else state.value.apartment.toString(),
onValueChange = {viewModel.setApartment(it)},
textColor = Neutral_90,
label = {
Text(
"Квартира",
color = Neutral_90,
style = TextStyle(
color = Neutral_90,
fontSize = 15.sp,
fontFamily = FontFamily(Font(R.font.manrope))
)
)
}
)
}
Spacer(Modifier.height(16.dp))
BasieFurnitureTextField(
value = state.value.comment ?: "",
onValueChange = {viewModel.setComment(it)},
textColor = Neutral_90,
label = {
Text(
"Комментарий курьеру",
color = Neutral_90,
style = TextStyle(
color = Neutral_90,
fontSize = 15.sp,
fontFamily = FontFamily(Font(R.font.manrope))
)
)
}
)
Spacer(Modifier.height(16.dp))
val text = if(addressResponse != null) "Изменить" else "Добавить"
FurnitureButton(
text = text
) {
createAddressCallback(state.value)
}
}
}
}

View File

@ -0,0 +1,33 @@
package com.example.furintiture.ui.screen.bottomsheet
import androidx.lifecycle.ViewModel
import com.example.furintiture.model.request.AddAddressRequest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class FurnitureAddressBottomSheetViewModel: ViewModel() {
private val _furnitureAddressState = MutableStateFlow(AddAddressRequest())
val furnitureAddressState = _furnitureAddressState.asStateFlow()
fun setAddress(address: String){
_furnitureAddressState.update {
it.copy(address = address)
}
}
fun setFloor(floor: String){
_furnitureAddressState.update {
it.copy(floor = floor.toIntOrNull())
}
}
fun setApartment(apartment: String){
_furnitureAddressState.update {
it.copy(apartment = apartment.toIntOrNull())
}
}
fun setComment(comment: String){
_furnitureAddressState.update {
it.copy(comment = comment)
}
}
}

View File

@ -0,0 +1,118 @@
package com.example.furintiture.ui.screen.cart
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.furintiture.R
import com.example.furintiture.ui.common.FurnitureButton
import com.example.furintiture.ui.common.FurnitureCartCard
import com.example.furintiture.ui.theme.Neutral_20
import kotlinx.coroutines.launch
import java.math.BigDecimal
@Composable
fun CartScreen(
uuid: String
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel: CartScreenViewModel = viewModel { CartScreenViewModel(uuid) }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
contentColor = Neutral_20
) { paddingValues ->
CartScreenContent(viewModel, paddingValues){
coroutineScope.launch {
snackbarHostState.showSnackbar(it)
}
}
}
}
@Composable
fun CartScreenContent(
viewModel: CartScreenViewModel,
paddingValues: PaddingValues,
onErrorCallback : (String) -> Unit
){
val state = viewModel.cartScreenState.collectAsState()
val totalSum = remember { derivedStateOf {
state.value.cartResponse.sumOf { cart ->
BigDecimal(cart.count) * cart.furnitureResponse.price
}
}}
LaunchedEffect(state.value.error) {
state.value.error?.let(onErrorCallback)
state.value.error = null
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(horizontal = 16.dp)
.padding(bottom = 100.dp)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(state.value.cartResponse.size){ item ->
FurnitureCartCard(state.value.cartResponse[item])
}
if(totalSum.value > BigDecimal(0)){
item {
Spacer(Modifier.height(16.dp))
Text(
"Итого: ${totalSum.value}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 36.sp,
fontWeight = FontWeight.Bold
)
)
}
item {
Spacer(Modifier.height(16.dp))
FurnitureButton(
modifier = Modifier.height(60.dp),
text = "Оформить заказ"
) {
viewModel.createOrder()
}
}
}
}
}
}

View File

@ -0,0 +1,8 @@
package com.example.furintiture.ui.screen.cart
import com.example.furintiture.model.response.CartResponse
data class CartScreenState(
var cartResponse: List<CartResponse> = emptyList(),
var error: String? = null
)

View File

@ -0,0 +1,80 @@
package com.example.furintiture.ui.screen.cart
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.RetrofitClient
import com.example.furintiture.model.request.CreateOrderItemRequest
import com.example.furintiture.model.request.CreateOrderRequest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.util.UUID
class CartScreenViewModel(private val uuid: String): ViewModel() {
private val _cartScreenState = MutableStateFlow(CartScreenState())
val cartScreenState = _cartScreenState.asStateFlow()
val totalSum = derivedStateOf { cartScreenState.value.cartResponse.sumOf { cart ->
BigDecimal(cart.count) * cart.furnitureResponse.price }
}
init {
try {
init()
}
catch (e: Exception){
_cartScreenState.update {
it.copy(error = e.message)
}
}
}
fun createOrder(){
viewModelScope.launch {
try {
val addresId = RetrofitClient.api.getProfileByUuid(UUID.fromString(uuid)).address?.addressId
if (addresId == null){
_cartScreenState.update {
it.copy(error = "Нельзя сделать заказ без адрес")
}
return@launch
}
val createOrderRequest = CreateOrderRequest(
userUuid = UUID.fromString(uuid),
addressId = addresId.toLong(),
orderStatus = 1,
orderTotalSum = cartScreenState.value.cartResponse.sumOf { cart ->
BigDecimal(cart.count) * cart.furnitureResponse.price
},
orderSet = cartScreenState.value.cartResponse.map {
CreateOrderItemRequest(
furnitureId = it.furnitureResponse.id,
furniturePrice = it.furnitureResponse.price,
count = it.count
)
}
)
RetrofitClient.api.createOrder(createOrderRequest)
_cartScreenState.update {
it.copy(cartResponse = emptyList())
}
}
catch (e: Exception){
_cartScreenState.update {
it.copy(error = e.message)
}
}
}
}
private fun init(){
viewModelScope.launch {
val cart = RetrofitClient.api.getCartByUuid(UUID.fromString(uuid))
_cartScreenState.update {
it.copy(cartResponse = cart)
}
}
}
}

View File

@ -0,0 +1,216 @@
package com.example.furintiture.ui.screen.detail
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.example.furintiture.R
import com.example.furintiture.ui.common.FurnitureButton
import com.example.furintiture.ui.screen.Detail
import com.example.furintiture.ui.theme.Neutral_10
import com.example.furintiture.ui.theme.Neutral_20
import com.example.furintiture.ui.theme.Neutral_40
import kotlinx.coroutines.launch
import java.math.BigDecimal
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(
detail: Detail,
onClosePressed: () -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel = viewModel {
DetailScreenViewModel(detail.furnitureId, detail.uuid)
}
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
topBar = {
TopAppBar(
title = {},
navigationIcon = {
IconButton (
onClick = {
onClosePressed()
}
){
Icon(
imageVector = Icons.Default.Close,
contentDescription = null
)
}
}
)
},
bottomBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = Neutral_20, shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp))
.padding(20.dp)
) {
FurnitureButton(
modifier = Modifier.height(60.dp),
text = "Добавить в корзину",
) {
viewModel.addToCart()
}
}
},
contentColor = Neutral_20
) { paddingValues ->
DetailScreenContent(viewModel, paddingValues){
coroutineScope.launch {
snackbarHostState.showSnackbar(message = it)
}
}
}
}
@Composable
fun DetailScreenContent(
detailScreenViewModel: DetailScreenViewModel,
paddingValues: PaddingValues,
errorCallback: (String) -> Unit
){
val state = detailScreenViewModel.detailScreenState.collectAsState()
LaunchedEffect(state.value.error) {
state.value.error?.let(errorCallback)
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.background(color = Neutral_20)
,
verticalArrangement = Arrangement.Center
) {
Spacer(modifier = Modifier.height(16.dp))
AsyncImage(
modifier = Modifier
.size(350.dp, 350.dp)
.align(Alignment.CenterHorizontally)
,
model = ImageRequest
.Builder(LocalContext.current)
.data(state.value.furnitureResponse.url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.images),
contentScale = ContentScale.FillWidth,
contentDescription = null
)
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, shape = RoundedCornerShape(
topStart = 16.dp,
topEnd = 16.dp)
)
.weight(1f)
.padding(horizontal = 20.dp, vertical = 10.dp)
) {
Text(
text = state.value.furnitureResponse.name,
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 28.sp
)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${state.value.furnitureResponse.price}",
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 40.sp,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "${state.value.furnitureResponse.sale.multiply(BigDecimal(100)).toInt()}% OFF",
modifier = Modifier
.background(
color = Color.Red,
shape = RoundedCornerShape(10.dp)
)
.padding(
vertical = 4.dp,
horizontal = 6.dp
),
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 20.sp
)
)
}
Text(
text = state.value.furnitureResponse.description,
style = TextStyle(
color = com.example.furintiture.ui.theme.Text,
fontWeight = FontWeight.Normal,
fontFamily = FontFamily(Font(R.font.manrope)),
fontSize = 18.sp
)
)
}
}
}

View File

@ -0,0 +1,22 @@
package com.example.furintiture.ui.screen.detail
import com.example.furintiture.model.response.FurnitureCategoryResponse
import com.example.furintiture.model.response.FurnitureResponse
import java.math.BigDecimal
data class DetailScreenState(
val furnitureResponse: FurnitureResponse = FurnitureResponse(
id = 1,
name = "Стул",
price = BigDecimal("1000.5"),
description = "Этот стул выполнен из натурального дуба, что придает ему прочность и долговечность. Спинка украшена резными узорами, напоминающими старинные европейские интерьеры. Сиденье обито мягкой тканью с антикварным орнаментом, обеспечивая комфорт во время сидения. Ножки слегка изогнуты, что добавляет изделию элегантности. Такой стул идеально впишется в гостиную или столовую в классическом стиле, создавая атмосферу уюта и респектабельности.",
sale = BigDecimal("0.40"),
url = "https://laurendanger.com/wp-content/uploads/sites/33/2024/10/IMG_3826.jpeg",
category = FurnitureCategoryResponse(
id = 1,
name = "!@3"
),
shopCategories = emptyList()
),
var error: String? = null
)

View File

@ -0,0 +1,56 @@
package com.example.furintiture.ui.screen.detail
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.RetrofitClient
import com.example.furintiture.model.request.AddToCartRequest
import com.example.furintiture.model.request.ChangeCountFromCartRequest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.UUID
class DetailScreenViewModel(private val furnitureId: Long, private val uuidUser: String): ViewModel() {
private val _detailScreenState = MutableStateFlow(DetailScreenState())
val detailScreenState = _detailScreenState.asStateFlow()
init {
init()
}
fun addToCart(){
viewModelScope.launch {
try {
RetrofitClient.api.addToCartByUuid(
AddToCartRequest(
uuid = UUID.fromString(uuidUser),
furnitureId = furnitureId,
count = 1
)
)
_detailScreenState.update {
it.copy(error = "Успешно добавлено")
}
}
catch (e: Exception){
_detailScreenState.update {
it.copy(error = e.message)
}
}
}
}
private fun init(){
viewModelScope.launch {
try {
val result = RetrofitClient.api.getFurnitureById(furnitureId)
_detailScreenState.update {
it.copy(furnitureResponse = result)
}
}
catch (e: Exception){
println(e.message)
}
}
}
}

View File

@ -0,0 +1,109 @@
package com.example.furintiture.ui.screen.furniture
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.furintiture.ui.common.FurnitureCard
import com.example.furintiture.ui.screen.Detail
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FurnitureScreen(
uuid: String,
onClosePressed: () -> Unit,
onNavigateToDetail: (Detail) -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel = viewModel {
FurnitureScreenViewModel()
}
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
topBar = {
TopAppBar(
title = {},
navigationIcon = {
IconButton (
onClick = {
onClosePressed()
}
){
Icon(
imageVector = Icons.Default.Close,
contentDescription = null
)
}
}
)
},
) { paddingValues ->
FurnitureContent(uuid, viewModel, paddingValues, onNavigateToDetail){
coroutineScope.launch {
snackbarHostState.showSnackbar(message = it)
}
}
}
}
@Composable
fun FurnitureContent(
uuid: String,
furnitureScreenViewModel: FurnitureScreenViewModel,
paddingValues: PaddingValues,
onNavigateToDetail: (Detail) -> Unit,
onErrorCallback: (String) -> Unit
){
val state = furnitureScreenViewModel.furnitureScreenState.collectAsState()
LaunchedEffect(state.value.error) {
state.value.error?.let(onErrorCallback)
state.value.error = null
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
LazyVerticalGrid (
modifier = Modifier
.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
columns = GridCells.Adaptive(minSize = 130.dp)
){
items(state.value.furnitureList){ it ->
FurnitureCard(it) {
onNavigateToDetail(Detail(uuid = uuid, furnitureId = it.id))
}
}
}
}
}

View File

@ -0,0 +1,8 @@
package com.example.furintiture.ui.screen.furniture
import com.example.furintiture.model.response.FurnitureResponse
data class FurnitureScreenState(
var furnitureList: List<FurnitureResponse> = emptyList(),
var error: String? = null
)

View File

@ -0,0 +1,34 @@
package com.example.furintiture.ui.screen.furniture
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.RetrofitClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class FurnitureScreenViewModel: ViewModel(){
private val _furnitureScreenState = MutableStateFlow(FurnitureScreenState())
val furnitureScreenState = _furnitureScreenState.asStateFlow()
init {
try {
init()
}
catch (e: Exception){
_furnitureScreenState.update {
it.copy(error = e.message)
}
}
}
private fun init(){
viewModelScope.launch {
val result = RetrofitClient.api.getAllFurniture()
_furnitureScreenState.update {
it.copy(furnitureList = result)
}
}
}
}

View File

@ -0,0 +1,146 @@
package com.example.furintiture.ui.screen.login
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.furintiture.R
import com.example.furintiture.data.DataStoreSettings
import com.example.furintiture.ui.common.FurnitureButton
import com.example.furintiture.ui.common.FurniturePasswordTextField
import com.example.furintiture.ui.common.FurnitureTextField
import com.example.furintiture.ui.theme.Neutral_20
import com.example.furintiture.ui.theme.Neutral_70
import com.example.furintiture.ui.theme.Primary
import kotlinx.coroutines.launch
@Composable
fun LoginScreen(
dataStoreSettings: DataStoreSettings,
onNavigateToRegistration: () -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel = viewModel { LoginScreenViewModel(dataStoreSettings) }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
contentColor = Neutral_20
) { paddingValues ->
LoginScreenContent(viewModel, paddingValues, onNavigateToRegistration){
coroutineScope.launch {
snackbarHostState.showSnackbar(message = it)
}
}
}
}
@Composable
fun LoginScreenContent(
loginScreenViewModel: LoginScreenViewModel,
paddingValues: PaddingValues,
onNavigateToRegistration: () -> Unit,
errorCallback: (String) -> Unit
){
val state = loginScreenViewModel.loginScreenState.collectAsState()
LaunchedEffect(state.value.error) {
state.value.error?.let {
errorCallback(it)
}
state.value.error = null
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "C возвращением!",
style = TextStyle(
fontSize = 32.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope_extrabold))
),
color = Color.Black
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Введите свой email, чтобы начать делать покупки и получать выгодные предложения уже сегодня!",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
color = Neutral_70
)
Spacer(modifier = Modifier.height(32.dp))
FurnitureTextField(
value = state.value.request.email,
onValueChange = { loginScreenViewModel.setEmail(it) },
textLabel = "Email",
leadingResourceId = R.drawable.mail_icon
)
Spacer(modifier = Modifier.height(16.dp))
FurniturePasswordTextField(
value = state.value.request.password,
onValueChange = { loginScreenViewModel.setPassword(it) }
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Забыли пароль?",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
color = Primary
)
Spacer(modifier = Modifier.height(16.dp))
FurnitureButton(
text = "Войти"
) {
loginScreenViewModel.login()
}
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier.clickable {
onNavigateToRegistration()
},
text = "У вас нет учетной записи? Зарегистрируйтесь",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
color = Neutral_70
)
}
}

View File

@ -0,0 +1,10 @@
package com.example.furintiture.ui.screen.login
import android.util.Log
import androidx.compose.runtime.Composable
import com.example.furintiture.model.request.LoginRequest
data class LoginScreenState(
var request: LoginRequest = LoginRequest(email = "", password = ""),
var error: String? = null
)

View File

@ -0,0 +1,55 @@
package com.example.furintiture.ui.screen.login
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.DataStoreSettings
import com.example.furintiture.data.RetrofitClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class LoginScreenViewModel(private val dataStoreSettings: DataStoreSettings): ViewModel() {
private val _loginScreenState = MutableStateFlow(LoginScreenState())
val loginScreenState = _loginScreenState.asStateFlow()
fun setPassword(password: String){
_loginScreenState.update {
it.copy(request = it.request.copy(password = password))
}
}
fun setEmail(email: String){
_loginScreenState.update {
it.copy(request = it.request.copy(email = email))
}
}
fun login(){
if(!validate()) return
viewModelScope.launch {
try {
val result = RetrofitClient.api.auth(loginScreenState.value.request)
dataStoreSettings.setUuid(result.userUuid)
}
catch (e: Exception){
_loginScreenState.update {
it.copy(error = e.message)
}
}
}
}
private fun validate(): Boolean{
if (_loginScreenState.value.request.email.isEmpty()){
_loginScreenState.update {
it.copy(error = "Введите email")
}
return false
}
if (_loginScreenState.value.request.password.isEmpty()){
_loginScreenState.update {
it.copy(error = "Введите password")
}
return false
}
return true
}
}

View File

@ -0,0 +1,246 @@
package com.example.furintiture.ui.screen.main
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.example.furintiture.R
import com.example.furintiture.ui.screen.bottomsheet.FurnitureAddressBottomSheet
import com.example.furintiture.ui.common.FurnitureHorizontalList
import com.example.furintiture.ui.screen.Detail
import com.example.furintiture.ui.screen.Furniture
import com.example.furintiture.ui.screen.bottomsheet.FurnitureAddressBottomSheetViewModel
import com.example.furintiture.ui.theme.Neutral_20
import com.example.furintiture.ui.theme.Neutral_40
import com.example.furintiture.ui.theme.Primary
import com.example.furintiture.ui.theme.gradientColors
import kotlinx.coroutines.launch
@Composable
fun MainScreen(
uuid: String,
onNavigateToFurniture: (Furniture) -> Unit,
onNavigateToDetail: (Detail) -> Unit,
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel: MainScreenViewModel = viewModel {
MainScreenViewModel(uuid)
}
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
contentColor = Neutral_20
) { paddingValues ->
MainScreenContent(uuid, viewModel, paddingValues, onNavigateToFurniture, onNavigateToDetail ){
coroutineScope.launch {
snackbarHostState.showSnackbar(message = it)
}
}
}
}
@Composable
fun MainScreenContent(
userUuid: String,
mainScreenViewModel: MainScreenViewModel,
paddingValues: PaddingValues,
onNavigateToFurniture: (Furniture) -> Unit,
onNavigateToDetail: (Detail) -> Unit,
errorCallback: (String) -> Unit,
) {
val state = mainScreenViewModel.mainScreenState.collectAsState()
val pagerState = rememberPagerState(pageCount = {state.value.sales.size})
val showBottomSheet = remember { mutableStateOf(false) }
val viewModel: FurnitureAddressBottomSheetViewModel = viewModel()
LaunchedEffect(state.value.error) {
state.value.error?.let {
errorCallback(it)
}
state.value.error = null
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(bottom = 100.dp,)
) {
Spacer(modifier = Modifier.height(16.dp))
state.value.profile?.let {
val text = if(it.address != null) "Доставим по адресу: ${it.address.address}" else "Выберите адрес доставки"
Text(
modifier =
Modifier.padding(horizontal = 16.dp)
.clickable {
showBottomSheet.value = true
}
,
text = text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope_extrabold)),
color = com.example.furintiture.ui.theme.Text
)
)
Spacer(modifier = Modifier.height(16.dp))
}
if(showBottomSheet.value){
FurnitureAddressBottomSheet(
state.value.profile?.address, viewModel
) { requestAddress ->
mainScreenViewModel.addAddress(requestAddress)
showBottomSheet.value = false
}
}
HorizontalPager(state = pagerState) { page ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(top = 20.dp)
,
) {
AsyncImage(
modifier = Modifier
.fillMaxSize()
,
model = ImageRequest
.Builder(LocalContext.current)
.data(state.value.sales[page].url)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.images),
contentScale = ContentScale.FillWidth,
contentDescription = null
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Brush.horizontalGradient(colorStops = gradientColors)))
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.Start,
modifier = Modifier
.padding(10.dp)
) {
Text(
text = state.value.sales[page].name,
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope_extrabold))
),
color = Neutral_20
)
Button(
onClick = {},
colors = ButtonDefaults.buttonColors().copy(
contentColor = Color.White,
disabledContentColor = Color.White,
containerColor = Color.White,
disabledContainerColor = Color.White
)
) {
Text(
"Купить сейчас",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope)),
fontWeight = FontWeight.Bold
),
color = Primary
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.Center
) {
repeat(pagerState.pageCount) { iteration ->
val color = if (pagerState.currentPage == iteration) Primary else Neutral_40
Box(
modifier = Modifier
.padding(2.dp)
.clip(CircleShape)
.background(color)
.size(12.dp)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
LazyColumn(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp)
) {
items(state.value.shopCategories.size){ item ->
FurnitureHorizontalList(
categoryName = state.value.shopCategories[item].name,
categoryFurnitureList = state.value.shopCategories[item].furnitureList,
onAllFurnitureClick = { onNavigateToFurniture(Furniture(uuid = userUuid)) }
){
onNavigateToDetail(Detail(furnitureId = it.id, uuid = userUuid))
}
}
}
}
}

View File

@ -0,0 +1,16 @@
package com.example.furintiture.ui.screen.main
import com.example.furintiture.model.response.AllShopCategoryResponse
import com.example.furintiture.model.response.FurnitureResponse
import com.example.furintiture.model.response.SaleResponse
import com.example.furintiture.model.response.ShopCategoryResponse
import com.example.furintiture.model.response.UserResponse
data class MainScreenState(
var sales: List<SaleResponse> = emptyList(),
var shopCategories: List<AllShopCategoryResponse> = emptyList(),
var addresses: List<String> = emptyList(),
var cartItems: List<FurnitureResponse> = emptyList(),
var error: String? = null,
val profile: UserResponse? = null
)

View File

@ -0,0 +1,85 @@
package com.example.furintiture.ui.screen.main
import androidx.compose.runtime.saveable.autoSaver
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.RetrofitClient
import com.example.furintiture.model.request.AddAddressRequest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.UUID
class MainScreenViewModel(private val uuid: String): ViewModel() {
private val _mainScreenState = MutableStateFlow(MainScreenState())
val mainScreenState = _mainScreenState.asStateFlow()
init {
try {
init()
}
catch (e: Exception){
_mainScreenState.update {
it.copy(error = e.message)
}
}
}
private fun init(){
viewModelScope.launch {
val sales = RetrofitClient.api.getAllSales()
_mainScreenState.update {
it.copy(sales = sales)
}
val allCategory = RetrofitClient.api.getAllShopCategory()
_mainScreenState.update {
it.copy(shopCategories = allCategory)
}
val profile = RetrofitClient.api.getProfileByUuid(UUID.fromString(uuid))
_mainScreenState.update {
it.copy(profile = profile)
}
}
}
fun addAddress(addressRequest: AddAddressRequest){
viewModelScope.launch {
if (!validate(addressRequest)) return@launch
addressRequest.entrance = 10
try {
RetrofitClient.api.addAddressByUuid(uuid = UUID.fromString(uuid), addressRequest)
val profile = RetrofitClient.api.getProfileByUuid(UUID.fromString(uuid))
_mainScreenState.update {
it.copy(profile = profile)
}
}
catch (e: Exception){
_mainScreenState.update {
it.copy(error = e.message)
}
}
}
}
private fun validate(addressRequest: AddAddressRequest): Boolean{
if (addressRequest.address.isEmpty()){
_mainScreenState.update {
it.copy(error = "Адрес не может быть пустым")
}
return false
}
if(addressRequest.apartment == null){
_mainScreenState.update {
it.copy(error = "Укажите квартиру")
}
return false
}
if(addressRequest.floor == null){
_mainScreenState.update {
it.copy(error = "Укажите этаж")
}
return false
}
return true
}
}

View File

@ -0,0 +1,84 @@
package com.example.furintiture.ui.screen.order
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.furintiture.ui.common.FurnitureOrderCard
import com.example.furintiture.ui.screen.cart.CartScreenContent
import com.example.furintiture.ui.screen.cart.CartScreenViewModel
import com.example.furintiture.ui.theme.Neutral_20
import kotlinx.coroutines.launch
import java.math.BigDecimal
@Composable
fun OrderScreen(
uuid: String
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel: OrderScreenViewModel = viewModel { OrderScreenViewModel(uuid) }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
contentColor = Neutral_20
) { paddingValues ->
OrderScreenContent(viewModel, paddingValues){
coroutineScope.launch {
snackbarHostState.showSnackbar(it)
}
}
}
}
@Composable
fun OrderScreenContent(
viewModel: OrderScreenViewModel,
paddingValues: PaddingValues,
onErrorCallback : (String) -> Unit
) {
val state = viewModel.orderScreenState.collectAsState()
LaunchedEffect(state.value.error) {
state.value.error?.let(onErrorCallback)
state.value.error = null
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(horizontal = 16.dp)
.padding(bottom = 100.dp)
) {
Spacer(modifier = Modifier.height(16.dp))
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(state.value.orders){
FurnitureOrderCard(it)
}
}
}
}

View File

@ -0,0 +1,8 @@
package com.example.furintiture.ui.screen.order
import com.example.furintiture.model.response.OrderResponse
data class OrderScreenState(
var orders: List<OrderResponse> = emptyList(),
var error: String? = null
)

View File

@ -0,0 +1,30 @@
package com.example.furintiture.ui.screen.order
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.RetrofitClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.UUID
class OrderScreenViewModel(private val uuid: String): ViewModel() {
private val _orderScreenState = MutableStateFlow(OrderScreenState())
val orderScreenState = _orderScreenState.asStateFlow()
init {
try{
viewModelScope.launch {
_orderScreenState.update {
it.copy(orders = RetrofitClient.api.getOrdersByUuid(UUID.fromString(uuid)))
}
}
}
catch (e: Exception){
_orderScreenState.update {
it.copy(error = e.message)
}
}
}
}

View File

@ -0,0 +1,160 @@
package com.example.furintiture.ui.screen.registration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.furintiture.R
import com.example.furintiture.data.DataStoreSettings
import com.example.furintiture.ui.common.FurnitureButton
import com.example.furintiture.ui.common.FurniturePasswordTextField
import com.example.furintiture.ui.common.FurnitureTextField
import com.example.furintiture.ui.screen.login.LoginScreenContent
import com.example.furintiture.ui.screen.login.LoginScreenViewModel
import com.example.furintiture.ui.theme.Neutral_20
import com.example.furintiture.ui.theme.Neutral_70
import com.example.furintiture.ui.theme.Primary
import kotlinx.coroutines.launch
@Composable
fun RegistrationScreen(
dataStoreSettings: DataStoreSettings,
onNavigateToLogin: () -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val viewModel: RegistrationScreenViewModel = viewModel { RegistrationScreenViewModel(dataStoreSettings) }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
contentColor = Neutral_20
) { paddingValues ->
RegisterScreenContent(viewModel, paddingValues, onNavigateToLogin){
coroutineScope.launch {
snackbarHostState.showSnackbar(message = it)
}
}
}
}
@Composable
fun RegisterScreenContent(
registrationScreenViewModel: RegistrationScreenViewModel,
paddingValues: PaddingValues,
onNavigateToLogin: () -> Unit,
errorCallback: (String) -> Unit
){
val state = registrationScreenViewModel.registrationScreenState.collectAsState()
LaunchedEffect(state.value.error) {
state.value.error?.let {
errorCallback(it)
}
state.value.error = null
}
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "Создать аккаунт",
style = TextStyle(
fontSize = 32.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope_extrabold))
),
color = Color.Black
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Введите свои данные ниже, чтобы начать совершать покупки.",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
color = Neutral_70
)
Spacer(modifier = Modifier.height(32.dp))
FurnitureTextField(
value = state.value.request.firstName,
onValueChange = { registrationScreenViewModel.setFirstName(it) },
textLabel = "Имя",
leadingResourceId = R.drawable.user_icon
)
Spacer(modifier = Modifier.height(16.dp))
FurnitureTextField(
value = state.value.request.lastName,
onValueChange = { registrationScreenViewModel.setLastName(it) },
textLabel = "Фамиля",
leadingResourceId = R.drawable.user_icon
)
Spacer(modifier = Modifier.height(16.dp))
FurnitureTextField(
value = state.value.request.email,
onValueChange = { registrationScreenViewModel.setEmail(it) },
textLabel = "Email",
leadingResourceId = R.drawable.mail_icon
)
Spacer(modifier = Modifier.height(16.dp))
FurniturePasswordTextField(
value = state.value.request.password,
onValueChange = { registrationScreenViewModel.setPassword(it) }
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Нажимая кнопку Зарегистрироваться, вы подтверждаете, что ознакомились и согласились с нашими Условиями использования и Политикой конфиденциальности.",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
color = Primary
)
Spacer(modifier = Modifier.height(16.dp))
FurnitureButton(
text = "Зарегистрироваться"
) {
registrationScreenViewModel.register()
}
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier.clickable {
onNavigateToLogin()
},
text = "Войти",
style = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
fontFamily = FontFamily(Font(R.font.manrope))
),
color = Neutral_70
)
}
}

View File

@ -0,0 +1,13 @@
package com.example.furintiture.ui.screen.registration
import com.example.furintiture.model.request.RegisterRequest
data class RegistrationScreenState(
var error: String? = null,
var request: RegisterRequest = RegisterRequest(
firstName = "",
lastName = "",
email = "",
password = ""
)
)

View File

@ -0,0 +1,79 @@
package com.example.furintiture.ui.screen.registration
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.furintiture.data.DataStoreSettings
import com.example.furintiture.data.RetrofitClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class RegistrationScreenViewModel(private val dataStoreSettings: DataStoreSettings): ViewModel() {
private val _registrationScreenState = MutableStateFlow(RegistrationScreenState())
val registrationScreenState = _registrationScreenState.asStateFlow()
fun setPassword(password: String){
_registrationScreenState.update {
it.copy(request = it.request.copy(password = password))
}
}
fun setEmail(email: String){
_registrationScreenState.update {
it.copy(request = it.request.copy(email = email))
}
}
fun setLastName(lastName: String){
_registrationScreenState.update {
it.copy(request = it.request.copy(lastName = lastName))
}
}
fun setFirstName(firstName: String){
_registrationScreenState.update {
it.copy(request = it.request.copy(firstName = firstName))
}
}
fun register(){
if(!validate()) return
viewModelScope.launch {
try {
val result = RetrofitClient.api.register(_registrationScreenState.value.request)
dataStoreSettings.setUuid(result.userUuid)
}
catch (e: Exception){
_registrationScreenState.update {
it.copy(error = e.message)
}
}
}
}
private fun validate(): Boolean{
if (_registrationScreenState.value.request.email.isEmpty()){
_registrationScreenState.update {
it.copy(error = "Введите email")
}
return false
}
if (_registrationScreenState.value.request.password.isEmpty()){
_registrationScreenState.update {
it.copy(error = "Введите password")
}
return false
}
if (_registrationScreenState.value.request.lastName.isEmpty()){
_registrationScreenState.update {
it.copy(error = "Введите фамилию")
}
return false
}
if (_registrationScreenState.value.request.firstName.isEmpty()){
_registrationScreenState.update {
it.copy(error = "Введите имя")
}
return false
}
return true
}
}

View File

@ -0,0 +1,54 @@
package com.example.furintiture.ui.screen.splashscreen
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.furintiture.R
import com.example.furintiture.ui.theme.gradientColors
@Composable
fun SplashScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Brush.verticalGradient(colorStops = gradientColors))
,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
modifier = Modifier.size(87.dp),
imageVector = Icons.Default.Home,
contentDescription = null,
tint = Color.White
)
Text(
text = "Дом мечты",
style = TextStyle(
fontSize = 36.sp,
fontWeight = FontWeight.ExtraBold,
fontFamily = FontFamily(Font(R.font.manrope_extrabold)),
color = Color.White
)
)
}
}

View File

@ -0,0 +1,19 @@
package com.example.furintiture.ui.theme
import androidx.compose.ui.graphics.Color
val Primary = Color(0xFF5C6B67)
val Secondary = Color(0xFFEBB65B)
val Text = Color(0xFF404040)
val Neutral_40 = Color(0xFFE0E0E0)
val Green_Linear= Color(0xFF156651)
val Neutral_70 = Color(0xFF757575)
val Neutral_90 = Color(0xFF404040)
val Neutral_20 = Color(0xFFF5F5F5)
val Neutral_10 = Color(0xFFFFFFFF)
val gradientColors = arrayOf(
0.0f to Color(0xFF156651).copy(alpha = 0.94f),
0.47f to Color(0xFF156651).copy(alpha = 0.67f),
1f to Color(0xFF156651).copy(alpha = 0f)
)

View File

@ -0,0 +1,58 @@
package com.example.furintiture.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Primary,
secondary = Secondary,
tertiary = Text
)
private val LightColorScheme = lightColorScheme(
primary = Primary,
secondary = Secondary,
tertiary = Text
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun FurintitureTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.example.furintiture.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="21dp"
android:viewportWidth="20"
android:viewportHeight="21">
<group>
<clip-path
android:pathData="M0,0.5h20v20h-20z"/>
<path
android:pathData="M10,13.833C11.841,13.833 13.333,12.341 13.333,10.5C13.333,8.659 11.841,7.167 10,7.167C8.159,7.167 6.667,8.659 6.667,10.5C6.667,12.341 8.159,13.833 10,13.833Z"
android:fillColor="#9E9E9E"/>
<path
android:pathData="M19.392,8.349C18.1,6.244 15.16,2.712 10,2.712C4.84,2.712 1.9,6.244 0.608,8.349C0.208,8.995 -0.004,9.74 -0.004,10.5C-0.004,11.26 0.208,12.005 0.608,12.651C1.9,14.756 4.84,18.288 10,18.288C15.16,18.288 18.1,14.756 19.392,12.651C19.792,12.005 20.004,11.26 20.004,10.5C20.004,9.74 19.792,8.995 19.392,8.349ZM10,15.5C9.011,15.5 8.044,15.207 7.222,14.657C6.4,14.108 5.759,13.327 5.381,12.413C5.002,11.5 4.903,10.494 5.096,9.525C5.289,8.555 5.765,7.664 6.465,6.964C7.164,6.265 8.055,5.789 9.025,5.596C9.995,5.403 11,5.502 11.913,5.881C12.827,6.259 13.608,6.9 14.157,7.722C14.707,8.544 15,9.511 15,10.5C14.999,11.826 14.472,13.097 13.534,14.034C12.597,14.972 11.326,15.499 10,15.5Z"
android:fillColor="#9E9E9E"/>
</group>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="25dp"
android:viewportWidth="24"
android:viewportHeight="25">
<path
android:pathData="M6.546,10.682V7.955C6.546,6.508 7.12,5.121 8.143,4.098C9.166,3.075 10.554,2.5 12,2.5C13.447,2.5 14.834,3.075 15.857,4.098C16.88,5.121 17.455,6.508 17.455,7.955V10.682H18.364C19.87,10.682 21.091,11.903 21.091,13.409V19.773C21.091,21.279 19.87,22.5 18.364,22.5H5.636C4.13,22.5 2.909,21.279 2.909,19.773V13.409C2.909,11.903 4.13,10.682 5.636,10.682H6.546ZM9.429,5.383C10.111,4.701 11.036,4.318 12,4.318C12.965,4.318 13.889,4.701 14.571,5.383C15.253,6.065 15.637,6.99 15.637,7.955V10.682H8.364V7.955C8.364,6.99 8.747,6.065 9.429,5.383ZM5.636,12.5C5.134,12.5 4.727,12.907 4.727,13.409V19.773C4.727,20.275 5.134,20.682 5.636,20.682H18.364C18.866,20.682 19.273,20.275 19.273,19.773V13.409C19.273,12.907 18.866,12.5 18.364,12.5H5.636Z"
android:fillColor="#757575"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="25dp"
android:viewportWidth="24"
android:viewportHeight="25">
<path
android:pathData="M2,7.03C2,7.039 2,7.048 2,7.057V17.955C2,19.457 3.225,20.682 4.727,20.682H19.273C20.775,20.682 22,19.457 22,17.955V7.057C22,7.048 22,7.039 22,7.03C21.991,5.535 20.77,4.318 19.273,4.318H4.727C3.231,4.318 2.009,5.535 2,7.03ZM3.915,6.64C4.065,6.343 4.375,6.136 4.727,6.136H19.273C19.625,6.136 19.935,6.343 20.085,6.64L12,12.299L3.915,6.64ZM20.182,8.792V17.955C20.182,18.452 19.771,18.864 19.273,18.864H4.727C4.229,18.864 3.818,18.452 3.818,17.955V8.792L11.479,14.154C11.792,14.373 12.208,14.373 12.521,14.154L20.182,8.792Z"
android:fillColor="#757575"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="25dp"
android:viewportWidth="24"
android:viewportHeight="25">
<path
android:pathData="M19.784,21.263V19.263C19.784,18.203 19.363,17.185 18.613,16.435C17.862,15.685 16.845,15.263 15.784,15.263H7.784C6.723,15.263 5.706,15.685 4.956,16.435C4.206,17.185 3.784,18.203 3.784,19.263V21.263M15.784,7.263C15.784,9.473 13.993,11.263 11.784,11.263C9.575,11.263 7.784,9.473 7.784,7.263C7.784,5.054 9.575,3.263 11.784,3.263C13.993,3.263 15.784,5.054 15.784,7.263Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#757575"
android:strokeLineCap="round"/>
</vector>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">furintiture</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More