diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8aecc629..75afb311 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,13 +5,11 @@
-
-
-
-
+
Unit,
+ modifier: Modifier = Modifier
+) {
+ val backgroundColor = when {
+ enabled -> PawKeyTheme.colors.primary
+ else -> PawKeyTheme.colors.background1
+ }
+
+ val textColor = when {
+ enabled -> PawKeyTheme.colors.background1
+ else -> PawKeyTheme.colors.default
+ }
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .background(backgroundColor, shape = RoundedCornerShape(8.dp))
+ .noRippleClickable {
+ if (enabled) onClick()
+ }
+ .padding(vertical = 14.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = text,
+ style = PawKeyTheme.typography.mainButtonDefault,
+ color = textColor
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun DogkyButtonPreview() {
+ PawKeyTheme {
+ DogkyButton(
+ text = "",
+ enabled = true,
+ onClick = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/core/designsystem/component/PawKeyBottomSheet.kt b/app/src/main/java/com/paw/key/core/designsystem/component/PawKeyBottomSheet.kt
new file mode 100644
index 00000000..fdc18dcb
--- /dev/null
+++ b/app/src/main/java/com/paw/key/core/designsystem/component/PawKeyBottomSheet.kt
@@ -0,0 +1,50 @@
+package com.paw.key.core.designsystem.component
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.SheetState
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PawKeyBottomSheet(
+ sheetState: SheetState,
+ onDismissRequest: () -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable (sheetState: SheetState) -> Unit
+) {
+ ModalBottomSheet(
+ onDismissRequest = onDismissRequest,
+ sheetState = sheetState,
+ shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
+ containerColor = PawKeyTheme.colors.background2,
+ modifier = modifier
+ .fillMaxWidth(),
+ dragHandle = null,
+ ) {
+ content(sheetState)
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+private fun PawKeyBottomSheetPreview() {
+ PawKeyTheme {
+ val sheetState = rememberModalBottomSheetState(
+ skipPartiallyExpanded = true
+ )
+
+ PawKeyBottomSheet(
+ sheetState = sheetState,
+ onDismissRequest = {}
+ ) { }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/core/designsystem/component/PawkeyButton.kt b/app/src/main/java/com/paw/key/core/designsystem/component/PawkeyButton.kt
index aeb4181a..2c0fda0e 100644
--- a/app/src/main/java/com/paw/key/core/designsystem/component/PawkeyButton.kt
+++ b/app/src/main/java/com/paw/key/core/designsystem/component/PawkeyButton.kt
@@ -85,6 +85,7 @@ private fun PreviewPawkeyButton() {
}
}
+// Todo : 이 더러운 분기의 버튼 제거예정
@Composable
fun PawkeyButton(
text: String,
@@ -102,7 +103,7 @@ fun PawkeyButton(
}
val backgroundColor = when {
- actualEnabled && !isBackGround -> PawKeyTheme.colors.green500
+ actualEnabled && !isBackGround -> PawKeyTheme.colors.primary
actualEnabled && isBackGround -> PawKeyTheme.colors.white1
!actualEnabled && isBackGround -> PawKeyTheme.colors.white1
else -> PawKeyTheme.colors.gray200
@@ -139,7 +140,7 @@ fun PawkeyButton(
) {
Text(
text = text,
- style = PawKeyTheme.typography.body16Sb,
+ style = PawKeyTheme.typography.mainButtonActive,
color = contentColor
)
}
diff --git a/app/src/main/java/com/paw/key/core/util/DateVisualTransformation.kt b/app/src/main/java/com/paw/key/core/util/DateVisualTransformation.kt
new file mode 100644
index 00000000..188fc499
--- /dev/null
+++ b/app/src/main/java/com/paw/key/core/util/DateVisualTransformation.kt
@@ -0,0 +1,44 @@
+package com.paw.key.core.util
+
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TransformedText
+import androidx.compose.ui.text.input.VisualTransformation
+
+// 날짜 텍스트 입력받아서 변환 텍스트 만들기
+class DateVisualTransformation : VisualTransformation {
+ override fun filter(text: AnnotatedString): TransformedText {
+ val digitsOnly = text.text.filter { it.isDigit() }
+
+ val trimmed = if (digitsOnly.length > 8) digitsOnly.substring(0, 8) else digitsOnly
+
+ val formattedText = buildString {
+ trimmed.forEachIndexed { index, char ->
+ append(char)
+ if (index == 3 || index == 5) {
+ append('/')
+ }
+ }
+ }
+
+ val offsetMapping = object : OffsetMapping {
+ override fun originalToTransformed(offset: Int): Int {
+ return when {
+ offset >= 6 -> offset + 2
+ offset >= 4 -> offset + 1
+ else -> offset
+ }
+ }
+
+ override fun transformedToOriginal(offset: Int): Int {
+ return when {
+ offset >= 8 -> offset - 2 // yyyy/MM/dd
+ offset >= 5 -> offset - 1 // yyyy/MM
+ else -> offset
+ }
+ }
+ }
+
+ return TransformedText(AnnotatedString(formattedText), offsetMapping)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/util/PermissionRequestEffect.kt b/app/src/main/java/com/paw/key/core/util/PermissionRequestEffect.kt
similarity index 93%
rename from app/src/main/java/com/paw/key/presentation/ui/course/util/PermissionRequestEffect.kt
rename to app/src/main/java/com/paw/key/core/util/PermissionRequestEffect.kt
index 3e3c683e..e87302f0 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/course/util/PermissionRequestEffect.kt
+++ b/app/src/main/java/com/paw/key/core/util/PermissionRequestEffect.kt
@@ -1,4 +1,4 @@
-package com.paw.key.presentation.ui.course.util
+package com.paw.key.core.util
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
diff --git a/app/src/main/java/com/paw/key/core/util/flattenCoordinatesToLatLng.kt b/app/src/main/java/com/paw/key/core/util/flattenCoordinatesToLatLng.kt
new file mode 100644
index 00000000..37251be9
--- /dev/null
+++ b/app/src/main/java/com/paw/key/core/util/flattenCoordinatesToLatLng.kt
@@ -0,0 +1,15 @@
+package com.paw.key.core.util
+
+import com.naver.maps.geometry.LatLng
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toPersistentList
+
+fun flattenCoordinatesToLatLng(
+ coordinates: List>>>
+): ImmutableList> {
+ return coordinates.map { polygon -> // 각 Polygon
+ polygon.firstOrNull()?.map { point ->
+ LatLng(point.first, point.second)
+ }.orEmpty().toPersistentList()
+ }.toPersistentList()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/core/util/saveBitmapToCache.kt b/app/src/main/java/com/paw/key/core/util/saveBitmapToCache.kt
new file mode 100644
index 00000000..1333d8e5
--- /dev/null
+++ b/app/src/main/java/com/paw/key/core/util/saveBitmapToCache.kt
@@ -0,0 +1,56 @@
+package com.paw.key.core.util
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import androidx.core.content.FileProvider
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+
+// 혹시나 비트맵 사용 시
+fun saveBitmapToCache(
+ context: Context,
+ bitmap: Bitmap,
+ maxFiles: Int = 5, // 캐시 최대 개수
+ maxCacheSizeBytes: Long = 50L * 1024 * 1024, // 50mb,
+ childName : String = "map_snapshot_" // 이미지 이름 변경용
+): Result {
+ val cacheDir = context.cacheDir
+
+ // 기존 스냅샷 파일 목록 오래된 순 정렬
+ val existingFiles = cacheDir.listFiles { file ->
+ file.name.startsWith(childName) && file.extension == "png"
+ }?.sortedBy { it.lastModified() } ?: emptyList()
+
+ // 최대 개수 초과 시 오래된 파일 삭제 LRU 구현
+ if (existingFiles.size >= maxFiles) {
+ val filesToDelete = existingFiles.take(existingFiles.size - maxFiles + 1)
+ filesToDelete.forEach { it.delete() }
+ }
+
+ // 용량 기반 삭제
+ var totalSize = existingFiles.sumOf { it.length() }
+ val iterator = existingFiles.iterator()
+ while (totalSize > maxCacheSizeBytes && iterator.hasNext()) {
+ val file = iterator.next()
+ totalSize -= file.length()
+ file.delete()
+ }
+
+ val imageFile = File(cacheDir, "${childName}${System.currentTimeMillis()}.png")
+ return try {
+ FileOutputStream(imageFile).use { out ->
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
+ }
+ Result.success(
+ FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.fileprovider",
+ imageFile
+ )
+ )
+ } catch (e: IOException) {
+ Result.failure(e)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/data/di/AppModule.kt b/app/src/main/java/com/paw/key/data/di/AppModule.kt
index f86f1af8..fcb2ad21 100644
--- a/app/src/main/java/com/paw/key/data/di/AppModule.kt
+++ b/app/src/main/java/com/paw/key/data/di/AppModule.kt
@@ -1,11 +1,15 @@
package com.paw.key.data.di
+import android.content.ContentResolver
+import android.content.Context
import com.paw.key.BuildConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Named
+import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
@@ -16,4 +20,10 @@ object AppModule {
fun provideKakaoNativeKey(): String {
return BuildConfig.KAKAO_NATIVE_KEY
}
+
+ @Provides
+ @Singleton
+ fun provideContentResolver(@ApplicationContext context: Context): ContentResolver {
+ return context.contentResolver
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/data/dto/response/onboarding/OnboardingRegionResponse.kt b/app/src/main/java/com/paw/key/data/dto/response/onboarding/OnboardingRegionResponse.kt
index 4bb07413..a7aef12a 100644
--- a/app/src/main/java/com/paw/key/data/dto/response/onboarding/OnboardingRegionResponse.kt
+++ b/app/src/main/java/com/paw/key/data/dto/response/onboarding/OnboardingRegionResponse.kt
@@ -1,7 +1,3 @@
-import com.paw.key.domain.model.entity.onboarding.District
-import com.paw.key.domain.model.entity.onboarding.Dong
-import com.paw.key.domain.model.entity.onboarding.Gu
-import com.paw.key.domain.model.entity.onboarding.OnboardingRegion
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -48,31 +44,4 @@ data class DongDto(
@SerialName("name")
val name: String
-)
-
-fun DistrictResponse.toDomain(): OnboardingRegion {
- return OnboardingRegion(
- districtList = this.data.districtDtos.map { it.toDomain() }
- )
-}
-
-fun DistrictDto.toDomain(): District {
- return District(
- gu = this.gu.toDomain(),
- dongs = this.dongs.map { it.toDomain() }
- )
-}
-
-fun GuDto.toDomain(): Gu {
- return Gu(
- id = this.id,
- name = this.name
- )
-}
-
-fun DongDto.toDomain(): Dong {
- return Dong(
- id = this.id,
- name = this.name
- )
-}
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/RegionDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/RegionDataSource.kt
index fa39eae0..d3be31e8 100644
--- a/app/src/main/java/com/paw/key/data/remote/datasource/RegionDataSource.kt
+++ b/app/src/main/java/com/paw/key/data/remote/datasource/RegionDataSource.kt
@@ -7,4 +7,6 @@ class RegionDataSource @Inject constructor (
private val regionService: RegionService
) {
suspend fun getRegionGeometry(userId: Int, regionId: Int) = regionService.getRegionGeometry(userId, regionId)
+
+ suspend fun getRegionsList() = regionService.getRegionsList()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/RegionRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/RegionRepositoryImpl.kt
index 42d82910..8c435d2b 100644
--- a/app/src/main/java/com/paw/key/data/repositoryimpl/RegionRepositoryImpl.kt
+++ b/app/src/main/java/com/paw/key/data/repositoryimpl/RegionRepositoryImpl.kt
@@ -3,6 +3,8 @@ package com.paw.key.data.repositoryimpl
import com.paw.key.data.mapper.RegionMapper
import com.paw.key.data.remote.datasource.RegionDataSource
import com.paw.key.domain.model.entity.region.RegionDataEntity
+import com.paw.key.domain.model.entity.signup.DistrictEntity
+import com.paw.key.domain.model.entity.signup.toEntity
import com.paw.key.domain.repository.RegionRepository
import javax.inject.Inject
@@ -15,4 +17,8 @@ class RegionRepositoryImpl @Inject constructor(
mapper.mapDtoToEntity(it)
}
}
+
+ override suspend fun getRegionList(): Result> = runCatching {
+ regionDataSource.getRegionsList().data.districtDtos.map { it.toEntity() }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/data/service/RegionService.kt b/app/src/main/java/com/paw/key/data/service/RegionService.kt
index e262de63..ad3e1d3c 100644
--- a/app/src/main/java/com/paw/key/data/service/RegionService.kt
+++ b/app/src/main/java/com/paw/key/data/service/RegionService.kt
@@ -1,5 +1,6 @@
package com.paw.key.data.service
+import DistrictDataDto
import com.paw.key.data.dto.response.BaseResponse
import com.paw.key.data.dto.response.region.RegionResponseDto
import retrofit2.http.GET
@@ -12,4 +13,7 @@ interface RegionService {
@Header("X-USER-ID") userId: Int,
@Path("regionId") regionId: Int,
): BaseResponse
+
+ @GET("regions")
+ suspend fun getRegionsList(): BaseResponse
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/domain/model/entity/onboarding/OnboardingEntity.kt b/app/src/main/java/com/paw/key/domain/model/entity/onboarding/OnboardingEntity.kt
index e7046ab4..8cf30fc5 100644
--- a/app/src/main/java/com/paw/key/domain/model/entity/onboarding/OnboardingEntity.kt
+++ b/app/src/main/java/com/paw/key/domain/model/entity/onboarding/OnboardingEntity.kt
@@ -1,5 +1,7 @@
package com.paw.key.domain.model.entity.onboarding
+import com.paw.key.domain.model.entity.signup.DistrictEntity
+
data class OnboardingInfo(
val userId: Int,
val userName: String,
@@ -24,22 +26,7 @@ data class PetTraitCategoryOption(
)
data class OnboardingRegion(
- val districtList: List
-)
-
-data class District(
- val gu: Gu,
- val dongs: List
-)
-
-data class Gu(
- val id: Int,
- val name: String
-)
-
-data class Dong(
- val id: Int,
- val name: String
+ val districtList: List
)
data class PetTraitDto(
diff --git a/app/src/main/java/com/paw/key/domain/model/entity/signup/DistrictEntity.kt b/app/src/main/java/com/paw/key/domain/model/entity/signup/DistrictEntity.kt
new file mode 100644
index 00000000..85833a26
--- /dev/null
+++ b/app/src/main/java/com/paw/key/domain/model/entity/signup/DistrictEntity.kt
@@ -0,0 +1,41 @@
+package com.paw.key.domain.model.entity.signup
+
+import DistrictDto
+import DongDto
+import GuDto
+
+data class DistrictEntity(
+ val gu: Gu,
+ val dongs: List
+)
+
+data class Gu(
+ val id: Int,
+ val name: String
+)
+
+data class Dong(
+ val id: Int,
+ val name: String
+)
+
+fun DistrictDto.toEntity(): DistrictEntity {
+ return DistrictEntity(
+ gu = gu.toEntity(),
+ dongs = dongs.map { it.toEntity() }
+ )
+}
+
+fun GuDto.toEntity(): Gu {
+ return Gu(
+ id = id,
+ name = name
+ )
+}
+
+fun DongDto.toEntity(): Dong {
+ return Dong(
+ id = id,
+ name = name
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/domain/repository/RegionRepository.kt b/app/src/main/java/com/paw/key/domain/repository/RegionRepository.kt
index 05a06930..b74830df 100644
--- a/app/src/main/java/com/paw/key/domain/repository/RegionRepository.kt
+++ b/app/src/main/java/com/paw/key/domain/repository/RegionRepository.kt
@@ -1,7 +1,9 @@
package com.paw.key.domain.repository
import com.paw.key.domain.model.entity.region.RegionDataEntity
+import com.paw.key.domain.model.entity.signup.DistrictEntity
interface RegionRepository {
suspend fun getRegionGeometry(userId: Int, regionId: Int): Result
+ suspend fun getRegionList(): Result>
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/community/CommunityScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/community/CommunityScreen.kt
index 57c1f4f5..060ec9f6 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/community/CommunityScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/community/CommunityScreen.kt
@@ -4,7 +4,9 @@ import androidx.compose.foundation.Image
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.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -14,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import com.paw.key.R
import com.paw.key.core.designsystem.theme.PawKeyTheme
@@ -42,26 +45,37 @@ fun CommunityScreen(
snackBarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
- Column (
+ Column(
modifier = modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
- ){
+ ) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_community),
- contentDescription = stringResource(R.string.ic_community_description),
+ contentDescription = stringResource(R.string.ic_community_description)
)
- Text(
- text = "커뮤니티 기능은\n 아직 준비중이에요",
- modifier = modifier,
- style = PawKeyTheme.typography.body16Sb,
- color = PawKeyTheme.colors.gray300
- )
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "커뮤니티 기능은",
+ style = PawKeyTheme.typography.body16Sb,
+ color = PawKeyTheme.colors.gray300
+ )
+ Text(
+ text = "아직 준비중이에요",
+ style = PawKeyTheme.typography.body16Sb,
+ color = PawKeyTheme.colors.gray300
+ )
+ }
}
}
+
@Preview
@Composable
private fun CommunityScreenPreview() {
diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt
index 7510e675..fc5954dd 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/CourseOptionBottomSheet.kt
@@ -10,7 +10,6 @@ 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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt
index ef198e8a..5117cda1 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt
@@ -78,7 +78,7 @@ import com.paw.key.core.util.PreferenceDataStore
import com.paw.key.core.util.UiState
import com.paw.key.core.extension.noRippleClickable
import com.paw.key.presentation.ui.course.util.FusedLocationSource
-import com.paw.key.presentation.ui.course.util.PermissionRequestEffect
+import com.paw.key.core.util.PermissionRequestEffect
import com.paw.key.presentation.ui.course.util.StepCountListener
import com.paw.key.presentation.ui.course.util.rememberCustomFusedLocationSource
import com.paw.key.presentation.ui.course.util.rememberStepCounter
diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/WalkReviewScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/WalkReviewScreen.kt
index 01ac302a..3af5fb97 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/WalkReviewScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/WalkReviewScreen.kt
@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@@ -28,6 +27,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
@@ -39,11 +42,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
import coil.compose.AsyncImage
import com.paw.key.R
+import com.paw.key.core.designsystem.component.DataLoadingScreen
import com.paw.key.core.designsystem.component.PawkeyButton
import com.paw.key.core.designsystem.component.SubChip
import com.paw.key.core.designsystem.component.TopBar
import com.paw.key.core.designsystem.theme.PawKeyTheme
-import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewDialog
import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewFeedbackForm
import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewFeedbackHeader
import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewImageRow
@@ -51,6 +54,8 @@ import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewInfoHol
import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewTextField
import com.paw.key.presentation.ui.course.walkreview.state.WalkReviewContract
import com.paw.key.presentation.ui.course.walkreview.viewmodel.WalkReviewViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
@@ -67,6 +72,8 @@ fun WalkReviewRoute(
val state by viewModel.state.collectAsStateWithLifecycle()
val isValid = state.isValidForm
val context = LocalContext.current
+ var isLoading by remember { mutableStateOf(false) }
+ val coroutineScope = rememberCoroutineScope()
val lifecycleOwner = LocalLifecycleOwner.current
@@ -161,16 +168,25 @@ fun WalkReviewRoute(
viewModel.onImageDelete(it)
},
onClickPublic = { isShare ->
- viewModel.postWalkReview(
- routeId = routeId,
- isShare = isShare
- )
+ coroutineScope.launch {
+ isLoading = false
+ delay(3000L)
+ isLoading = true
+ viewModel.postWalkReview(
+ routeId = routeId,
+ isShare = isShare
+ )
+ }
},
/*navigateShared = {
navigateShared(routeId)
},*/
modifier = modifier,
)
+
+ if (isLoading) {
+ DataLoadingScreen()
+ }
}
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/component/DaytimeCard.kt b/app/src/main/java/com/paw/key/presentation/ui/home/component/DaytimeCard.kt
index f42fcbce..d0c6c52f 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/home/component/DaytimeCard.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/home/component/DaytimeCard.kt
@@ -43,7 +43,7 @@ fun DaytimeCard(
Box(
contentAlignment = Alignment.BottomCenter,
modifier = modifier
- .width(81.dp)
+ .fillMaxWidth()
.height(110.dp)
.background(
color = PawKeyTheme.colors.white1,
@@ -57,7 +57,7 @@ fun DaytimeCard(
.fillMaxWidth()
.padding(horizontal = 11.dp)
.padding(bottom = 14.dp)
- .zIndex(1F),
+ .zIndex(2F),
) {
Text(
text = daytime,
diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt b/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt
index df2cbca6..cfcfc023 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/home/component/TrackingCard.kt
@@ -42,8 +42,8 @@ fun TrackingCard(
) {
Box(
contentAlignment = Alignment.Center,
- modifier = Modifier
- .width(235.dp)
+ modifier = modifier
+ .fillMaxWidth()
.height(110.dp)
.background(
color = PawKeyTheme.colors.black,
diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt b/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt
index bb784688..09fdbe64 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt
@@ -30,7 +30,7 @@ import com.paw.key.presentation.ui.mypage.navigation.navigateSavedDetail
import com.paw.key.presentation.ui.mypage.navigation.navigateUserProfile
import com.paw.key.presentation.ui.onboard.navigation.navigateOnboarding
import com.paw.key.presentation.ui.region.navigation.navigateRegional
-import com.paw.key.presentation.ui.signup.navigation.navigateSignUpFlow
+import com.paw.key.presentation.ui.signup.navigation.navigateSignUp
import com.paw.key.presentation.ui.splash.navigation.Splash
class MainNavigator(
@@ -83,11 +83,6 @@ class MainNavigator(
navController.navigateLogin(navOptions = navOptions)
}
-
- fun navigateSignUp(navOptions: NavOptions? = null) {
- navController.navigateSignUpFlow(navOptions)
- }
-
fun navigateMyPage(navOptions: NavOptions? = null) {
navController.navigateMyPage(navOptions = navOptions)
}
@@ -117,8 +112,9 @@ class MainNavigator(
)
}
- fun navigateSignUpFlow(navOptions: NavOptions? = null) {
- navController.navigateSignUpFlow(navOptions)
+ // Todo : 나중에 로직 플로우 확인하고 수정예정
+ fun navigateSignUp(navOptions: NavOptions? = null) {
+ navController.navigateSignUp(navOptions)
}
fun navigateArchivedCourse(navOptions: NavOptions? = null) {
diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt b/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt
index a39e1e39..1b9ec1db 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/main/PawKeyNavHost.kt
@@ -1,10 +1,10 @@
package com.paw.key.presentation.ui.main
import android.os.Build
-import android.util.Log
import androidx.annotation.RequiresApi
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
@@ -19,7 +19,6 @@ import com.paw.key.presentation.ui.course.sharedwalk.review.navigation.sharedWal
import com.paw.key.presentation.ui.course.sharedwalk.sharedroute.navigation.sharedWalkCourseNavGraph
import com.paw.key.presentation.ui.course.walk.navigation.walkCourseNavGraph
import com.paw.key.presentation.ui.course.walkcomplete.navigation.walkCompletionNavGraph
-import com.paw.key.presentation.ui.course.walkreview.navigation.navigateWalkReview
import com.paw.key.presentation.ui.course.walkreview.navigation.walkReviewNavGraph
import com.paw.key.presentation.ui.dummy.navigation.dummyNavGraph
import com.paw.key.presentation.ui.dummy.next.dummyNextNavGraph
@@ -49,10 +48,30 @@ fun PawKeyNavHost(
NavHost(
navController = navigator.navController,
startDestination = navigator.startDestination,
- enterTransition = { EnterTransition.None },
- exitTransition = { ExitTransition.None },
- popEnterTransition = { EnterTransition.None },
- popExitTransition = { ExitTransition.None },
+ enterTransition = {
+ slideInHorizontally(
+ initialOffsetX = { fullWidth -> fullWidth },
+ animationSpec = tween(durationMillis = 300)
+ )
+ },
+ exitTransition = {
+ slideOutHorizontally(
+ targetOffsetX = { fullWidth -> -fullWidth },
+ animationSpec = tween(durationMillis = 300)
+ )
+ },
+ popEnterTransition = {
+ slideInHorizontally(
+ initialOffsetX = { fullWidth -> -fullWidth },
+ animationSpec = tween(durationMillis = 300)
+ )
+ },
+ popExitTransition = {
+ slideOutHorizontally(
+ targetOffsetX = { fullWidth -> fullWidth },
+ animationSpec = tween(durationMillis = 300)
+ )
+ },
) {
homeNavGraph(
paddingValues = paddingValues,
@@ -258,7 +277,7 @@ fun PawKeyNavHost(
onboardingNavGraph(
paddingValues = paddingValues,
navigateUp = navigator::navigateUp,
- navigateNext = navigator::navigateDummyNext,
+ navigateNext = navigator::navigateSignUp,
navigateSignUp = navigator::navigateLogin,
snackBarHostState = snackbarHostState
)
@@ -270,7 +289,7 @@ fun PawKeyNavHost(
navigator.navigateUp()
},
navigateNext = {
- navigator.navigateSignUpFlow()
+ //navigator.navigateSignUpFlow()
},
navigateHome = {
navigator.navigateHome()
@@ -286,7 +305,7 @@ fun PawKeyNavHost(
)
signUpNavGraph(
- navController = navigator.navController,
+ navigateUp = navigator::navigateUp,
navigateToHome = {
val options = navOptions {
popUpTo(0) { inclusive = true }
@@ -295,7 +314,5 @@ fun PawKeyNavHost(
navigator.navigateHome(options)
}
)
-
-
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt
index a29766cc..db2eee7a 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt
@@ -8,7 +8,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.paw.key.core.navigation.Route
import com.paw.key.presentation.ui.mypage.ArchivedDetailRoute
-import com.paw.key.presentation.ui.region.navigation.Regional
import kotlinx.serialization.Serializable
fun NavController.navigateArchivedDetail(
@@ -21,6 +20,7 @@ fun NavController.navigateArchivedDetail(
fun NavGraphBuilder.archivedDetailNavGraph(
navigateUp: () -> Unit,
+ //navigateDetail: () -> Unit,
navigateToSharedWalk: (Int, Int) -> Unit,
modifier: Modifier = Modifier,
) {
@@ -32,6 +32,7 @@ fun NavGraphBuilder.archivedDetailNavGraph(
navigateToSharedWalk = { routeId, pageId ->
navigateToSharedWalk(routeId, pageId)
},
+ //navigateDetail = navigateDetail,
routeId = archivedDetail.routeId,
pageId = archivedDetail.pageId,
modifier = modifier
diff --git a/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt
index cedb33bd..f3627097 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt
@@ -5,10 +5,8 @@ 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.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.material3.SnackbarHostState
import androidx.compose.runtime.Composable
@@ -22,6 +20,21 @@ import com.paw.key.core.designsystem.theme.PawKeyTheme
import com.paw.key.presentation.ui.onboard.component.OnboardPager
import com.paw.key.presentation.ui.onboard.component.OnboardingPosting
+@Preview(showBackground = true)
+@Composable
+private fun PreviewOnboardingScreen() {
+ PawKeyTheme {
+ OnboardingScreen(
+ paddingValues = PaddingValues(),
+ navigateUp = {},
+ navigateNext = {},
+ navigateSignUp = {},
+ snackBarHostState = SnackbarHostState(),
+ modifier = Modifier
+ )
+ }
+}
+
@Composable
fun OnboardingRoute(
paddingValues: PaddingValues,
@@ -43,21 +56,6 @@ fun OnboardingRoute(
)
}
-@Preview(showBackground = true)
-@Composable
-fun PreviewOnboardingScreen() {
- PawKeyTheme {
- OnboardingScreen(
- paddingValues = PaddingValues(),
- navigateUp = {},
- navigateNext = {},
- navigateSignUp = {},
- snackBarHostState = SnackbarHostState(),
- modifier = Modifier
- )
- }
-}
-
@Composable
fun OnboardingScreen(
paddingValues: PaddingValues,
@@ -110,7 +108,7 @@ fun OnboardingScreen(
PawkeyButton(
text = "신규 계정으로 회원가입",
enabled = true,
- onClick = { },
+ onClick = navigateNext, // Todo : 나중에 로그인, 회원가입 네이밍 수정
isBackGround = true,
isBorder = false
)
diff --git a/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt
index 71f0626f..6384ab3f 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt
@@ -24,11 +24,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -58,8 +56,8 @@ fun RegionalManagementRoute(
snackBarHostState: SnackbarHostState,
navigateUp: () -> Unit,
navigateNext: () -> Unit,
- regionId: Int,
modifier: Modifier = Modifier,
+ regionId: Int? = -1,
viewModel: RegionViewModel = hiltViewModel()
) {
val state by viewModel.state.collectAsStateWithLifecycle()
@@ -108,6 +106,7 @@ fun RegionalManagementRoute(
paddingValues = paddingValues,
snackBarHostState = snackBarHostState,
cameraPositionState = cameraPositionState,
+ regionId = regionId,
type = state.drawType,
regionCoordinates = uiState.data,
selectedRegion = state.selectedRegion,
@@ -139,6 +138,7 @@ fun RegionalManagementScreen(
cameraPositionState: CameraPositionState,
type: DrawType,
regionCoordinates: ImmutableList>,
+ regionId: Int?,
selectedRegion: String?,
preRegionName: String?,
regionName: String?,
@@ -152,7 +152,7 @@ fun RegionalManagementScreen(
SnackbarHost(
hostState = snackBarHostState,
modifier = Modifier.padding(
- bottom = LocalConfiguration.current.screenHeightDp.dp * 0.4f
+ bottom = LocalWindowInfo.current.containerSize.height.dp * 0.4f
)
) { data ->
CustomSnackBar(
@@ -177,7 +177,7 @@ fun RegionalManagementScreen(
if (singlePolygonCoords.isNotEmpty()) {
PolygonOverlay(
coords = singlePolygonCoords,
- color = PawKeyTheme.colors.green500.copy(alpha = 0.3f),
+ color = PawKeyTheme.colors.opacityPrimary.copy(alpha = 0.3f),
outlineWidth = 1.dp,
outlineColor = PawKeyTheme.colors.green500
)
@@ -188,7 +188,7 @@ fun RegionalManagementScreen(
regionCoordinates.forEach {
PolygonOverlay(
coords = it,
- color = PawKeyTheme.colors.green500.copy(alpha = 0.3f),
+ color = PawKeyTheme.colors.opacityPrimary.copy(alpha = 0.3f),
outlineWidth = 1.dp,
outlineColor = PawKeyTheme.colors.green500
)
@@ -222,122 +222,66 @@ fun RegionalManagementScreen(
) {
Text(
text = "선택한 위치",
- style = PawKeyTheme.typography.head20B2,
- color = PawKeyTheme.colors.black
+ style = PawKeyTheme.typography.header3,
+ color = PawKeyTheme.colors.contents
)
Text(
text = regionName ?: "강남구 역삼동",
- style = PawKeyTheme.typography.head20B2,
- color = PawKeyTheme.colors.green500
+ style = PawKeyTheme.typography.header3,
+ color = PawKeyTheme.colors.primary
)
}
Spacer(modifier = Modifier.height(12.dp))
- if (regionName == preRegionName) {
- Text(
- text = "기존에 산책하던 지역은\n" +
- "기존 지역과 같은 동네에요.",
- style = PawKeyTheme.typography.body14M,
- color = PawKeyTheme.colors.gray500,
- modifier = Modifier
- .padding(bottom = 12.dp)
- )
-
- Spacer(modifier = Modifier.height(12.dp))
-
- PawkeyButton(
- text = "지역 변경하기",
- onClick = {
- onClickButton()
- },
- modifier = Modifier
- .fillMaxWidth(),
- enabled = false,
- )
- } else {
- Text(
- text = "기존에 산책하던 지역은 ${preRegionName}이에요.\n선택한 위치로 산책 지역을 변경하시겠어요?",
- style = PawKeyTheme.typography.body14M,
- color = PawKeyTheme.colors.gray500,
- modifier = Modifier
- .padding(bottom = 12.dp)
- )
-
- Spacer(modifier = Modifier.height(12.dp))
-
- PawkeyButton(
- text = "지역 변경하기",
- onClick = {
- onClickButton()
- },
- modifier = Modifier
- .fillMaxWidth(),
- enabled = true,
- )
- }
- }
- }
- }
-}
+ if (regionId == -1) {
+ if (regionName == preRegionName) {
+ Text(
+ text = "기존에 산책하던 지역은\n" +
+ "기존 지역과 같은 동네에요.",
+ style = PawKeyTheme.typography.bodyDefault,
+ color = PawKeyTheme.colors.gray500,
+ modifier = Modifier
+ .padding(bottom = 12.dp)
+ )
-@Preview(showBackground = true)
-@Composable
-private fun RadiusTestPreview() {
- PawKeyTheme {
- Scaffold(
- modifier = Modifier
- .fillMaxSize()
- .background(Color.Blue)
- ) { paddingValues ->
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(Color.Blue)
- .padding(paddingValues)
- ) {
- Box(
- modifier = Modifier
- .weight(1f),
- )
+ Spacer(modifier = Modifier.height(12.dp))
- Column(
- modifier = Modifier
- .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp))
- .background(
- color = PawKeyTheme.colors.black,
- shape = RoundedCornerShape(
- topStart = 16.dp, topEnd = 16.dp
- )
+ PawkeyButton(
+ text = "지역 변경하기",
+ onClick = {
+ onClickButton()
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ enabled = false,
)
- .padding(horizontal = 16.dp, vertical = 24.dp)
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 8.dp, bottom = 16.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
+ } else {
Text(
- text = "선택한 위치",
- style = PawKeyTheme.typography.head20B2,
- color = PawKeyTheme.colors.black
+ text = "기존에 산책하던 지역은 ${preRegionName}이에요.\n선택한 위치로 산책 지역을 변경하시겠어요?",
+ style = PawKeyTheme.typography.bodyDefault,
+ color = PawKeyTheme.colors.gray500,
+ modifier = Modifier
+ .padding(bottom = 12.dp)
)
- Text(
- text = "강남구 역삼동",
- style = PawKeyTheme.typography.head20B2,
- color = PawKeyTheme.colors.green500
+ Spacer(modifier = Modifier.height(12.dp))
+
+ PawkeyButton(
+ text = "지역 변경하기",
+ onClick = {
+ onClickButton()
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ enabled = true,
)
}
-
- Spacer(modifier = Modifier.height(12.dp))
-
+ } else {
Text(
- text = "기존에 산책하던 지역은이에요.\n선택한 위치로 산책 지역을 변경하시겠어요?",
- style = PawKeyTheme.typography.body14M,
+ text = "선택한 산책 지역은 ${regionName}이에요.\n이 위치로 산책 지역을 설정하시겠어요?",
+ style = PawKeyTheme.typography.bodyDefault,
color = PawKeyTheme.colors.gray500,
modifier = Modifier
.padding(bottom = 12.dp)
@@ -346,9 +290,9 @@ private fun RadiusTestPreview() {
Spacer(modifier = Modifier.height(12.dp))
PawkeyButton(
- text = "지역 변경하기",
+ text = "선택",
onClick = {
-
+ onClickButton()
},
modifier = Modifier
.fillMaxWidth(),
@@ -358,4 +302,4 @@ private fun RadiusTestPreview() {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/region/navigation/RegionalNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/region/navigation/RegionalNavigation.kt
index 101ea8f6..ae89a818 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/region/navigation/RegionalNavigation.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/region/navigation/RegionalNavigation.kt
@@ -7,7 +7,6 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
-import androidx.navigation.toRoute
import com.paw.key.core.navigation.Route
import com.paw.key.presentation.ui.region.RegionalManagementRoute
import kotlinx.serialization.Serializable
@@ -26,14 +25,12 @@ fun NavGraphBuilder.regionalNavGraph(
snackBarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
- composable {backStackEntry ->
- val regional = backStackEntry.toRoute()
+ composable {
RegionalManagementRoute(
paddingValues = paddingValues,
snackBarHostState = snackBarHostState,
navigateUp = navigateUp,
navigateNext = navigateNext,
- regionId = regional.regionId,
modifier = modifier
)
}
@@ -41,5 +38,5 @@ fun NavGraphBuilder.regionalNavGraph(
@Serializable
data class Regional(
- val regionId: Int
+ val regionId: Int? = -1
) : Route
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/region/viewmodel/RegionViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/region/viewmodel/RegionViewModel.kt
index e12a3dc3..331ec7d1 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/region/viewmodel/RegionViewModel.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/region/viewmodel/RegionViewModel.kt
@@ -5,9 +5,9 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
-import com.naver.maps.geometry.LatLng
import com.paw.key.core.util.PreferenceDataStore
import com.paw.key.core.util.UiState
+import com.paw.key.core.util.flattenCoordinatesToLatLng
import com.paw.key.core.util.handleError
import com.paw.key.domain.repository.RegionRepository
import com.paw.key.domain.repository.home.HomeRegionRepository
@@ -16,7 +16,6 @@ import com.paw.key.presentation.ui.region.state.DrawType
import com.paw.key.presentation.ui.region.state.RegionSideEffect
import com.paw.key.presentation.ui.region.state.RegionState
import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -28,6 +27,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@@ -53,18 +53,28 @@ class RegionViewModel @Inject constructor(
)
init {
- viewModelScope.launch {
- Log.e("RegionViewModel", "regionId: ${regionIdState.regionId}")
- val validUserId = userId.filter { it != -1 }.first()
- getRegionGeometry(
- userId = validUserId,
- regionId = regionIdState.regionId,
- )
+ if (regionIdState.regionId != -1) {
+ viewModelScope.launch {
+ val validUserId = userId.filter { it != -1 }.first()
+ getRegionGeometry(
+ userId = validUserId,
+ regionId = regionIdState.regionId,
+ )
+ }
+ } else {
+ viewModelScope.launch {
+ Timber.e("RegionViewModel test용 regionId: ${regionIdState.regionId}")
+ val validUserId = userId.filter { it != -1 }.first()
+ getRegionGeometry(
+ userId = validUserId,
+ regionId = 39,
+ )
+ }
}
}
- fun getRegionGeometry(userId: Int, regionId: Int) = viewModelScope.launch {
- regionRepository.getRegionGeometry(userId, regionId)
+ fun getRegionGeometry(userId: Int, regionId: Int?) = viewModelScope.launch {
+ regionRepository.getRegionGeometry(userId, regionId!!)
.onSuccess { data ->
val coordinates = data.geometry.coordinates
val flattenedLatLng = flattenCoordinatesToLatLng(coordinates)
@@ -103,7 +113,6 @@ class RegionViewModel @Inject constructor(
}
}
.onFailure { throwable ->
- Log.e("RegionViewModel", "API 호출 실패", throwable)
val errorMessage = handleError(throwable)
_state.update {
it.copy(
@@ -114,21 +123,23 @@ class RegionViewModel @Inject constructor(
}
fun patchRegion() {
- viewModelScope.launch {
- homeRepository.patchRegion(userId.value, regionIdState.regionId)
- .onSuccess { data ->
- Log.d("RegionViewModel", "API 응답 성공: $data")
- _sideEffect.emit(
- RegionSideEffect.ShowSnackBar("지역을 ${state.value.regionName ?: "역삼동"}으로 변경했어요.")
- )
- }
- .onFailure { throwable ->
- Log.e("RegionViewModel", "API 호출 실패", throwable)
- val errorMessage = handleError(throwable)
- _sideEffect.emit(
- RegionSideEffect.ShowSnackBar(errorMessage)
- )
- }
+ if (regionIdState.regionId != -1) {
+ viewModelScope.launch {
+ homeRepository.patchRegion(userId.value, regionIdState.regionId!!)
+ .onSuccess { data ->
+ Log.d("RegionViewModel", "API 응답 성공: $data")
+ _sideEffect.emit(
+ RegionSideEffect.ShowSnackBar("지역을 ${state.value.regionName ?: "역삼동"}으로 변경했어요.")
+ )
+ }
+ .onFailure { throwable ->
+ Log.e("RegionViewModel", "API 호출 실패", throwable)
+ val errorMessage = handleError(throwable)
+ _sideEffect.emit(
+ RegionSideEffect.ShowSnackBar(errorMessage)
+ )
+ }
+ }
}
}
@@ -154,12 +165,3 @@ private fun flattenCoordinatesToLatLng(
}
*/
-private fun flattenCoordinatesToLatLng(
- coordinates: List>>>
-): ImmutableList> {
- return coordinates.map { polygon -> // 각 Polygon
- polygon.firstOrNull()?.map { point ->
- LatLng(point.first, point.second)
- }.orEmpty().toPersistentList()
- }.toPersistentList()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpActivityScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpActivityScreen.kt
deleted file mode 100644
index 6cf8d740..00000000
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpActivityScreen.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.paw.key.presentation.ui.signup
-
-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.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.paw.key.R
-import com.paw.key.core.designsystem.component.PawkeyButton
-import com.paw.key.core.designsystem.theme.PawKeyTheme
-import com.paw.key.presentation.ui.signup.component.FormField
-import com.paw.key.presentation.ui.signup.component.LocationItem
-import com.paw.key.presentation.ui.signup.component.LocationItemList
-import com.paw.key.presentation.ui.signup.component.LocationList
-import com.paw.key.presentation.ui.signup.component.SignUpHeader
-import com.paw.key.presentation.ui.signup.viewmodel.SignUpViewModel
-
-@Preview(showBackground = true)
-@Composable
-private fun PreviewSignUpActivityScreen() {
- PawKeyTheme {
- SignUpActivityScreen(
- step = 0.5F,
- navigateSignUpDog = {},
- viewModel = hiltViewModel()
- )
- }
-}
-
-@Composable
-fun SignUpActivityRoute(
- navigateSignUpDog: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: SignUpViewModel? = null
-) {
-
- SignUpActivityScreen(
- step = 0.5F,
- navigateSignUpDog = navigateSignUpDog,
- modifier = modifier,
- viewModel = viewModel ?: hiltViewModel()
- )
-}
-
-@Composable
-fun SignUpActivityScreen(
- step: Float,
- navigateSignUpDog: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: SignUpViewModel
-) {
- val state by viewModel.state.collectAsStateWithLifecycle()
- val regionList by viewModel.regionList.collectAsStateWithLifecycle()
-
- val selectedGu = state.selectedGu
- val selectedDong = state.selectedDong
-
- val guOptions = regionList.map { it.gu.name }
-
- val dongOptions = if (selectedGu.isNotEmpty()) {
- regionList.find { it.gu.name == selectedGu }?.dongs?.map {
- LocationItem(id = it.id, name = it.name)
- } ?: emptyList()
- } else {
- emptyList()
- }
-
- Column(
- modifier = modifier.fillMaxSize()
- ) {
- SignUpHeader(
- title = stringResource(R.string.ic_onboarding_signup),
- subtitle = stringResource(id = R.string.ic_onboarding_signup_subtitle_step2),
- progress = step,
- )
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(horizontal = 16.dp)
- ) {
- Spacer(modifier = Modifier.height(27.dp))
-
- // 지역구 섹션 - 처음부터 모든 구 칩들을 보여줌
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_main_location),
- content = {
- LocationList(
- selected = selectedGu,
- locations = guOptions,
- onLocationSelected = { guName ->
- val selectedGuItem = regionList.find { it.gu.name == guName }
- selectedGuItem?.let {
- viewModel.onGuSelected(it.gu.name, it.gu.id)
- }
- }
- )
- }
- )
-
- Spacer(modifier = Modifier.height(46.dp))
-
- if (selectedGu.isNotEmpty()) {
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_sub_location),
- content = {
- LocationItemList(
- selected = selectedDong,
- locations = dongOptions,
- onLocationSelected = { locationItem ->
- viewModel.onDongSelected(locationItem.name, locationItem.id)
- }
- )
- }
- )
- }
-
- Spacer(modifier = Modifier.weight(1f))
-
- val isFormValid = selectedGu.isNotEmpty() && selectedDong.isNotEmpty()
-
- PawkeyButton(
- text = stringResource(id = R.string.ic_onboarding_signup_button),
- enabled = isFormValid,
- onClick = {
- if (isFormValid) {
- navigateSignUpDog()
- }
- }
- )
-
- Spacer(modifier = Modifier.height(46.dp))
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt
deleted file mode 100644
index 92a0b3a4..00000000
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpDogScreen.kt
+++ /dev/null
@@ -1,527 +0,0 @@
-package com.paw.key.presentation.ui.signup
-
-import android.Manifest
-import android.net.Uri
-import android.os.Build
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.PickVisualMediaRequest
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.RequiresApi
-import androidx.compose.animation.core.FastOutSlowInEasing
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-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.imePadding
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material3.Icon
-import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.res.vectorResource
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import coil.compose.AsyncImage
-import com.paw.key.R
-import com.paw.key.core.designsystem.component.PawkeyButton
-import com.paw.key.core.designsystem.theme.PawKeyTheme
-import com.paw.key.core.extension.noRippleClickable
-import com.paw.key.presentation.ui.signup.component.FormField
-import com.paw.key.presentation.ui.signup.component.SignUpTextField
-import com.paw.key.presentation.ui.signup.component.SignUpUserSelectButton
-import com.paw.key.presentation.ui.signup.state.SignUpContract
-import com.paw.key.presentation.ui.signup.viewmodel.SignUpViewModel
-
-@Preview(showBackground = true)
-@Composable
-private fun PreviewSignUpDogScreen() {
- PawKeyTheme {
- // Preview content
- }
-}
-
-@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
-@Composable
-fun SignUpDogRoute(
- navigateNext: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: SignUpViewModel? = null
-) {
- val actualViewModel = viewModel ?: hiltViewModel()
- SignUpDogScreen(
- progress = 0.75F,
- navigateNext = navigateNext,
- modifier = modifier,
- viewModel = actualViewModel
- )
-}
-
-private fun isAgeValid(ageKnown: SignUpContract.AgeKnown, dogAge: String): Boolean {
- return when (ageKnown) {
- SignUpContract.AgeKnown.KNOWN -> dogAge.isNotEmpty()
- SignUpContract.AgeKnown.UNKNOWN -> true
- SignUpContract.AgeKnown.NONE -> false
- }
-}
-
-@RequiresApi(Build.VERSION_CODES.TIRAMISU)
-@Composable
-fun SignUpDogScreen(
- navigateNext: () -> Unit,
- modifier: Modifier = Modifier,
- progress: Float = 1F,
- viewModel: SignUpViewModel
-) {
- val state by viewModel.state.collectAsState()
- val keyboardController = LocalSoftwareKeyboardController.current
- val focusManager = LocalFocusManager.current
-
- val dogNameFocusRequester = remember { FocusRequester() }
- val dogBreedFocusRequester = remember { FocusRequester() }
- val dogAgeFocusRequester = remember { FocusRequester() }
-
- val animatedProgress by animateFloatAsState(
- targetValue = progress,
- animationSpec = tween(
- durationMillis = 1000,
- easing = FastOutSlowInEasing
- ),
- label = "progress_animation"
- )
-
- val imagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- Manifest.permission.READ_MEDIA_IMAGES
- } else {
- Manifest.permission.READ_EXTERNAL_STORAGE
- }
-
- val pickSingleMediaLauncher = rememberLauncherForActivityResult(
- ActivityResultContracts.PickVisualMedia()
- ) { uri ->
- if (uri != null) {
- viewModel.onDogImageSelected(uri)
- }
- }
-
- val galleryLauncher = rememberLauncherForActivityResult(
- ActivityResultContracts.GetContent()
- ) { uri: Uri? ->
- if (uri != null) {
- viewModel.onDogImageSelected(uri)
- }
- }
-
- val permissionLauncher = rememberLauncherForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) { isGranted ->
- if (isGranted) {
- galleryLauncher.launch("image/*")
- }
- }
-
- val onClickImage = {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- pickSingleMediaLauncher.launch(
- PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
- )
- } else {
- permissionLauncher.launch(imagePermission)
- }
- }
-
- val isFormValid = remember(state.dogName, state.dogGender, state.dogBreed, state.ageKnown, state.dogAge) {
- state.dogName.isNotEmpty() &&
- state.dogGender != SignUpContract.DogGender.UNKNOWN &&
- state.dogBreed.isNotEmpty() &&
- isAgeValid(state.ageKnown, state.dogAge)
- }
-
- val hideKeyboardAndClearFocus = {
- keyboardController?.hide()
- focusManager.clearFocus()
- }
-
- val proceedToNext = {
- if (isFormValid) {
- hideKeyboardAndClearFocus()
- navigateNext()
- }
- }
-
- val requestFocusSafely = { focusRequester: FocusRequester ->
- try {
- focusRequester.requestFocus()
- } catch (e: Exception) {
-
- }
- }
-
- Box(
- modifier = modifier
- .fillMaxSize()
- ) {
- Column(modifier = Modifier.fillMaxSize()) {
- // 헤더
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
- ) {
- Text(
- text = stringResource(id = R.string.ic_onboarding_signup),
- color = PawKeyTheme.colors.black,
- style = PawKeyTheme.typography.body16Sb,
- modifier = Modifier.padding(top = 16.dp),
- )
-
- Spacer(modifier = Modifier.height(16.dp))
-
- LinearProgressIndicator(
- progress = { animatedProgress },
- modifier = Modifier
- .fillMaxWidth()
- .height(2.dp),
- color = PawKeyTheme.colors.green500,
- trackColor = PawKeyTheme.colors.gray100,
- strokeCap = StrokeCap.Square,
- gapSize = 0.dp,
- drawStopIndicator = {}
- )
- }
-
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(32.dp),
- contentPadding = PaddingValues(16.dp),
- modifier = Modifier.fillMaxSize()
- ) {
- item {
- Text(
- text = stringResource(id = R.string.ic_onboarding_signup_subtitle_step3),
- color = PawKeyTheme.colors.black,
- style = PawKeyTheme.typography.head22Sb,
- modifier = Modifier.padding(top = 20.dp)
- )
- }
-
- item {
- DogProfileImage(
- dogImage = state.dogImage,
- onClickImage = onClickImage
- )
- }
-
- item {
- DogNameField(
- value = state.dogName,
- onValueChange = viewModel::onDogNameChanged,
- focusRequester = dogNameFocusRequester,
- onNext = { requestFocusSafely(dogBreedFocusRequester) }
- )
- }
-
- item {
- Column {
- DogGenderSection(
- selectedGender = state.dogGender,
- onGenderSelected = { gender ->
- viewModel.selectDogGender(gender)
- requestFocusSafely(dogBreedFocusRequester)
- }
- )
- Spacer(modifier = Modifier.height(10.dp))
- NeuteringCheckbox(
- isNeutered = state.isNeutered,
- onToggle = viewModel::toggleNeutering
- )
- }
- }
-
- item {
- DogBreedField(
- value = state.dogBreed,
- onValueChange = viewModel::onDogBreedChanged,
- focusRequester = dogBreedFocusRequester,
- onNext = { focusManager.clearFocus() }
- )
- }
-
- item {
- DogAgeSection(
- ageKnown = state.ageKnown,
- dogAge = state.dogAge,
- onAgeKnownSelected = { ageKnown ->
- viewModel.selectAgeKnown(ageKnown)
- if (ageKnown == SignUpContract.AgeKnown.KNOWN) {
- requestFocusSafely(dogAgeFocusRequester)
- }
- },
- onDogAgeChanged = viewModel::onDogAgeChanged,
- focusRequester = dogAgeFocusRequester,
- onDone = proceedToNext
- )
- }
-
- item { Spacer(modifier = Modifier.height(80.dp)) }
- }
- }
-
- PawkeyButton(
- text = "다음으로",
- enabled = isFormValid,
- onClick = proceedToNext,
- modifier = Modifier
- .align(Alignment.BottomCenter)
- .fillMaxWidth()
- .padding(horizontal = 16.dp, vertical = 46.dp)
- )
- }
-}
-
-@Composable
-private fun DogProfileImage(
- dogImage: Uri?,
- onClickImage: () -> Unit
-) {
- Column {
- Box(
- contentAlignment = Alignment.Center,
- modifier = Modifier
- .fillMaxWidth()
- .height(96.dp)
- ) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = Modifier
- .size(96.dp)
- .clip(CircleShape)
- .border(
- width = 2.dp,
- color = if (dogImage != null) PawKeyTheme.colors.green500 else PawKeyTheme.colors.white2,
- shape = CircleShape
- )
- .background(PawKeyTheme.colors.white2)
- .noRippleClickable { onClickImage() }
- ) {
- if (dogImage != null) {
- AsyncImage(
- model = dogImage,
- contentDescription = "강아지 프로필 이미지",
- contentScale = ContentScale.Crop,
- modifier = Modifier
- .size(92.dp)
- .clip(CircleShape)
- )
- } else {
- Icon(
- imageVector = ImageVector.vectorResource(R.drawable.ic_onboarding_img_plus),
- contentDescription = "앨범",
- tint = PawKeyTheme.colors.gray100
- )
- }
- }
- }
- }
-}
-
-@Composable
-private fun DogNameField(
- value: String,
- onValueChange: (String) -> Unit,
- focusRequester: FocusRequester,
- onNext: () -> Unit
-) {
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_dog_name),
- content = {
- SignUpTextField(
- value = value,
- onValueChange = onValueChange,
- placeholder = "강아지 이름을 입력해주세요",
- modifier = Modifier.focusRequester(focusRequester),
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Next,
- keyboardType = KeyboardType.Text
- ),
- keyboardActions = KeyboardActions(
- onNext = { onNext() }
- )
- )
- }
- )
-}
-
-@Composable
-private fun DogGenderSection(
- selectedGender: SignUpContract.DogGender,
- onGenderSelected: (SignUpContract.DogGender) -> Unit,
-) {
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_gender),
- content = {
- Row(
- horizontalArrangement = Arrangement.spacedBy(10.dp),
- modifier = Modifier.fillMaxWidth()
- ) {
- SignUpUserSelectButton(
- user = "남성",
- isSelect = selectedGender == SignUpContract.DogGender.MALE,
- onClick = { onGenderSelected(SignUpContract.DogGender.MALE) },
- modifier = Modifier.weight(1f)
- )
- SignUpUserSelectButton(
- user = "여성",
- isSelect = selectedGender == SignUpContract.DogGender.FEMALE,
- onClick = { onGenderSelected(SignUpContract.DogGender.FEMALE) },
- modifier = Modifier.weight(1f)
- )
- }
- }
- )
-}
-
-@Composable
-private fun NeuteringCheckbox(
- isNeutered: Boolean,
- onToggle: () -> Unit,
-) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- modifier = Modifier
- .fillMaxWidth()
- .noRippleClickable { onToggle() }
- ) {
- Icon(
- imageVector = if (isNeutered)
- ImageVector.vectorResource(R.drawable.ic_roundcheck_valid)
- else
- ImageVector.vectorResource(R.drawable.ic_roundcheck_invalid),
- contentDescription = "",
- tint = Color.Unspecified
- )
- Text(
- text = "중성화했어요",
- color = if (isNeutered)
- PawKeyTheme.colors.black
- else PawKeyTheme.colors.gray300,
- style = PawKeyTheme.typography.body14Sb
- )
- }
-}
-
-@Composable
-private fun DogBreedField(
- value: String,
- onValueChange: (String) -> Unit,
- focusRequester: FocusRequester,
- onNext: () -> Unit
-) {
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_dog_breed),
- content = {
- SignUpTextField(
- value = value,
- onValueChange = onValueChange,
- placeholder = "견종을 입력해주세요",
- modifier = Modifier.focusRequester(focusRequester),
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Next,
- keyboardType = KeyboardType.Text
- ),
- keyboardActions = KeyboardActions(
- onNext = { onNext() }
- )
- )
- }
- )
-}
-
-@Composable
-private fun DogAgeSection(
- ageKnown: SignUpContract.AgeKnown,
- dogAge: String,
- onAgeKnownSelected: (SignUpContract.AgeKnown) -> Unit,
- onDogAgeChanged: (String) -> Unit,
- focusRequester: FocusRequester,
- onDone: () -> Unit
-) {
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_age),
- content = {
- Column(
- verticalArrangement = Arrangement.spacedBy(10.dp)
- ) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(10.dp),
- modifier = Modifier.fillMaxWidth()
- ) {
- SignUpUserSelectButton(
- user = "나이를 알아요",
- isSelect = ageKnown == SignUpContract.AgeKnown.KNOWN,
- onClick = {
- onAgeKnownSelected(SignUpContract.AgeKnown.KNOWN)
- onDogAgeChanged("")
- },
- modifier = Modifier.weight(1f)
- )
-
- SignUpUserSelectButton(
- user = "나이를 몰라요",
- isSelect = ageKnown == SignUpContract.AgeKnown.UNKNOWN,
- onClick = {
- onAgeKnownSelected(SignUpContract.AgeKnown.UNKNOWN)
- onDogAgeChanged("")
- },
- modifier = Modifier.weight(1f)
- )
- }
-
- if (ageKnown == SignUpContract.AgeKnown.KNOWN) {
- SignUpTextField(
- value = dogAge,
- onValueChange = onDogAgeChanged,
- placeholder = "나이를 입력해주세요",
- modifier = Modifier.focusRequester(focusRequester),
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Done,
- keyboardType = KeyboardType.Number
- ),
- keyboardActions = KeyboardActions(
- onDone = { onDone() }
- )
- )
- }
- }
- }
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpLevelScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpLevelScreen.kt
deleted file mode 100644
index e314bad4..00000000
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpLevelScreen.kt
+++ /dev/null
@@ -1,203 +0,0 @@
-package com.paw.key.presentation.ui.signup
-
-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.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import com.paw.key.R
-import com.paw.key.core.designsystem.component.PawkeyButton
-import com.paw.key.core.designsystem.theme.PawKeyTheme
-import com.paw.key.presentation.ui.signup.component.SignUpHeader
-import com.paw.key.presentation.ui.signup.component.SignUpUserSelectButton
-import com.paw.key.presentation.ui.signup.state.SignUpContract
-import com.paw.key.presentation.ui.signup.viewmodel.SignUpViewModel
-
-@Preview(showBackground = true)
-@Composable
-private fun PreviewSignUpLevelScreen() {
- PawKeyTheme {
- SignUpLevelScreen(
- onSignUpClick = {},
- selectedEnergyLevel = "",
- selectedSocialLevel = "",
- energyOptions = listOf("매우 차분해요", "조금 느긋해요"),
- socialOptions = listOf("잘 어울려요", "천천히 친해져요"),
- energyTitle = "에너지 레벨",
- socialTitle = "사회성 레벨",
- viewModel = hiltViewModel()
- )
- }
-}
-
-@Composable
-fun SignUpLevelRoute(
- navigateNext: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: SignUpViewModel? = null
-) {
- val actualViewModel = viewModel ?: hiltViewModel()
- val state by actualViewModel.state.collectAsState()
- val context = LocalContext.current
-
- LaunchedEffect(actualViewModel.sideEffect) {
- actualViewModel.sideEffect.collect { sideEffect ->
- when (sideEffect) {
- is SignUpContract.SignUpSideEffect.NavigateNext -> {
- navigateNext()
- }
- is SignUpContract.SignUpSideEffect.ShowSnackBar -> {
- println("SnackBar: ${sideEffect.message}")
- }
- is SignUpContract.SignUpSideEffect.NavigateUp -> {
-
- }
- }
- }
- }
-
- val energyCategory = state.petTraitCategoryList.find { it.petTraitCategoryName == "에너지레벨" }
- val socialCategory = state.petTraitCategoryList.find { it.petTraitCategoryName == "사회성레벨" }
-
- val energyOptions = energyCategory?.petTraitCategoryOptions?.map { it.petTraitCategoryOptionText } ?: emptyList()
- val socialOptions = socialCategory?.petTraitCategoryOptions?.map { it.petTraitCategoryOptionText } ?: emptyList()
-
- val isButtonEnabled = state.selectedEnergyLevel.isNotEmpty() &&
- state.selectedSocialLevel.isNotEmpty()
-
- SignUpLevelScreen(
- enabled = isButtonEnabled,
- selectedEnergyLevel = state.selectedEnergyLevel,
- selectedSocialLevel = state.selectedSocialLevel,
- onSignUpClick = {
- println("SignUp button clicked")
- actualViewModel.debugSignUpState()
- actualViewModel.signUp(context)
- },
- energyOptions = energyOptions,
- socialOptions = socialOptions,
- energyTitle = "에너지 레벨",
- socialTitle = "사회성 레벨",
- modifier = modifier,
- viewModel = actualViewModel
- )
-}
-
-@Composable
-fun SignUpLevelScreen(
- onSignUpClick: () -> Unit,
- modifier: Modifier = Modifier,
- enabled: Boolean = false,
- selectedEnergyLevel: String = "",
- selectedSocialLevel: String = "",
- energyOptions: List,
- socialOptions: List,
- energyTitle: String = "에너지 레벨",
- socialTitle: String = "사회성 레벨",
- viewModel: SignUpViewModel
-) {
- Column(
- modifier = modifier.fillMaxSize()
- ) {
- SignUpHeader(
- title = stringResource(id = R.string.ic_onboarding_signup),
- subtitle = stringResource(id = R.string.ic_onboarding_signup_subtitle_step4)
- )
-
- Spacer(modifier = Modifier.height(42.dp))
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(horizontal = 16.dp)
- ) {
- LevelSection(
- title = energyTitle,
- options = energyOptions.chunked(2),
- selectedOption = selectedEnergyLevel,
- onOptionClick = { option ->
- println("Energy level selected: $option")
- viewModel.selectEnergyLevel(option)
- }
- )
-
- Spacer(modifier = Modifier.height(36.dp))
-
- LevelSection(
- title = socialTitle,
- options = socialOptions.chunked(2),
- selectedOption = selectedSocialLevel,
- onOptionClick = { option ->
- println("Social level selected: $option")
- viewModel.selectSocialLevel(option)
- }
- )
-
- Spacer(modifier = Modifier.weight(1f))
-
- PawkeyButton(
- text = stringResource(id = R.string.ic_onboarding_signup_button),
- enabled = enabled,
- onClick = {
- println("Button clicked, enabled: $enabled")
- onSignUpClick()
- }
- )
-
- Spacer(modifier = Modifier.height(46.dp))
- }
- }
-}
-
-@Composable
-private fun LevelSection(
- title: String,
- options: List>,
- selectedOption: String,
- onOptionClick: (String) -> Unit,
-) {
- Column {
- Text(
- text = title,
- style = PawKeyTheme.typography.body14Sb,
- color = PawKeyTheme.colors.black
- )
-
- Spacer(modifier = Modifier.height(12.dp))
-
- options.forEach { rowOptions ->
- Row(
- horizontalArrangement = Arrangement.spacedBy(10.dp),
- modifier = Modifier
- .fillMaxWidth()
- .padding(bottom = 10.dp)
- ) {
- rowOptions.forEach { option ->
- SignUpUserSelectButton(
- user = option,
- isSelect = selectedOption == option,
- onClick = {
- println("Option clicked: $option")
- onOptionClick(option)
- },
- modifier = Modifier.weight(1f)
- )
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpLocationInfoScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpLocationInfoScreen.kt
new file mode 100644
index 00000000..7a642951
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpLocationInfoScreen.kt
@@ -0,0 +1,88 @@
+package com.paw.key.presentation.ui.signup
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.unit.dp
+import com.paw.key.R
+import com.paw.key.core.designsystem.component.PawKeyBottomSheet
+import com.paw.key.core.extension.noRippleClickable
+import com.paw.key.presentation.ui.signup.component.FormField
+import com.paw.key.presentation.ui.signup.component.RegionSearchContent
+import com.paw.key.presentation.ui.signup.component.SignUpTextField
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignUpLocationInfoScreen(
+ gu : String,
+ dong : String,
+ onSelectedLocation : (gu : String, dong : String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val scope = rememberCoroutineScope()
+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ var isSheetOpen by remember { mutableStateOf(false) }
+
+ Column (
+ modifier = modifier
+ .padding(
+ top = 40.dp,
+ start = 16.dp,
+ end = 16.dp
+ )
+ ) {
+ FormField(
+ label = "선택 지역",
+ content = {
+ SignUpTextField(
+ value = if (gu.isNotEmpty() && dong.isNotEmpty()) "$gu $dong" else "",
+ onValueChange = {},
+ enabled = false,
+ placeholder = "주로 산책하는 지역을 검색해보세요",
+ suffix = {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_signup_search),
+ contentDescription = "breed search",
+ tint = Color.Unspecified
+ )
+ },
+ modifier = Modifier
+ .noRippleClickable {
+ scope.launch {
+ isSheetOpen = true
+ }
+ }
+ )
+ }
+ )
+ }
+
+ if (isSheetOpen) {
+ PawKeyBottomSheet(
+ sheetState = sheetState,
+ onDismissRequest = { isSheetOpen = false },
+ ) {
+ RegionSearchContent(
+ selectedGu = gu,
+ selectedDong = dong,
+ onRegionSelected = { gu, dong ->
+ isSheetOpen = false
+ onSelectedLocation(gu, dong)
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpMapInfoScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpMapInfoScreen.kt
new file mode 100644
index 00000000..77fa811f
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpMapInfoScreen.kt
@@ -0,0 +1,161 @@
+package com.paw.key.presentation.ui.signup
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+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.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.naver.maps.geometry.LatLng
+import com.naver.maps.geometry.LatLngBounds
+import com.naver.maps.map.CameraUpdate
+import com.naver.maps.map.compose.ExperimentalNaverMapApi
+import com.naver.maps.map.compose.NaverMap
+import com.naver.maps.map.compose.PolygonOverlay
+import com.naver.maps.map.compose.rememberCameraPositionState
+import com.paw.key.core.designsystem.component.PawkeyButton
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+import com.paw.key.presentation.ui.region.state.DrawType
+import kotlinx.collections.immutable.ImmutableList
+
+
+@OptIn(ExperimentalNaverMapApi::class)
+@Composable
+fun SignUpMapInfoScreen(
+ type: DrawType,
+ regionCoordinates: ImmutableList>,
+ entireCoordinates: ImmutableList,
+ regionName: String,
+ onClickButton: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val cameraPositionState = rememberCameraPositionState()
+ val bottomPanelHeightPx = remember {
+ mutableIntStateOf(0)
+ }
+
+ val density = LocalDensity.current
+
+ LaunchedEffect(entireCoordinates.size) {
+ if (entireCoordinates.size >= 2 && bottomPanelHeightPx.intValue > 0) {
+ val bounds = LatLngBounds.from(entireCoordinates)
+
+ val bottomPadding =
+ bottomPanelHeightPx.intValue + with(density) { 100.dp.roundToPx() }
+
+ cameraPositionState.move(
+ CameraUpdate.fitBounds(bounds, 100, 100, 100, bottomPadding)
+ )
+ }
+ }
+
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ ) {
+ NaverMap(
+ modifier = Modifier
+ .fillMaxSize()
+ .align(Alignment.Center),
+ cameraPositionState = cameraPositionState
+ ) {
+ when (type) {
+ DrawType.SINGLE -> {
+ val singlePolygonCoords = regionCoordinates.first()
+ if (singlePolygonCoords.isNotEmpty()) {
+ PolygonOverlay(
+ coords = singlePolygonCoords,
+ color = PawKeyTheme.colors.opacityPrimary.copy(alpha = 0.3f),
+ outlineWidth = 1.dp,
+ outlineColor = PawKeyTheme.colors.green500
+ )
+ }
+ }
+
+ DrawType.MULTIPLE -> {
+ regionCoordinates.forEach {
+ PolygonOverlay(
+ coords = it,
+ color = PawKeyTheme.colors.opacityPrimary.copy(alpha = 0.3f),
+ outlineWidth = 1.dp,
+ outlineColor = PawKeyTheme.colors.green500
+ )
+ }
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.BottomCenter)
+ .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp))
+ .background(
+ color = PawKeyTheme.colors.white1,
+ shape = RoundedCornerShape(
+ topStart = 16.dp, topEnd = 16.dp
+ )
+ )
+ .padding(horizontal = 16.dp, vertical = 24.dp)
+ .onSizeChanged { size ->
+ bottomPanelHeightPx.intValue = size.height
+ }
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp, bottom = 16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "선택한 위치",
+ style = PawKeyTheme.typography.header3,
+ color = PawKeyTheme.colors.contents
+ )
+
+ Text(
+ text = regionName,
+ style = PawKeyTheme.typography.header3,
+ color = PawKeyTheme.colors.primary
+ )
+ }
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Text(
+ text = "선택한 산책 지역은 ${regionName}이에요.\n이 위치로 산책 지역을 설정하시겠어요?",
+ style = PawKeyTheme.typography.bodyDefault,
+ color = PawKeyTheme.colors.gray500,
+ modifier = Modifier
+ .padding(bottom = 12.dp)
+ )
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ PawkeyButton(
+ text = "선택",
+ onClick = onClickButton,
+ modifier = Modifier
+ .fillMaxWidth(),
+ enabled = true,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpPetInfoScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpPetInfoScreen.kt
new file mode 100644
index 00000000..71f44d03
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpPetInfoScreen.kt
@@ -0,0 +1,247 @@
+package com.paw.key.presentation.ui.signup
+
+import android.Manifest
+import android.net.Uri
+import android.os.Build
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import com.paw.key.R
+import com.paw.key.core.designsystem.component.PawKeyBottomSheet
+import com.paw.key.core.extension.noRippleClickable
+import com.paw.key.core.util.DateVisualTransformation
+import com.paw.key.presentation.ui.signup.component.FormField
+import com.paw.key.presentation.ui.signup.component.GenderSelector
+import com.paw.key.presentation.ui.signup.component.PetBreedSearchContent
+import com.paw.key.presentation.ui.signup.component.SignUpNeuteringCheckRadio
+import com.paw.key.presentation.ui.signup.component.SignUpPetImageHolder
+import com.paw.key.presentation.ui.signup.component.SignUpTextField
+import com.paw.key.presentation.ui.signup.state.Gender
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignUpPetInfoScreen(
+ petName : String,
+ petBirthDate : String,
+ petGender : Gender,
+ petNeutered : Boolean,
+ petBreed : String,
+ selectedImageUri: Uri?,
+ deniedPermission: () -> Unit,
+ onPetNameChanged : (String) -> Unit,
+ onPetBirthDateChanged : (String) -> Unit,
+ onPetGenderChanged : (Gender) -> Unit,
+ onPetNeuteredChanged : (Boolean) -> Unit,
+ onPetBreedChanged : (String) -> Unit,
+ onSelectedImage: (Uri?) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ var isSheetOpen by remember { mutableStateOf(false) }
+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ val scope = rememberCoroutineScope()
+
+ val petBirthDateFocusRequester = remember { FocusRequester() }
+ val focusManager = LocalFocusManager.current
+
+ val photoPickerLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.PickVisualMedia(),
+ onResult = { uri ->
+ onSelectedImage(uri)
+ }
+ )
+
+ // 구버전 권한 요청용
+ val legacyGalleryLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent(),
+ onResult = { uri ->
+ onSelectedImage(uri)
+ }
+ )
+
+ val permissionLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestPermission(),
+ onResult = { isGranted ->
+ if (isGranted) {
+ legacyGalleryLauncher.launch("image/*")
+ } else {
+ deniedPermission
+ }
+ }
+ )
+
+ Column(
+ modifier = modifier
+ .padding(
+ top = 40.dp,
+ start = 16.dp,
+ end = 16.dp
+ )
+ ) {
+ SignUpPetImageHolder(
+ uri = selectedImageUri,
+ modifier = Modifier
+ .noRippleClickable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ photoPickerLauncher.launch(
+ PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
+ )
+ } else {
+ permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
+ }
+ }
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ FormField(
+ label = "이름",
+ content = {
+ SignUpTextField(
+ value = petName,
+ onValueChange = {
+ if (it.length <= 8) {
+ onPetNameChanged(it)
+ }
+ },
+ placeholder = "최대 8글자 이내로 입력해주세요",
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ ),
+ keyboardActions = KeyboardActions(
+ onDone = {
+ petBirthDateFocusRequester.requestFocus()
+ }
+ ),
+ )
+ }
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ FormField(
+ label = "생년월일",
+ content = {
+ SignUpTextField(
+ modifier = Modifier
+ .focusRequester(petBirthDateFocusRequester),
+ value = petBirthDate,
+ onValueChange = {
+ if (it.length <= 8) {
+ onPetBirthDateChanged(it)
+ }
+ },
+ placeholder = "YYYYMMDD",
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ imeAction = ImeAction.Done
+ ),
+ keyboardActions = KeyboardActions(
+ onDone = {
+ focusManager.clearFocus()
+ }
+ ),
+ visualTransformation = DateVisualTransformation()
+ )
+ }
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ FormField(
+ label = "성별",
+ content = {
+ GenderSelector(
+ selectedGender = petGender,
+ onGenderSelected = onPetGenderChanged,
+ type = "반려 동물"
+ )
+ }
+ )
+
+ SignUpNeuteringCheckRadio(
+ isNeutered = petNeutered,
+ onToggle = { onPetNeuteredChanged(!petNeutered) },
+ modifier = Modifier
+ .padding(top = 8.dp)
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ FormField(
+ label = "견종",
+ content = {
+ SignUpTextField(
+ value = petBreed,
+ onValueChange = onPetBreedChanged,
+ enabled = false,
+ placeholder = "견종을 검색해보세요",
+ suffix = {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_signup_search),
+ contentDescription = "breed search",
+ tint = Color.Unspecified
+ )
+ },
+ modifier = Modifier
+ .noRippleClickable {
+ scope.launch {
+ isSheetOpen = true
+ }
+ }
+ )
+ }
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ if (isSheetOpen) {
+ PawKeyBottomSheet(
+ onDismissRequest = { isSheetOpen = false },
+ sheetState = sheetState,
+ //sheetGesturesEnabled = false,
+ ) { sheetState ->
+ PetBreedSearchContent(
+ sheetState = sheetState,
+ selectedBreed = petBreed,
+ onBreedSelected = {
+ onPetBreedChanged(it)
+ scope.launch {
+
+ sheetState.hide()
+ }.invokeOnCompletion {
+ if (!sheetState.isVisible) {
+ isSheetOpen = false
+ }
+ }
+ },
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpScreen.kt
index e63e76b3..70ffad7a 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpScreen.kt
@@ -1,219 +1,257 @@
package com.paw.key.presentation.ui.signup
-import androidx.compose.foundation.layout.Arrangement
+import android.net.Uri
+import androidx.activity.compose.BackHandler
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.imePadding
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
-import com.paw.key.R
-import com.paw.key.core.designsystem.component.PawkeyButton
-import com.paw.key.core.designsystem.theme.PawKeyTheme
-import com.paw.key.presentation.ui.signup.component.FormField
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.flowWithLifecycle
+import com.paw.key.core.designsystem.component.DogkyButton
+import com.paw.key.core.designsystem.component.LoadingScreen
+import com.paw.key.core.util.UiState
import com.paw.key.presentation.ui.signup.component.SignUpHeader
-import com.paw.key.presentation.ui.signup.component.SignUpTextField
-import com.paw.key.presentation.ui.signup.component.SignUpUserSelectButton
-import com.paw.key.presentation.ui.signup.state.SignUpContract
+import com.paw.key.presentation.ui.signup.component.SignUpSubHeader
+import com.paw.key.presentation.ui.signup.model.SignUpLocationInfo
+import com.paw.key.presentation.ui.signup.model.SignUpMapInfo
+import com.paw.key.presentation.ui.signup.model.SignUpPetInfo
+import com.paw.key.presentation.ui.signup.model.SignUpUserInfo
+import com.paw.key.presentation.ui.signup.state.Gender
+import com.paw.key.presentation.ui.signup.state.SignUpSideEffect
+import com.paw.key.presentation.ui.signup.state.SignUpStateType
import com.paw.key.presentation.ui.signup.viewmodel.SignUpViewModel
-@Preview(showBackground = true)
@Composable
-private fun PreviewSignUpScreen() {
- PawKeyTheme {
- SignUpScreen(
- step = 0.25F,
- navigateSignUpActivity = {},
- viewModel = hiltViewModel()
- )
+fun SignUpRoute(
+ navigateUp: () -> Unit,
+ navigateToHome: () -> Unit,
+ viewModel: SignUpViewModel = hiltViewModel()
+) {
+ val state by viewModel.state.collectAsStateWithLifecycle()
+ val lifecycleOwner = LocalLifecycleOwner.current
+
+ BackHandler(enabled = true) {
+ viewModel.onBackPressed()
}
-}
-@Composable
-fun SignUpRoute(
- navigateSignUpActivity: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: SignUpViewModel? = null
- ) {
- val actualViewModel = viewModel ?: hiltViewModel()
+ LaunchedEffect(Unit) {
+ viewModel.sideEffect.flowWithLifecycle(lifecycleOwner.lifecycle)
+ .collect { it ->
+ when (it) {
+ is SignUpSideEffect.NavigateUp -> {
+ navigateUp()
+ }
+ is SignUpSideEffect.ShowSnackBar -> {
+
+ }
+ is SignUpSideEffect.NavigateNext -> {
+ viewModel.updateStep()
+ }
+ is SignUpSideEffect.NavigateHome -> {
+ navigateToHome()
+ }
+ }
+ }
+ }
SignUpScreen(
- step = 0.25F,
- navigateSignUpActivity = navigateSignUpActivity,
- modifier = modifier,
- viewModel = actualViewModel
+ currentStep = state.currentStep,
+ type = state.signUpState,
+ isNextEnabled = state.isNextEnabled,
+
+ onNextClick = viewModel::onNextClick,
+ onBackClick = viewModel::onBackPressed,
+
+ userInfo = state.userInfo,
+ onNickNameChanged = { viewModel.updateNickname(it) },
+ onBirthDateChanged = { viewModel.updateBirthDate(it) },
+ onGenderChanged = viewModel::updateGender,
+
+ petInfo = state.petInfo,
+ onPetNameChanged = { viewModel.updatePetName(it) },
+ onPetBirthDateChanged = { viewModel.updatePetBirthDate(it) },
+ onPetGenderChanged = viewModel::updatePetGender,
+ onPetNeuteredChanged = viewModel::updatePetNeutered,
+ onPetBreedChanged = { viewModel.updatePetBreed(it) },
+ deniedPermission = viewModel::deniedPermission,
+ onSelectedImage = viewModel::updatePetImage,
+
+ locationInfo = state.locationInfo,
+ onSelectedLocation = { gu, dong ->
+ viewModel.onRegionSelected(gu, dong)
+ },
+
+ mapInfo = state.mapInfo,
)
}
@Composable
fun SignUpScreen(
- step: Float,
- navigateSignUpActivity: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: SignUpViewModel
+ userInfo : SignUpUserInfo,
+ onNickNameChanged: (String) -> Unit,
+ onBirthDateChanged: (String) -> Unit,
+ onGenderChanged: (Gender) -> Unit,
+
+ petInfo : SignUpPetInfo,
+ deniedPermission: () -> Unit,
+ onPetNameChanged : (String) -> Unit,
+ onPetBirthDateChanged : (String) -> Unit,
+ onPetGenderChanged : (Gender) -> Unit,
+ onPetNeuteredChanged : (Boolean) -> Unit,
+ onPetBreedChanged : (String) -> Unit,
+ onSelectedImage: (Uri?) -> Unit,
+
+ locationInfo : SignUpLocationInfo,
+ onSelectedLocation : (gu : String, dong : String) -> Unit,
+
+ mapInfo: SignUpMapInfo,
+
+ isNextEnabled: Boolean,
+ onNextClick: () -> Unit,
+ onBackClick: () -> Unit,
+ currentStep : Float,
+ type: SignUpStateType,
) {
- val state by viewModel.state.collectAsState()
- val keyboardController = LocalSoftwareKeyboardController.current
- val focusManager = LocalFocusManager.current
+ val title = when(type) {
+ SignUpStateType.USER_INFO -> {
+ "내 정보 입력"
+ }
+ SignUpStateType.PET_INFO -> {
+ "반려견 정보 입력"
+ }
+ SignUpStateType.LOCATION_INFO -> {
+ "산책 지역 입력"
+ }
+ SignUpStateType.REGION_MANAGEMENT -> {
+ "산책 지역 입력"
+ }
+ }
- // FocusRequester들 생성
- val nameFocusRequester = remember { FocusRequester() }
- val ageFocusRequester = remember { FocusRequester() }
+ val buttonText = when(type) {
+ SignUpStateType.LOCATION_INFO -> {
+ "완료"
+ }
+ else -> {
+ "다음"
+ }
+ }
- LazyColumn(
- modifier = modifier
+ Column (
+ modifier = Modifier
.fillMaxSize()
- .imePadding()
) {
- item {
- SignUpHeader(
- title = stringResource(R.string.ic_onboarding_signup),
- subtitle = stringResource(id = R.string.ic_onboarding_signup_subtitle_step1),
- progress = step,
- )
- }
+ SignUpHeader(
+ title = title,
+ onBackClick = onBackClick,
+ progress = currentStep
+ )
- item {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 16.dp)
- ) {
- Spacer(modifier = Modifier.height(42.dp))
-
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_name),
- content = {
- SignUpTextField(
- value = state.name,
- onValueChange = viewModel::onNameChanged,
- placeholder = "이름을 입력해주세요",
- modifier = Modifier.focusRequester(nameFocusRequester),
- keyboardOptions = KeyboardOptions(
- imeAction = androidx.compose.ui.text.input.ImeAction.Next,
- keyboardType = KeyboardType.Text
- ),
- keyboardActions = KeyboardActions(
- onNext = {
- focusManager.clearFocus()
- }
- )
- )
- }
- )
-
- Spacer(modifier = Modifier.height(33.dp))
-
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_gender),
- content = {
- GenderSelector(
- selectedGender = state.selectedGender,
- onGenderSelected = { gender ->
- viewModel.selectGender(gender)
- // 성별 선택 후 나이 입력으로 포커싱
- ageFocusRequester.requestFocus()
- }
- )
+ when (type) {
+ SignUpStateType.REGION_MANAGEMENT -> {
+ when (val state = mapInfo.uiState) {
+ is UiState.Loading -> {
+ LoadingScreen()
}
- )
-
- Spacer(modifier = Modifier.height(33.dp))
-
- FormField(
- label = stringResource(id = R.string.ic_onboarding_signup_age),
- content = {
- SignUpTextField(
- value = state.age,
- onValueChange = viewModel::onAgeChanged,
- placeholder = "나이를 입력해주세요",
- modifier = Modifier.focusRequester(ageFocusRequester),
- keyboardOptions = KeyboardOptions(
- imeAction = androidx.compose.ui.text.input.ImeAction.Done,
- keyboardType = KeyboardType.Number
- ),
- keyboardActions = KeyboardActions(
- onDone = {
- keyboardController?.hide()
- focusManager.clearFocus()
-
- // 폼이 유효하면 다음 단계로
- val isFormValid = state.name.isNotBlank() &&
- state.age.isNotBlank() &&
- state.selectedGender != SignUpContract.Gender.UNKNOWN
-
- if (isFormValid) {
- navigateSignUpActivity()
- }
- }
- )
+
+ is UiState.Success -> {
+ SignUpMapInfoScreen(
+ type = mapInfo.drawType,
+ regionCoordinates = state.data,
+ entireCoordinates = mapInfo.entireCoordinates,
+ regionName = mapInfo.regionName,
+ onClickButton = onNextClick,
+ modifier = Modifier
)
}
- )
- Spacer(modifier = Modifier.height(60.dp))
+ else -> {}
+ }
+ }
+
+ else -> {
+ SignUpSubHeader()
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ when (type) {
+ SignUpStateType.USER_INFO -> {
+ SignUpUserInfoScreen(
+ nickName = userInfo.nickName,
+ birthDate = userInfo.birthDate,
+ gender = userInfo.gender,
+ onNickNameChanged = onNickNameChanged,
+ onBirthDateChanged = onBirthDateChanged,
+ onGenderChanged = onGenderChanged,
+ modifier = Modifier
+ .padding(top = 40.dp)
+ )
+ }
- val isFormValid = state.name.isNotBlank() &&
- state.age.isNotBlank() &&
- state.selectedGender != SignUpContract.Gender.UNKNOWN
+ SignUpStateType.PET_INFO -> {
+ SignUpPetInfoScreen(
+ petName = petInfo.petName,
+ petBirthDate = petInfo.petBirthDate,
+ petGender = petInfo.petGender,
+ petNeutered = petInfo.petNeutered,
+ petBreed = petInfo.petBreed,
+ selectedImageUri = petInfo.petImage,
+ onPetNameChanged = onPetNameChanged,
+ onPetBirthDateChanged = onPetBirthDateChanged,
+ onPetGenderChanged = onPetGenderChanged,
+ onPetNeuteredChanged = onPetNeuteredChanged,
+ onPetBreedChanged = onPetBreedChanged,
+ deniedPermission = deniedPermission,
+ onSelectedImage = {
+ onSelectedImage(it)
+ },
+ modifier = Modifier
+ )
+ }
- PawkeyButton(
- text = stringResource(id = R.string.ic_onboarding_signup_button),
- enabled = isFormValid,
- onClick = {
- if (isFormValid) {
- keyboardController?.hide()
- navigateSignUpActivity()
+ SignUpStateType.LOCATION_INFO -> {
+ SignUpLocationInfoScreen(
+ gu = locationInfo.selectedGu,
+ dong = locationInfo.selectedDong,
+ onSelectedLocation = { gu, dong ->
+ onSelectedLocation(gu, dong)
+ },
+ modifier = Modifier
+ )
}
+
+ else -> {}
}
- )
- Spacer(modifier = Modifier.height(46.dp))
+ Spacer(modifier = Modifier.weight(1f))
+
+ DogkyButton(
+ text = buttonText,
+ onClick = onNextClick,
+ enabled = isNextEnabled,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = 32.dp
+ )
+ )
+ }
}
}
}
-}
-
-@Composable
-private fun GenderSelector(
- selectedGender: SignUpContract.Gender,
- onGenderSelected: (SignUpContract.Gender) -> Unit,
-) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(10.dp),
- modifier = Modifier.fillMaxWidth()
- ) {
- SignUpUserSelectButton(
- user = "남성",
- isSelect = selectedGender == SignUpContract.Gender.MALE,
- onClick = { onGenderSelected(SignUpContract.Gender.MALE) },
- modifier = Modifier.weight(1f)
- )
-
- SignUpUserSelectButton(
- user = "여성",
- isSelect = selectedGender == SignUpContract.Gender.FEMALE,
- onClick = { onGenderSelected(SignUpContract.Gender.FEMALE) },
- modifier = Modifier.weight(1f)
- )
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpUserInfoScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpUserInfoScreen.kt
new file mode 100644
index 00000000..ca726a70
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/SignUpUserInfoScreen.kt
@@ -0,0 +1,105 @@
+package com.paw.key.presentation.ui.signup
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.dp
+import com.paw.key.core.util.DateVisualTransformation
+import com.paw.key.presentation.ui.signup.component.FormField
+import com.paw.key.presentation.ui.signup.component.GenderSelector
+import com.paw.key.presentation.ui.signup.component.SignUpTextField
+import com.paw.key.presentation.ui.signup.state.Gender
+
+@Composable
+fun SignUpUserInfoScreen(
+ nickName: String,
+ birthDate: String,
+ gender: Gender,
+ onNickNameChanged: (String) -> Unit,
+ onBirthDateChanged: (String) -> Unit,
+ onGenderChanged: (Gender) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val birthDateFocusRequester = remember { FocusRequester() }
+ val focusManager = LocalFocusManager.current
+
+ Column (
+ modifier = modifier
+ .padding(16.dp)
+ ) {
+ FormField(
+ label = "닉네임",
+ content = {
+ SignUpTextField(
+ value = nickName,
+ onValueChange = {
+ if (it.length <= 8) {
+ onNickNameChanged(it)
+ }
+ },
+ placeholder = "최대 8글자 이내로 입력해주세요",
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ ),
+ keyboardActions = KeyboardActions(
+ onNext = {
+ birthDateFocusRequester.requestFocus()
+ }
+ )
+ )
+ }
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ FormField(
+ label = "생년월일",
+ content = {
+ SignUpTextField(
+ modifier = Modifier
+ .focusRequester(birthDateFocusRequester),
+ value = birthDate,
+ onValueChange = {
+ if (it.length <= 8) {
+ onBirthDateChanged(it)
+ }
+ },
+ placeholder = "YYYYMMDD",
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ imeAction = ImeAction.Done
+ ),
+ keyboardActions = KeyboardActions(
+ onDone = {
+ focusManager.clearFocus()
+ }
+ ),
+ visualTransformation = DateVisualTransformation()
+ )
+ }
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ FormField(
+ label = "성별",
+ content = {
+ GenderSelector(
+ selectedGender = gender,
+ onGenderSelected = onGenderChanged
+ )
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/GenderSelector.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/GenderSelector.kt
new file mode 100644
index 00000000..a17d497f
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/GenderSelector.kt
@@ -0,0 +1,66 @@
+package com.paw.key.presentation.ui.signup.component
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+import com.paw.key.presentation.ui.signup.state.Gender
+
+@Composable
+fun GenderSelector(
+ selectedGender: Gender,
+ onGenderSelected: (Gender) -> Unit,
+ modifier: Modifier = Modifier,
+ type : String? = "유저",
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = modifier.fillMaxWidth()
+ ) {
+ if (type == "유저") {
+ SignUpUserSelectButton(
+ user = "남성",
+ isSelect = selectedGender == Gender.MALE,
+ onClick = { onGenderSelected(Gender.MALE) },
+ modifier = Modifier.weight(1f)
+ )
+
+ SignUpUserSelectButton(
+ user = "여성",
+ isSelect = selectedGender == Gender.FEMALE,
+ onClick = { onGenderSelected(Gender.FEMALE) },
+ modifier = Modifier.weight(1f)
+ )
+ } else {
+ SignUpUserSelectButton(
+ user = "남아",
+ isSelect = selectedGender == Gender.MALE,
+ onClick = { onGenderSelected(Gender.MALE) },
+ modifier = Modifier.weight(1f)
+ )
+
+ SignUpUserSelectButton(
+ user = "여아",
+ isSelect = selectedGender == Gender.FEMALE,
+ onClick = { onGenderSelected(Gender.FEMALE) },
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun GenderSelectorPreview() {
+ PawKeyTheme {
+ GenderSelector(
+ selectedGender = Gender.MALE,
+ onGenderSelected = {},
+ type = "성별"
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/NeuteringCheckbox.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/NeuteringCheckbox.kt
new file mode 100644
index 00000000..c4823b22
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/NeuteringCheckbox.kt
@@ -0,0 +1,56 @@
+package com.paw.key.presentation.ui.signup.component
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.paw.key.R
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+import com.paw.key.core.extension.noRippleClickable
+
+@Composable
+fun SignUpNeuteringCheckRadio(
+ isNeutered: Boolean,
+ onToggle: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = modifier
+ .fillMaxWidth()
+ .noRippleClickable(onToggle)
+ ) {
+ Icon(
+ imageVector = if (isNeutered)
+ ImageVector.vectorResource(R.drawable.ic_roundcheck_valid)
+ else
+ ImageVector.vectorResource(R.drawable.ic_roundcheck_invalid),
+ contentDescription = "neutering check",
+ tint = Color.Unspecified
+ )
+ Text(
+ text = "중성화 했어요",
+ color = PawKeyTheme.colors.default,
+ style = PawKeyTheme.typography.bodySmall
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun NeuteringCheckRadioPreview() {
+ PawKeyTheme {
+ SignUpNeuteringCheckRadio(
+ isNeutered = true,
+ onToggle = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/PetBreedSearchContent.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/PetBreedSearchContent.kt
new file mode 100644
index 00000000..123faac9
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/PetBreedSearchContent.kt
@@ -0,0 +1,196 @@
+package com.paw.key.presentation.ui.signup.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.SheetState
+import androidx.compose.material3.SheetValue
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.paw.key.R
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+import com.paw.key.core.extension.noRippleClickable
+import kotlinx.coroutines.launch
+
+private val dummyPetBreeds = listOf(
+ "닥스훈트", "달마시안", "말라뮤트", "말티즈", "믹스견",
+ "미니핀", "보스턴 테리어", "불독", "비글", "비숑 프리제",
+ "사모예드", "시바견", "시츄", "요크셔 테리어", "진돗개",
+ "치와와", "포메라니안", "푸들"
+)
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PetBreedSearchContent(
+ sheetState: SheetState,
+ selectedBreed : String,
+ onBreedSelected : (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ // 바텀 시트용
+ var bottomPetBreed by remember {
+ mutableStateOf("")
+ }
+
+ val scope = rememberCoroutineScope()
+
+ Column (
+ modifier = modifier
+ .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
+ .fillMaxWidth()
+ .fillMaxHeight(0.7f)
+ .background(
+ color = PawKeyTheme.colors.background2,
+ shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
+ )
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = "견종 검색",
+ style = PawKeyTheme.typography.subTitle,
+ color = PawKeyTheme.colors.contents,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ top = 24.dp,
+ bottom = 24.dp
+ ),
+ textAlign = TextAlign.Center
+ )
+
+ SignUpTextField(
+ value = bottomPetBreed,
+ onValueChange = {
+ bottomPetBreed = it
+ },
+ placeholder = "견종을 검색해보세요",
+ suffix = {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_signup_search),
+ contentDescription = "breed search",
+ tint = PawKeyTheme.colors.contents
+ )
+ },
+ modifier = Modifier.onFocusChanged { focusState ->
+ if (focusState.isFocused && sheetState.currentValue != SheetValue.Expanded) {
+ scope.launch {
+ sheetState.expand()
+ }
+ }
+ }
+ )
+
+ PetBreedSearchList(
+ breedList = dummyPetBreeds,
+ petBreed = bottomPetBreed,
+ selectedBreed = selectedBreed,
+ onBreedSelected = onBreedSelected,
+ modifier = Modifier
+ .weight(1f)
+ )
+ }
+}
+
+@Composable
+fun PetBreedSearchList(
+ breedList : List,
+ petBreed : String,
+ selectedBreed : String,
+ onBreedSelected : (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val filteredList = if (petBreed.isBlank()) {
+ breedList
+ } else {
+ breedList.filter { it.contains(petBreed, ignoreCase = true) }
+ }
+
+ LazyColumn(
+ modifier = modifier
+ .padding(top = 8.dp)
+ ) {
+ itemsIndexed(
+ items = filteredList
+ ) { index, item ->
+ PetBreedSearchItem(
+ petBreed = item,
+ onBreedSelected = onBreedSelected,
+ isPetBreedSelected = petBreed == item || selectedBreed == item,
+ )
+ }
+ }
+}
+
+@Composable
+fun PetBreedSearchItem(
+ petBreed : String,
+ onBreedSelected : (String) -> Unit,
+ isPetBreedSelected : Boolean,
+ modifier: Modifier = Modifier
+) {
+ val textColor = if (isPetBreedSelected) {
+ PawKeyTheme.colors.background2
+ } else {
+ PawKeyTheme.colors.contents
+ }
+
+ val backgroundColor = if (isPetBreedSelected) {
+ PawKeyTheme.colors.primary
+ } else {
+ PawKeyTheme.colors.background2
+ }
+
+ Text(
+ text = petBreed,
+ style = PawKeyTheme.typography.bodyActive,
+ color = textColor,
+ modifier = modifier
+ .fillMaxWidth()
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(
+ horizontal = 16.dp,
+ vertical = 11.dp
+ )
+ .noRippleClickable {
+ onBreedSelected(petBreed)
+ },
+ textAlign = TextAlign.Start
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview(showBackground = true)
+@Composable
+private fun PetPetBreedSearchContentPreview() {
+ PawKeyTheme {
+ PetBreedSearchContent(
+ selectedBreed = "",
+ onBreedSelected = {},
+ sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/RegionSearchContent.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/RegionSearchContent.kt
new file mode 100644
index 00000000..3f5d44b8
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/RegionSearchContent.kt
@@ -0,0 +1,278 @@
+package com.paw.key.presentation.ui.signup.component
+
+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.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.paw.key.R
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+import com.paw.key.core.extension.noRippleClickable
+
+private val dummySeoulRegions = listOf(
+ Pair(
+ "강남구", listOf(
+ "개포동", "논현동", "대치동", "도곡동", "삼성동", "세곡동", "수서동",
+ "신사동", "압구정동", "역삼동", "율현동", "일원동", "자곡동", "청담동"
+ )
+ ),
+ Pair(
+ "구로구", listOf(
+ "가리봉동", "개봉동", "고척동", "구로동", "궁동", "신도림동", "오류동",
+ "온수동", "천왕동", "항동"
+ )
+ ),
+ Pair(
+ "금천구", listOf(
+ "가산동", "독산동", "시흥동"
+ )
+ ),
+ Pair(
+ "노원구", listOf(
+ "공릉동", "상계동", "월계동", "중계동", "하계동"
+ )
+ ),
+ Pair(
+ "도봉구", listOf(
+ "도봉동", "방학동", "쌍문동", "창동"
+ )
+ ),
+ Pair(
+ "동대문구", listOf(
+ "답십리동", "신설동", "용두동", "이문동", "장안동", "전농동", "제기동",
+ "청량리동", "회기동", "휘경동"
+ )
+ ),
+ Pair(
+ "동작구", listOf(
+ "노량진동", "대방동", "동작동", "본동", "사당동", "상도1동", "상도동",
+ "신대방동", "흑석동"
+ )
+ )
+)
+
+@Composable
+fun RegionSearchContent(
+ selectedGu: String,
+ selectedDong: String,
+ onRegionSelected: (gu: String, dong: String) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ var searchQuery by remember { mutableStateOf("") }
+
+ var selectedGu by remember {
+ mutableStateOf(selectedGu)
+ }
+
+ var selectedDong by remember {
+ mutableStateOf(selectedDong)
+ }
+
+ val filteredRegions = remember(searchQuery) {
+ if (searchQuery.isBlank()) {
+ dummySeoulRegions
+ } else {
+ dummySeoulRegions.mapNotNull { (gu, dongList) ->
+ val matchingDongs = dongList.filter { dong ->
+ "$gu $dong".contains(searchQuery, ignoreCase = true)
+ }
+
+ if (matchingDongs.isNotEmpty()) {
+ Pair(gu, matchingDongs)
+ } else {
+ null
+ }
+ }
+ }
+ }
+
+ val dongListForSelectedGu = remember(selectedGu, filteredRegions) {
+ filteredRegions.find { it.first == selectedGu }?.second ?: emptyList()
+ }
+
+ Column(
+ modifier = modifier
+ .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
+ .fillMaxWidth()
+ .fillMaxHeight(0.7f)
+ .background(
+ color = PawKeyTheme.colors.background2,
+ shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
+ )
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = "산책 지역",
+ style = PawKeyTheme.typography.subTitle,
+ color = PawKeyTheme.colors.contents,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ top = 24.dp,
+ bottom = 24.dp
+ ),
+ textAlign = TextAlign.Center
+ )
+
+ SignUpTextField(
+ value = searchQuery,
+ onValueChange = {
+ searchQuery = it
+ },
+ placeholder = "지역을 검색해보세요",
+ suffix = {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_signup_search),
+ contentDescription = "region search",
+ tint = PawKeyTheme.colors.contents
+ )
+ }
+ )
+
+ RegionSearchList(
+ guList = filteredRegions.map { it.first },
+ dongList = dongListForSelectedGu,
+ selectedGu = selectedGu,
+ selectedDong = selectedDong,
+ onGuSelected = { gu ->
+ selectedGu = gu
+ selectedDong = ""
+ },
+ onDongSelected = { dong ->
+ selectedDong = dong
+ onRegionSelected(selectedGu, selectedDong)
+ },
+ modifier = Modifier
+ .weight(1f)
+ )
+ }
+}
+
+@Composable
+private fun RegionSearchList(
+ guList: List,
+ dongList: List,
+ selectedGu: String,
+ selectedDong: String,
+ onGuSelected: (String) -> Unit,
+ onDongSelected: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .padding(vertical = 8.dp),
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .weight(0.45f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ items(guList) { gu ->
+ RegionItem(
+ name = gu,
+ isSelected = gu == selectedGu,
+ onClick = {
+ onGuSelected(gu)
+ }
+ )
+ }
+ }
+
+ VerticalDivider(
+ thickness = 1.dp,
+ color = PawKeyTheme.colors.default,
+ modifier = Modifier
+ .fillMaxHeight()
+ .background(
+ color = PawKeyTheme.colors.default,
+ shape = RoundedCornerShape(8.dp)
+ )
+ )
+
+ LazyColumn(
+ modifier = Modifier.weight(1f),
+ horizontalAlignment = Alignment.Start
+ ) {
+ items(dongList) { dong ->
+ RegionItem(
+ name = dong,
+ isSelected = dong == selectedDong,
+ onClick = {
+ onDongSelected(dong)
+ },
+ textAlign = TextAlign.Start
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun RegionItem(
+ name: String,
+ isSelected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ textAlign: TextAlign = TextAlign.Center
+) {
+ val textColor = if (isSelected) PawKeyTheme.colors.background2 else PawKeyTheme.colors.contents
+
+ val backgroundColor =
+ if (isSelected) PawKeyTheme.colors.primary else PawKeyTheme.colors.background2
+
+ Text(
+ text = name,
+ style = PawKeyTheme.typography.bodyActive,
+ color = textColor,
+ modifier = modifier
+ .fillMaxWidth()
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(
+ horizontal = 16.dp,
+ vertical = 11.dp
+ )
+ .noRippleClickable {
+ onClick()
+ },
+ textAlign = textAlign
+ )
+}
+
+
+@Preview
+@Composable
+private fun RegionSearchContentPreview() {
+ PawKeyTheme {
+ RegionSearchContent(
+ selectedGu = "",
+ selectedDong = "",
+ onRegionSelected = { _, _ -> }
+ )
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpHeader.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpHeader.kt
index f2a4c317..bfcb6abd 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpHeader.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpHeader.kt
@@ -3,14 +3,12 @@ package com.paw.key.presentation.ui.signup.component
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
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.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -18,9 +16,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import com.paw.key.R
import com.paw.key.core.designsystem.theme.PawKeyTheme
+import com.paw.key.core.extension.noRippleClickable
@Preview(showBackground = true)
@Composable
@@ -29,7 +32,7 @@ private fun PreviewSignUpHeader() {
SignUpHeader(
progress = 0.5F,
title = "회원가입",
- subtitle = "견주님에 대해 알려주세요."
+ onBackClick = {}
)
}
}
@@ -37,13 +40,14 @@ private fun PreviewSignUpHeader() {
@Composable
fun SignUpHeader(
title: String,
- subtitle: String,
+ onBackClick: () -> Unit,
modifier: Modifier = Modifier,
- progress: Float = 1F,
+ progress: Float = 1f,
) {
+ val stepProgress = (progress / 3f).coerceIn(0f, 1f)
val animatedProgress by animateFloatAsState(
- targetValue = progress,
+ targetValue = stepProgress,
animationSpec = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
@@ -51,54 +55,43 @@ fun SignUpHeader(
label = "progress_animation"
)
- Column(
+ Column (
modifier = modifier
.fillMaxWidth()
- .height(131.dp)
- ) {
- Row(
- verticalAlignment = Alignment.Bottom,
- modifier = modifier
+ ){
+ Box (
+ modifier = Modifier
.fillMaxWidth()
- .height(60.dp)
+ .padding(vertical = 16.dp),
+ contentAlignment = Alignment.Center
) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center,
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_left_black),
+ contentDescription = "back",
+ tint = PawKeyTheme.colors.contents,
modifier = Modifier
- .weight(1F)
- .fillMaxSize()
- ) {
- Text(
- text = title,
- color = PawKeyTheme.colors.black,
- style = PawKeyTheme.typography.body16Sb,
- modifier = Modifier
- .padding(top = 16.dp),
- )
-
- Spacer(modifier = Modifier.weight(1F))
+ .align(Alignment.CenterStart)
+ .padding(start = 16.dp)
+ .noRippleClickable(onBackClick)
+ )
- LinearProgressIndicator(
- progress = { animatedProgress },
- modifier = Modifier
- .fillMaxWidth()
- .height(2.dp),
- color = PawKeyTheme.colors.green500,
- trackColor = PawKeyTheme.colors.gray100,
- strokeCap = StrokeCap.Square,
- gapSize = 0.dp,
- drawStopIndicator = {}
- )
- }
+ Text(
+ text = title,
+ color = PawKeyTheme.colors.contents,
+ style = PawKeyTheme.typography.subTitle,
+ textAlign = TextAlign.Center
+ )
}
- Text(
- text = subtitle,
- color = PawKeyTheme.colors.black,
- style = PawKeyTheme.typography.head22Sb,
+
+ LinearProgressIndicator(
+ progress = { animatedProgress },
modifier = Modifier
- .padding(top = 36.dp)
- .padding(horizontal = 16.dp)
+ .fillMaxWidth()
+ .height(2.dp),
+ color = PawKeyTheme.colors.primary,
+ trackColor = PawKeyTheme.colors.default,
+ strokeCap = StrokeCap.Square,
+ gapSize = 0.dp
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpPetImageHolder.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpPetImageHolder.kt
new file mode 100644
index 00000000..16c5a726
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpPetImageHolder.kt
@@ -0,0 +1,68 @@
+package com.paw.key.presentation.ui.signup.component
+
+import android.net.Uri
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import com.paw.key.R
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+
+@Composable
+fun SignUpPetImageHolder(
+ uri: Uri?,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = modifier
+ .size(94.dp)
+ ) {
+ if (uri == null) {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_signup_profile_edit),
+ contentDescription = "add pet profile",
+ tint = Color.Unspecified,
+ )
+ } else {
+ AsyncImage(
+ model = uri,
+ contentDescription = "Pet Image",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape)
+ )
+
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_signup_image_edit),
+ contentDescription = "edit icon",
+ tint = Color.Unspecified,
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SignUpPetImageHolderPreview() {
+ PawKeyTheme {
+ SignUpPetImageHolder(
+ uri = null,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpSubHeader.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpSubHeader.kt
new file mode 100644
index 00000000..06639c38
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpSubHeader.kt
@@ -0,0 +1,44 @@
+package com.paw.key.presentation.ui.signup.component
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.paw.key.core.designsystem.theme.PawKeyTheme
+
+@Composable
+fun SignUpSubHeader() {
+ Text(
+ text = "산책하기 전 \n간단한 정보를 입력해주세요",
+ color = PawKeyTheme.colors.contents,
+ style = PawKeyTheme.typography.header2,
+ modifier = Modifier
+ .padding(
+ top = 20.dp,
+ start = 16.dp,
+ end = 16.dp
+ )
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Text(
+ text = "서비스 시작을 위해 간단한 정보를 입력해주세요!",
+ color = PawKeyTheme.colors.default,
+ style = PawKeyTheme.typography.bodyDefault,
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ )
+}
+
+@Preview
+@Composable
+private fun SignUpSubHeaderPreview() {
+ PawKeyTheme {
+ SignUpSubHeader()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpTextField.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpTextField.kt
index acb1ac65..5c945cf3 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpTextField.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpTextField.kt
@@ -2,81 +2,120 @@ package com.paw.key.presentation.ui.signup.component
import androidx.compose.foundation.background
import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.paw.key.core.designsystem.theme.PawKeyTheme
@Composable
+
fun SignUpTextField(
value: String,
onValueChange: (String) -> Unit,
- modifier: Modifier = Modifier,
placeholder: String,
+ modifier: Modifier = Modifier,
enabled: Boolean = true,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
+ visualTransformation: VisualTransformation = VisualTransformation.None,
singleLine: Boolean = true,
+ suffix: @Composable (() -> Unit)? = null
) {
val isFocused = remember { mutableStateOf(false) }
val borderColor = when {
- !enabled -> PawKeyTheme.colors.gray100
- isFocused.value -> PawKeyTheme.colors.green500
- else -> PawKeyTheme.colors.gray200
+ !enabled -> PawKeyTheme.colors.default
+ isFocused.value -> PawKeyTheme.colors.primary
+ else -> PawKeyTheme.colors.default
}
- BasicTextField(
- value = value,
- onValueChange = onValueChange,
- modifier = modifier
- .fillMaxWidth()
- .height(52.dp)
- .clip(RoundedCornerShape(8.dp))
- .onFocusChanged { focusState ->
- isFocused.value = focusState.isFocused
- }
- .background(color = PawKeyTheme.colors.white1)
- .border(
- width = 1.dp,
- color = borderColor,
- shape = RoundedCornerShape(8.dp)
- ),
- textStyle = PawKeyTheme.typography.body14R,
- maxLines = if (singleLine) 1 else Int.MAX_VALUE,
- singleLine = singleLine,
- enabled = enabled,
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions,
- decorationBox = { innerTextField ->
- androidx.compose.foundation.layout.Box(
- modifier = Modifier
- .fillMaxWidth()
- .height(52.dp)
- .padding(horizontal = 16.dp),
- contentAlignment = androidx.compose.ui.Alignment.CenterStart
- ) {
- if (value.isEmpty()) {
- Text(
- text = placeholder,
- style = PawKeyTheme.typography.body14R,
- color = PawKeyTheme.colors.gray200
- )
+ // Todo : Gra로 변경
+ val customTextSelectionColors = TextSelectionColors(
+ handleColor = PawKeyTheme.colors.primary,
+ backgroundColor = PawKeyTheme.colors.primary.copy(alpha = 0.4f)
+ )
+
+ CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) {
+ BasicTextField(
+ value = value,
+ onValueChange = onValueChange,
+ visualTransformation = visualTransformation,
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(8.dp))
+ .onFocusChanged { focusState ->
+ isFocused.value = focusState.isFocused
+ }
+ .background(color = PawKeyTheme.colors.background2)
+ .border(
+ width = 1.dp,
+ color = borderColor,
+ shape = RoundedCornerShape(8.dp)
+ ),
+ textStyle = PawKeyTheme.typography.bodyActive,
+ maxLines = if (singleLine) 1 else Int.MAX_VALUE,
+ singleLine = singleLine,
+ enabled = enabled,
+ keyboardOptions = keyboardOptions,
+ keyboardActions = keyboardActions,
+ cursorBrush = SolidColor(PawKeyTheme.colors.primary),
+ decorationBox = { innerTextField ->
+ Row (
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ ) {
+ if (value.isEmpty()) {
+ Text(
+ text = placeholder,
+ style = PawKeyTheme.typography.bodyDefault,
+ color = PawKeyTheme.colors.default
+ )
+ }
+ innerTextField()
+ }
+ suffix?.invoke()
}
- innerTextField()
}
- }
- )
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SignUpTextFieldPreview() {
+ PawKeyTheme {
+ SignUpTextField(
+ value = "",
+ onValueChange = {},
+ placeholder = "이름을 입력해주세요."
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpUserSelectButton.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpUserSelectButton.kt
index 3b91fdf0..a39f92de 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpUserSelectButton.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/SignUpUserSelectButton.kt
@@ -3,15 +3,14 @@ package com.paw.key.presentation.ui.signup.component
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-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.clip
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.paw.key.core.designsystem.theme.PawKeyTheme
@@ -39,39 +38,41 @@ fun SignUpUserSelectButton(
Box(
contentAlignment = Alignment.Center,
modifier = modifier
- .height(48.dp)
- .width(158.dp)
- .background(color = PawKeyTheme.colors.white1)
- .noRippleClickable { onClick() }
- .border(
- width = if (isSelect){
- 2.dp
+ .clip(RoundedCornerShape(8.dp))
+ .background(
+ color = if (isSelect) {
+ PawKeyTheme.colors.primary
} else {
- 1.dp
- },
+ PawKeyTheme.colors.background2
+ }
+ )
+ .border(
+ width = 1.dp,
color = if (isSelect) {
- PawKeyTheme.colors.green500
+ Color.Transparent
} else {
- PawKeyTheme.colors.gray200
+ PawKeyTheme.colors.default
},
shape = RoundedCornerShape(8.dp)
)
- .clip(RoundedCornerShape(8.dp))
+ .noRippleClickable(onClick)
) {
Text(
text = user,
color = if (isSelect) {
- PawKeyTheme.colors.green500
+ PawKeyTheme.colors.background2
} else {
- PawKeyTheme.colors.gray200
+ PawKeyTheme.colors.default
},
style = if (isSelect) {
- PawKeyTheme.typography.body14Sb
+ PawKeyTheme.typography.bodyActive
} else {
- PawKeyTheme.typography.body14R
+ PawKeyTheme.typography.bodyDefault
},
modifier = Modifier
- .padding(horizontal = 21.dp, vertical = 13.dp)
+ .padding(
+ vertical = 16.dp
+ )
)
}
}
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpLocationInfo.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpLocationInfo.kt
new file mode 100644
index 00000000..1f0fa190
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpLocationInfo.kt
@@ -0,0 +1,9 @@
+package com.paw.key.presentation.ui.signup.model
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class SignUpLocationInfo( // 바텀시트 지역 선택 시 보여주기 위함
+ val selectedGu: String = "",
+ val selectedDong: String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpMapInfo.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpMapInfo.kt
new file mode 100644
index 00000000..23273063
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpMapInfo.kt
@@ -0,0 +1,16 @@
+package com.paw.key.presentation.ui.signup.model
+
+import androidx.compose.runtime.Immutable
+import com.naver.maps.geometry.LatLng
+import com.paw.key.core.util.UiState
+import com.paw.key.presentation.ui.region.state.DrawType
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+@Immutable
+data class SignUpMapInfo(
+ val uiState: UiState>> = UiState.Loading,
+ val entireCoordinates: ImmutableList = persistentListOf(),
+ val regionName: String = "",
+ val drawType: DrawType = DrawType.SINGLE
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpPetInfo.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpPetInfo.kt
new file mode 100644
index 00000000..8e81a8dd
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpPetInfo.kt
@@ -0,0 +1,15 @@
+package com.paw.key.presentation.ui.signup.model
+
+import android.net.Uri
+import androidx.compose.runtime.Immutable
+import com.paw.key.presentation.ui.signup.state.Gender
+
+@Immutable
+data class SignUpPetInfo(
+ val petImage : Uri? = null,
+ val petName : String = "",
+ val petBirthDate : String = "",
+ val petGender : Gender = Gender.UNKNOWN,
+ val petNeutered : Boolean = false,
+ val petBreed : String = "",
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpUserInfo.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpUserInfo.kt
new file mode 100644
index 00000000..f103f0b5
--- /dev/null
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/model/SignUpUserInfo.kt
@@ -0,0 +1,11 @@
+package com.paw.key.presentation.ui.signup.model
+
+import androidx.compose.runtime.Immutable
+import com.paw.key.presentation.ui.signup.state.Gender
+
+@Immutable
+data class SignUpUserInfo(
+ val nickName : String = "",
+ val birthDate : String = "",
+ val gender : Gender = Gender.UNKNOWN,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/navigation/SignUpNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/navigation/SignUpNavigation.kt
index 841628f8..6542affe 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/navigation/SignUpNavigation.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/navigation/SignUpNavigation.kt
@@ -1,113 +1,29 @@
package com.paw.key.presentation.ui.signup.navigation
-import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
-import androidx.navigation.navigation
import com.paw.key.core.navigation.Route
-import com.paw.key.core.util.PreferenceDataStore
-import com.paw.key.presentation.ui.signup.SignUpActivityRoute
-import com.paw.key.presentation.ui.signup.SignUpDogRoute
-import com.paw.key.presentation.ui.signup.SignUpLevelRoute
import com.paw.key.presentation.ui.signup.SignUpRoute
-import com.paw.key.presentation.ui.signup.viewmodel.SignUpViewModel
import kotlinx.serialization.Serializable
-@Serializable data object SignUpFlow : Route
-@Serializable data object SignUp : Route
-@Serializable data object SignUpActivity : Route
-@Serializable data object SignUpDog : Route
-@Serializable data object SignUpLevel : Route
-
-
-fun NavHostController.navigateSignUpFlow(navOptions: NavOptions? = null) {
- this.navigate(SignUpFlow, navOptions)
+fun NavHostController.navigateSignUp(
+ navOptions: NavOptions? = null
+) {
+ navigate(SignUp, navOptions)
}
-@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun NavGraphBuilder.signUpNavGraph(
- navController: NavHostController,
navigateToHome: () -> Unit,
+ navigateUp: () -> Unit
) {
- navigation(
- startDestination = SignUp
- ) {
- composable { backStackEntry ->
-
- val parentEntry = remember(backStackEntry) {
- navController.getBackStackEntry()
- }
- val signUpViewModel: SignUpViewModel = hiltViewModel(parentEntry)
-
- val context = LocalContext.current
- val loginInfo by PreferenceDataStore.getLoginInfo().collectAsState(
- initial = PreferenceDataStore.LoginInfo("", "")
- )
-
- LaunchedEffect(loginInfo) {
- if (loginInfo.email.isNotEmpty() && loginInfo.password.isNotEmpty()) {
- signUpViewModel.setLoginCredentials(
- email = loginInfo.email,
- password = loginInfo.password
- )
- }
- }
-
- SignUpRoute(
- navigateSignUpActivity = {
- navController.navigate(SignUpActivity)
- },
- viewModel = signUpViewModel
- )
- }
-
- composable { backStackEntry ->
- val parentEntry = remember(backStackEntry) {
- navController.getBackStackEntry()
- }
- val signUpViewModel: SignUpViewModel = hiltViewModel(parentEntry)
-
- SignUpActivityRoute(
- navigateSignUpDog = {
- navController.navigate(SignUpDog)
- },
- viewModel = signUpViewModel
- )
- }
-
- composable { backStackEntry ->
- val parentEntry = remember(backStackEntry) {
- navController.getBackStackEntry()
- }
- val signUpViewModel: SignUpViewModel = hiltViewModel(parentEntry)
-
- SignUpDogRoute(
- navigateNext = {
- navController.navigate(SignUpLevel)
- },
- viewModel = signUpViewModel
- )
- }
-
- composable { backStackEntry ->
- val parentEntry = remember(backStackEntry) {
- navController.getBackStackEntry()
- }
- val signUpViewModel: SignUpViewModel = hiltViewModel(parentEntry)
-
- SignUpLevelRoute(
- navigateNext = navigateToHome,
- viewModel = signUpViewModel
- )
- }
+ composable {
+ SignUpRoute(
+ navigateUp = navigateUp,
+ navigateToHome = navigateToHome
+ )
}
}
+
+@Serializable data object SignUp : Route
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/state/SignUpContract.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/state/SignUpContract.kt
index ec2b09db..0767f40b 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/state/SignUpContract.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/state/SignUpContract.kt
@@ -1,63 +1,39 @@
package com.paw.key.presentation.ui.signup.state
-import android.net.Uri
import androidx.compose.runtime.Immutable
-import com.paw.key.data.dto.response.PetTraitCategoryDto
-
-class SignUpContract {
- @Immutable
- data class SignUpState(
- val selectedGender: Gender = Gender.UNKNOWN,
- val selectedLocation: String = "",
- val isLocationMenuVisible: Boolean = false,
- val name: String = "",
- val age: String = "",
- val dogImage: Uri? = null,
-
- val dogName: String = "",
- val dogGender: DogGender = DogGender.UNKNOWN,
- val isNeutered: Boolean = false,
- val dogBreed: String = "",
- val ageKnown: AgeKnown = AgeKnown.NONE,
- val dogAge: String = "",
-
- val selectedDongId: Int = 0,
- val selectedGuId: Int = 0,
-
- val selectedEnergyLevel: String = "",
- val selectedSocialLevel: String = "",
-
- val selectedDistrict: List = emptyList(),
- val isDistrictMenuVisible: Boolean = false,
-
- val petTraitCategoryList: List = emptyList(),
-
- val selectedGu: String = "",
- val selectedDong: String = "",
-
- )
-
- enum class Gender {
- MALE,
- FEMALE,
- UNKNOWN
- }
-
- enum class DogGender {
- MALE,
- FEMALE,
- UNKNOWN
- }
+import com.paw.key.presentation.ui.signup.model.SignUpLocationInfo
+import com.paw.key.presentation.ui.signup.model.SignUpMapInfo
+import com.paw.key.presentation.ui.signup.model.SignUpPetInfo
+import com.paw.key.presentation.ui.signup.model.SignUpUserInfo
+
+@Immutable
+data class SignUpState(
+ val userInfo: SignUpUserInfo = SignUpUserInfo(),
+ val petInfo: SignUpPetInfo = SignUpPetInfo(),
+ val locationInfo: SignUpLocationInfo = SignUpLocationInfo(),
+ val mapInfo: SignUpMapInfo = SignUpMapInfo(),
+ val signUpState: SignUpStateType = SignUpStateType.USER_INFO,
+ val currentStep: Float = 1f,
+ val isNextEnabled: Boolean = false,
+ val isRegionComplete: Boolean = false,
+)
+
+sealed class SignUpSideEffect {
+ data class ShowSnackBar(val message: String) : SignUpSideEffect()
+ data object NavigateUp : SignUpSideEffect()
+ data object NavigateNext : SignUpSideEffect()
+ data object NavigateHome : SignUpSideEffect()
+}
- enum class AgeKnown {
- NONE,
- KNOWN,
- UNKNOWN
- }
+enum class SignUpStateType {
+ USER_INFO,
+ PET_INFO,
+ LOCATION_INFO,
+ REGION_MANAGEMENT,
+}
- sealed class SignUpSideEffect {
- data class ShowSnackBar(val message: String) : SignUpSideEffect()
- data object NavigateUp: SignUpSideEffect()
- data object NavigateNext: SignUpSideEffect()
- }
+enum class Gender {
+ MALE,
+ FEMALE,
+ UNKNOWN
}
diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/viewmodel/SignUpViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/viewmodel/SignUpViewModel.kt
index 01e3b118..8a9bd399 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/signup/viewmodel/SignUpViewModel.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/signup/viewmodel/SignUpViewModel.kt
@@ -1,486 +1,421 @@
package com.paw.key.presentation.ui.signup.viewmodel
-import DistrictDto
-import android.content.Context
+import android.content.ContentResolver
import android.net.Uri
-import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.paw.key.core.util.PreferenceDataStore
-import com.paw.key.data.dto.request.onboarding.OnboardingInfoRequest
-import com.paw.key.data.dto.request.onboarding.PetInfoDto
-import com.paw.key.data.dto.request.onboarding.PetTraitDto
+import com.paw.key.core.util.UiState
+import com.paw.key.core.util.flattenCoordinatesToLatLng
+import com.paw.key.core.util.handleError
+import com.paw.key.domain.repository.RegionRepository
import com.paw.key.domain.repository.onboarding.OnboardingInfoRepository
import com.paw.key.domain.repository.onboarding.OnboardingRegionRepository
import com.paw.key.domain.repository.onboarding.OnboardingRepository
-import com.paw.key.presentation.ui.signup.state.SignUpContract
+import com.paw.key.presentation.ui.region.state.DrawType
+import com.paw.key.presentation.ui.signup.state.Gender
+import com.paw.key.presentation.ui.signup.state.SignUpSideEffect
+import com.paw.key.presentation.ui.signup.state.SignUpState
+import com.paw.key.presentation.ui.signup.state.SignUpStateType
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.MultipartBody
-import okhttp3.RequestBody.Companion.asRequestBody
-import java.io.File
+import timber.log.Timber
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
import javax.inject.Inject
@HiltViewModel
class SignUpViewModel @Inject constructor(
+ private val contentResolver : ContentResolver,
+ private val locationRegionRepository: RegionRepository,
private val repository: OnboardingRepository,
private val regionRepository: OnboardingRegionRepository,
private val infoRepository: OnboardingInfoRepository,
) : ViewModel() {
+ private val _state = MutableStateFlow(SignUpState())
+ val state: StateFlow = _state.asStateFlow()
- private val _state = MutableStateFlow(SignUpContract.SignUpState())
- val state: StateFlow = _state.asStateFlow()
+ private val _sideEffect = MutableSharedFlow()
+ val sideEffect: MutableSharedFlow = _sideEffect
- private val _sideEffect = MutableSharedFlow()
- val sideEffect: MutableSharedFlow = _sideEffect
-
- private val _regionList = MutableStateFlow>(emptyList())
- val regionList: StateFlow> = _regionList.asStateFlow()
-
- private var loginEmail: String = ""
- private var loginPassword: String = ""
-
- private val userId = PreferenceDataStore.getUserId()
+ private val userId : StateFlow = PreferenceDataStore.getUserId()
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5000),
+ initialValue = -1
+ )
- init {
- fetchPetTraits()
- fetchRegion()
+ fun deniedPermission() {
+ viewModelScope.launch {
+ _sideEffect.emit(SignUpSideEffect.ShowSnackBar("갤러리 접근 권한을 허용해주세요"))
+ }
}
- fun selectGender(gender: SignUpContract.Gender) {
- _state.update { it.copy(selectedGender = gender) }
- }
+ fun onBackPressed() {
+ viewModelScope.launch {
+ Timber.e("onBackPressed ${_state.value.currentStep}")
+ when (state.value.signUpState) {
+ SignUpStateType.USER_INFO -> {
+ _sideEffect.emit(SignUpSideEffect.NavigateUp)
+ }
- fun onDogImageSelected(uri: Uri) {
- _state.update { it.copy(dogImage = uri) }
- }
+ SignUpStateType.PET_INFO -> {
+ updateState {
+ it.copy(
+ signUpState = SignUpStateType.USER_INFO,
+ currentStep = it.currentStep - 1f
+ )
+ }
+ }
- fun selectDistrict(districts: List) {
- _state.update { it.copy(selectedDistrict = districts) }
- }
+ SignUpStateType.LOCATION_INFO -> {
+ updateState {
+ it.copy(
+ signUpState = SignUpStateType.PET_INFO,
+ currentStep = it.currentStep - 1f
+ )
+ }
+ }
- fun selectLocation(location: String) {
- _state.update { it.copy(selectedLocation = location) }
+ SignUpStateType.REGION_MANAGEMENT -> {
+ updateState { currentState ->
+ currentState.copy(
+ signUpState = SignUpStateType.LOCATION_INFO,
+ isRegionComplete = false,
+ locationInfo = currentState.locationInfo.copy(
+ selectedGu = "",
+ selectedDong = ""
+ )
+ )
+ }
+ }
+ }
+ }
}
- fun onLocationChanged(location: String) {
- _state.update { it.copy(selectedLocation = location) }
- }
+ fun onNextClick() {
+ viewModelScope.launch {
+ if (!validateCurrentStep(_state.value)) {
+ _state.update { currentState ->
+ currentState.copy(
+ isNextEnabled = false
+ )
+ }
+ _sideEffect.emit(SignUpSideEffect.ShowSnackBar("입력되지 않은 정보가 있습니다."))
+ return@launch
+ }
- fun toggleLocationMenu() {
- _state.update { it.copy(isLocationMenuVisible = !_state.value.isLocationMenuVisible) }
- }
+ when (_state.value.signUpState) {
+ SignUpStateType.USER_INFO -> {
+ updateState {
+ it.copy(
+ signUpState = SignUpStateType.PET_INFO,
+ )
+ }
+ _sideEffect.emit(SignUpSideEffect.NavigateNext)
+ }
- fun onNameChanged(name: String) {
- _state.update { it.copy(name = name) }
- }
+ SignUpStateType.PET_INFO -> {
+ updateState {
+ it.copy(
+ signUpState = SignUpStateType.LOCATION_INFO,
+ )
+ }
+ _sideEffect.emit(SignUpSideEffect.NavigateNext)
+ }
- fun onAgeChanged(age: String) {
- _state.update { it.copy(age = age) }
- }
+ SignUpStateType.LOCATION_INFO -> {
+ // TODO: 서버에 값 전송 후 Home으로
+ //submitRegistration()
+ // Todo : 서버 전송 시 uri를 contentresolver로 실제 사진 가져와서 전송하기 그리고 꼭 close하기
+ //val inputStream = contentResolver.openInputStream(_state.value.petInfo.petImage)
+ if (_state.value.isRegionComplete && _state.value.locationInfo.selectedGu.isNotBlank() && _state.value.locationInfo.selectedDong.isNotBlank()) {
+ _sideEffect.emit(SignUpSideEffect.NavigateHome)
+ } else {
+ updateState {
+ it.copy(
+ signUpState = SignUpStateType.REGION_MANAGEMENT,
+ )
+ }
+ _sideEffect.emit(SignUpSideEffect.NavigateNext)
+ }
+ }
- fun hideLocationMenu() {
- _state.update { it.copy(isLocationMenuVisible = false) }
+ SignUpStateType.REGION_MANAGEMENT -> {
+ updateState {
+ it.copy(
+ signUpState = SignUpStateType.LOCATION_INFO,
+ isRegionComplete = true
+ )
+ }
+ }
+ }
+ }
}
- fun onGuSelected(guName: String, guId: Int) {
+ private fun updateState(updateAction: (SignUpState) -> SignUpState) {
_state.update { currentState ->
- currentState.copy(
- selectedGu = guName,
- selectedGuId = guId,
- // 구를 새로 선택하면 기존 동 선택 초기화
- selectedDong = "",
- selectedDongId = 0,
- // 구 선택 후 메뉴 닫기
- isLocationMenuVisible = false
- )
+ val newState = updateAction(currentState)
+ val isButtonEnabled = validateCurrentStep(newState)
+ newState.copy(isNextEnabled = isButtonEnabled)
}
}
- fun onDongSelected(dongName: String, dongId: Int) {
- _state.update { it.copy(selectedDong = dongName, selectedDongId = dongId) }
- }
-
- fun onDogNameChanged(dogName: String) {
- _state.update { it.copy(dogName = dogName) }
- }
-
- fun selectDogGender(dogGender: SignUpContract.DogGender) {
- _state.update { it.copy(dogGender = dogGender) }
- }
-
- fun toggleNeutering() {
- _state.update { it.copy(isNeutered = !_state.value.isNeutered) }
- }
-
- fun onDogBreedChanged(dogBreed: String) {
- _state.update { it.copy(dogBreed = dogBreed) }
- }
-
- fun selectAgeKnown(ageKnown: SignUpContract.AgeKnown) {
- _state.update { it.copy(ageKnown = ageKnown) }
+ fun updateStep() {
+ viewModelScope.launch {
+ _state.update { currentState ->
+ if (_state.value.signUpState != SignUpStateType.REGION_MANAGEMENT) {
+ currentState.copy(
+ currentStep = _state.value.currentStep + 1f
+ )
+ } else {
+ currentState.copy(
+ currentStep = _state.value.currentStep
+ )
+ }
+ }
+ }
}
- fun onDogAgeChanged(dogAge: String) {
- _state.update { it.copy(dogAge = dogAge) }
+ // userinfo
+ fun updateNickname(nickname: String) {
+ viewModelScope.launch {
+ updateState { currentState ->
+ currentState.copy(
+ userInfo = currentState.userInfo.copy(nickName = nickname)
+ )
+ }
+ }
}
- fun selectEnergyLevel(energyLevel: String) {
- Log.d("SignUpViewModel", "Energy level selected: $energyLevel")
- _state.update { it.copy(selectedEnergyLevel = energyLevel) }
- }
+ fun updateBirthDate(birthDate: String) {
+ val digitsOnly = birthDate.filter { it.isDigit() }
- fun selectSocialLevel(socialLevel: String) {
- Log.d("SignUpViewModel", "Social level selected: $socialLevel")
- _state.update { it.copy(selectedSocialLevel = socialLevel) }
+ if (digitsOnly.length <= 8) {
+ updateState { currentState ->
+ currentState.copy(
+ userInfo = currentState.userInfo.copy(birthDate = digitsOnly)
+ )
+ }
+ }
}
- fun isNextButtonEnabled(): Boolean {
- return isSignUpEnabled()
+ fun updateGender(gender: Gender) {
+ viewModelScope.launch {
+ updateState { currentState ->
+ currentState.copy(
+ userInfo = currentState.userInfo.copy(gender = gender)
+ )
+ }
+ }
}
- fun isLevelScreenEnabled(): Boolean {
- val state = _state.value
- return state.selectedEnergyLevel.isNotEmpty() &&
- state.selectedSocialLevel.isNotEmpty()
+ // petinfo
+ fun updatePetName(name: String) {
+ viewModelScope.launch {
+ updateState { currentState ->
+ currentState.copy(
+ petInfo = currentState.petInfo.copy(petName = name)
+ )
+ }
+ }
}
- fun isSignUpEnabled(): Boolean {
- val state = _state.value
- Log.d(
- "SignUpViewModel", """
- isSignUpEnabled check:
- - selectedEnergyLevel: '${state.selectedEnergyLevel}' (isEmpty: ${state.selectedEnergyLevel.isEmpty()})
- - selectedSocialLevel: '${state.selectedSocialLevel}' (isEmpty: ${state.selectedSocialLevel.isEmpty()})
- - name: '${state.name}' (isEmpty: ${state.name.isEmpty()})
- - age: '${state.age}' (isEmpty: ${state.age.isEmpty()})
- - selectedGu: '${state.selectedGu}' (isEmpty: ${state.selectedGu.isEmpty()})
- - selectedDong: '${state.selectedDong}' (isEmpty: ${state.selectedDong.isEmpty()})
- - dogName: '${state.dogName}' (isEmpty: ${state.dogName.isEmpty()})
- - dogBreed: '${state.dogBreed}' (isEmpty: ${state.dogBreed.isEmpty()})
- - dogImage: ${state.dogImage != null}
- - loginEmail: '$loginEmail' (isEmpty: ${loginEmail.isEmpty()})
- - loginPassword: '$loginPassword' (isEmpty: ${loginPassword.isEmpty()})
- """.trimIndent()
- )
-
- return state.selectedEnergyLevel.isNotEmpty() &&
- state.selectedSocialLevel.isNotEmpty() &&
- state.name.isNotEmpty() &&
- state.age.isNotEmpty() &&
- state.selectedGu.isNotEmpty() &&
- state.selectedDong.isNotEmpty() &&
- state.dogName.isNotEmpty() &&
- state.dogBreed.isNotEmpty() &&
- state.dogImage != null &&
- loginEmail.isNotEmpty() &&
- loginPassword.isNotEmpty()
- }
+ fun updatePetBirthDate(birthDate: String) {
+ val digitsOnly = birthDate.filter { it.isDigit() }
- fun setLoginCredentials(email: String, password: String) {
- loginEmail = email
- loginPassword = password
- Log.d(
- "SignUpViewModel",
- "Login credentials set - Email: '$email', Password length: ${password.length}"
- )
+ if (digitsOnly.length <= 8) {
+ updateState { currentState ->
+ currentState.copy(
+ petInfo = currentState.petInfo.copy(petBirthDate = digitsOnly)
+ )
+ }
+ }
}
- fun debugSignUpState() {
- val state = _state.value
- Log.d(
- "DEBUG_SIGNUP", """
- === SignUp State Debug ===
- Energy: '${state.selectedEnergyLevel}' (empty: ${state.selectedEnergyLevel.isEmpty()})
- Social: '${state.selectedSocialLevel}' (empty: ${state.selectedSocialLevel.isEmpty()})
- Name: '${state.name}' (empty: ${state.name.isEmpty()})
- Age: '${state.age}' (empty: ${state.age.isEmpty()})
- Gu: '${state.selectedGu}' (empty: ${state.selectedGu.isEmpty()})
- GuId: ${state.selectedGuId}
- Dong: '${state.selectedDong}' (empty: ${state.selectedDong.isEmpty()})
- DongId: ${state.selectedDongId}
- Dog Name: '${state.dogName}' (empty: ${state.dogName.isEmpty()})
- Dog Breed: '${state.dogBreed}' (empty: ${state.dogBreed.isEmpty()})
- Dog Image: ${state.dogImage != null}
- Login Email: '$loginEmail' (empty: ${loginEmail.isEmpty()})
- Login Password: '$loginPassword' (length: ${loginPassword.length})
-
- 각 단계별 체크:
- - 성향 정보: ${state.selectedEnergyLevel.isNotEmpty() && state.selectedSocialLevel.isNotEmpty()}
- - 개인 정보: ${state.name.isNotEmpty() && state.age.isNotEmpty()}
- - 지역 정보: ${state.selectedGu.isNotEmpty() && state.selectedDong.isNotEmpty()}
- - 반려견 정보: ${state.dogName.isNotEmpty() && state.dogBreed.isNotEmpty() && state.dogImage != null}
- - 로그인 정보: ${loginEmail.isNotEmpty() && loginPassword.isNotEmpty()}
-
- 최종 가능 여부: ${isSignUpEnabled()}
- =========================
- """.trimIndent()
- )
+ fun updatePetGender(gender: Gender) {
+ viewModelScope.launch {
+ updateState { currentState ->
+ currentState.copy(
+ petInfo = currentState.petInfo.copy(petGender = gender)
+ )
+ }
+ }
}
- private fun fetchPetTraits() {
+ fun updatePetNeutered(neutered: Boolean) {
viewModelScope.launch {
- try {
- val result = repository.getOnboardingPets(userId = userId.first())
- result.onSuccess { response ->
- Log.d(
- "SignUpViewModel",
- "Pet traits loaded: ${response.data.petTraitCategoryList.size}"
- )
- _state.update { it.copy(petTraitCategoryList = response.data.petTraitCategoryList) }
- }.onFailure { error ->
- Log.e("SignUpViewModel", "성향 정보 불러오기 실패: ${error.message}")
- }
- } catch (e: Exception) {
- Log.e("SignUpViewModel", "fetchPetTraits Exception: ${e.message}")
+ updateState { currentState ->
+ currentState.copy(
+ petInfo = currentState.petInfo.copy(petNeutered = neutered)
+ )
}
}
}
- private fun getSelectedRegionId(): Int {
- val state = _state.value
-
- // selectedDongId가 있으면 그것을 우선 사용
- if (state.selectedDongId != 0) {
- Log.d("SignUpViewModel", "Using selectedDongId: ${state.selectedDongId}")
- return state.selectedDongId
+ fun updatePetBreed(breed: String) {
+ viewModelScope.launch {
+ updateState { currentState ->
+ currentState.copy(
+ petInfo = currentState.petInfo.copy(petBreed = breed)
+ )
+ }
}
+ }
- // 없으면 기존 방식으로 찾기
- val selectedDong = _regionList.value
- .find { it.gu.name == state.selectedGu }
- ?.dongs
- ?.find { it.name == state.selectedDong }
-
- Log.d(
- "SignUpViewModel",
- "Selected region - Gu: ${state.selectedGu}, Dong: ${state.selectedDong}, DongId: ${selectedDong?.id}"
- )
- return selectedDong?.id ?: run {
- Log.e(
- "SignUpViewModel",
- "동 ID를 찾을 수 없습니다. Gu: ${state.selectedGu}, Dong: ${state.selectedDong}"
- )
- 1
+ fun updatePetImage(uri: Uri?) {
+ viewModelScope.launch {
+ updateState { currentState ->
+ currentState.copy(
+ petInfo = currentState.petInfo.copy(petImage = uri)
+ )
+ }
}
}
- fun fetchRegion() {
- viewModelScope.launch {
- try {
- val result = regionRepository.getOnboardingRegion(userId = userId.first())
- result.onSuccess { response ->
- Log.d("SignUpViewModel", "Region loaded: ${response.data.districtDtos.size}")
- _regionList.value = response.data.districtDtos
- }.onFailure {
- Log.e("SignUpViewModel", "구/동 가져오기 실패: ${it.message}")
- }
- } catch (e: Exception) {
- Log.e("SignUpViewModel", "fetchRegion Exception: ${e.message}")
+ // location
+ fun onRegionSelected(gu: String, dong: String) {
+ updateState { currentState ->
+ // 원래 선택한 구와 동이 같으면 완료로 아니라면 다시 지도뷰
+ if (gu != currentState.locationInfo.selectedGu || dong != currentState.locationInfo.selectedDong) {
+ currentState.copy(
+ locationInfo = currentState.locationInfo.copy(
+ selectedGu = gu,
+ selectedDong = dong,
+ ),
+ isRegionComplete = false,
+ signUpState = SignUpStateType.LOCATION_INFO
+ )
+ } else {
+ currentState.copy(
+ locationInfo = currentState.locationInfo.copy(
+ selectedGu = gu,
+ selectedDong = dong,
+ ),
+ signUpState = SignUpStateType.LOCATION_INFO
+ )
}
}
+ getRegion()
}
- fun signUp(context: Context) {
+ fun getRegion() {
viewModelScope.launch {
- try {
- Log.d("SignUpViewModel", "Starting signUp process...")
- val state = _state.value
-
- // 에너지 레벨과 사회성 레벨 체크
- if (state.selectedEnergyLevel.isEmpty() || state.selectedSocialLevel.isEmpty()) {
- Log.e("SignUpViewModel", "Energy or social level not selected")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("에너지 레벨과 사회성 레벨을 모두 선택해주세요."))
- return@launch
- }
-
- // 지역 정보 체크
- if (state.selectedGu.isEmpty() || state.selectedDong.isEmpty()) {
- Log.e("SignUpViewModel", "Region not selected")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("지역을 선택해주세요."))
- return@launch
- }
-
- val regionId = getSelectedRegionId()
- if (regionId == 1) { // 기본값이면 실제로 선택되지 않았을 가능성
- Log.e("SignUpViewModel", "Invalid region ID")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("올바른 지역을 선택해주세요."))
- return@launch
- }
-
- // 전체 회원가입 정보 체크
- if (!isSignUpEnabled()) {
- Log.e("SignUpViewModel", "Required signup info missing")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("회원가입 정보가 부족합니다."))
- return@launch
- }
-
- // 나이 유효성 체크
- val userAge = state.age.toIntOrNull()
- if (userAge == null || userAge <= 0) {
- Log.e("SignUpViewModel", "Invalid user age: ${state.age}")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("올바른 나이를 입력해주세요."))
- return@launch
- }
+ val validUserId = userId.filter { it != -1 }.first()
+ getRegionGeometry(
+ userId = validUserId,
+ regionId = 39,
+ )
+ onNextClick()
+ }
+ }
- // 강아지 나이 유효성 체크 (나이를 안다고 했을 때만)
- val dogAge = if (state.ageKnown == SignUpContract.AgeKnown.KNOWN) {
- state.dogAge.toIntOrNull()?.takeIf { it >= 0 } ?: run {
- Log.e("SignUpViewModel", "Invalid dog age: ${state.dogAge}")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("올바른 강아지 나이를 입력해주세요."))
- return@launch
+ fun getRegionGeometry(userId: Int, regionId: Int?) = viewModelScope.launch {
+ locationRegionRepository.getRegionGeometry(userId, regionId!!)
+ .onSuccess { data ->
+ val coordinates = data.geometry.coordinates
+ val flattenedLatLng = flattenCoordinatesToLatLng(coordinates)
+
+ if (flattenedLatLng.isEmpty() || flattenedLatLng.first().isEmpty()) {
+ _state.update { currentState ->
+ currentState.copy(
+ mapInfo = currentState.mapInfo.copy(
+ uiState = UiState.Failure("좌표 데이터가 올바르지 않습니다")
+ )
+ )
}
- } else {
- 0
- }
-
- // trait ID 찾기
- val energyTraitId = state.petTraitCategoryList
- .find { it.petTraitCategoryName == "에너지레벨" }
- ?.petTraitCategoryOptions
- ?.find { it.petTraitCategoryOptionText == state.selectedEnergyLevel }
- ?.petTraitCategoryOptionId
-
- val socialTraitId = state.petTraitCategoryList
- .find { it.petTraitCategoryName == "사회성레벨" }
- ?.petTraitCategoryOptions
- ?.find { it.petTraitCategoryOptionText == state.selectedSocialLevel }
- ?.petTraitCategoryOptionId
-
- if (energyTraitId == null || socialTraitId == null) {
- Log.e(
- "SignUpViewModel",
- "Trait ID not found - Energy: $energyTraitId, Social: $socialTraitId"
- )
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("성향 정보를 다시 선택해주세요."))
return@launch
}
- Log.d(
- "SignUpViewModel",
- "Energy trait ID: $energyTraitId, Social trait ID: $socialTraitId"
- )
-
- // 요청 객체 생성
- val request = OnboardingInfoRequest(
- loginId = loginEmail,
- password = loginPassword,
- name = state.name,
- gender = when (state.selectedGender) {
- SignUpContract.Gender.MALE -> "M"
- SignUpContract.Gender.FEMALE -> "F"
- SignUpContract.Gender.UNKNOWN -> "M"
- },
- age = userAge,
- regionId = getSelectedRegionId(),
- pet = PetInfoDto(
- name = state.dogName,
- gender = when (state.dogGender) {
- SignUpContract.DogGender.MALE -> "M"
- SignUpContract.DogGender.FEMALE -> "F"
- SignUpContract.DogGender.UNKNOWN -> "M" // 예외.. 를 위해 일단 달아놨슴다
- },
- age = dogAge,
- isAgeKnown = state.ageKnown == SignUpContract.AgeKnown.KNOWN,
- isNeutered = state.isNeutered,
- breed = state.dogBreed,
- petTraits = listOf(
- PetTraitDto(
- traitCategoryId = 1,
- traitOptionId = energyTraitId
- ),
- PetTraitDto(
- traitCategoryId = 2,
- traitOptionId = socialTraitId
+ val allPoints = flattenedLatLng.flatten().toPersistentList()
+
+ if (flattenedLatLng.size == 1) {
+ // 폴리곤이 하나일 경우
+ _state.update { currentState ->
+ currentState.copy(
+ mapInfo = currentState.mapInfo.copy(
+ uiState = UiState.Success(flattenedLatLng),
+ entireCoordinates = allPoints,
+ drawType = DrawType.SINGLE,
+ regionName = data.regionName
)
)
- )
- )
-
- // 이미지 처리
- val imagePart = state.dogImage?.let { uri ->
- try {
- val inputStream = context.contentResolver.openInputStream(uri)
- val tempFile = File.createTempFile("pet_profile", ".jpg", context.cacheDir)
- inputStream?.use { input ->
- tempFile.outputStream().use { output -> input.copyTo(output) }
- }
- val fileRequestBody = tempFile.asRequestBody("image/jpeg".toMediaType())
- MultipartBody.Part.createFormData(
- "pet_profile",
- "pet_image.jpg",
- fileRequestBody
+ }
+ } else {
+ // 폴리곤이 여러 개일 경우
+ _state.update { currentState ->
+ currentState.copy(
+ mapInfo = currentState.mapInfo.copy(
+ uiState = UiState.Success(flattenedLatLng),
+ entireCoordinates = allPoints,
+ drawType = DrawType.MULTIPLE,
+ regionName = data.regionName
+ )
)
- } catch (e: Exception) {
- Log.e("SignUpViewModel", "Image processing failed: ${e.message}")
- null
}
}
-
- if (imagePart == null) {
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("이미지를 선택해주세요."))
- return@launch
- }
-
- Log.d("SignUpViewModel", "Image processing successful ${userId.first()}")
- // 서버 요청
- val result = infoRepository.postOnboardingInfo(
- userId = userId.first(),
- image = imagePart,
- onboardingInfoRequest = request
- )
-
- result.onSuccess { response ->
- Log.d("SignUpViewModel", "SignUp successful - Response: $response")
-
- // 회원가입 성공 시 사용자 정보를 DataStore에 저장
- try {
- PreferenceDataStore.saveUserInfo(
- userId = response.data.userId,
- userName = response.data.userName,
- petId = response.data.petId,
- petName = response.data.petName
+ }
+ .onFailure { throwable ->
+ val errorMessage = handleError(throwable)
+ _state.update { currentState ->
+ currentState.copy(
+ mapInfo = currentState.mapInfo.copy(
+ uiState = UiState.Failure(errorMessage)
)
+ )
+ }
+ }
+ }
- // 로그인 정보도 함께 저장
- PreferenceDataStore.saveLoginInfo(
- email = loginEmail,
- password = loginPassword
- )
+ private fun validateCurrentStep(state: SignUpState): Boolean {
+ return when (state.signUpState) {
+ SignUpStateType.USER_INFO -> {
+ // UserInfo 확인
+ state.userInfo.nickName.isNotBlank() && state.userInfo.nickName.length <= 8 &&
+ state.userInfo.birthDate.length == 8 && state.userInfo.birthDate.isValidDate() &&
+ state.userInfo.gender != Gender.UNKNOWN
+ }
- Log.d(
- "SignUpViewModel",
- "User info and login info saved to DataStore successfully"
- )
- Log.d(
- "SignUpViewModel",
- "Saved - UserId: ${response.data.userId}, UserName: ${response.data.userName}, PetId: ${response.data.petId}, PetName: ${response.data.petName}"
- )
+ SignUpStateType.PET_INFO -> {
+ // PetInfo 확인
+ state.petInfo.petName.isNotBlank() && state.petInfo.petName.length <= 8 &&
+ state.petInfo.petBirthDate.length == 8 && state.petInfo.petBirthDate.isValidDate() &&
+ state.petInfo.petGender != Gender.UNKNOWN &&
+ state.petInfo.petBreed.isNotBlank() &&
+ state.petInfo.petImage != null
+ }
- } catch (e: Exception) {
- Log.e(
- "SignUpViewModel",
- "Failed to save user info to DataStore: ${e.message}"
- )
- }
+ SignUpStateType.LOCATION_INFO -> {
+ // LocationInfo 확인
+ state.locationInfo.selectedGu.isNotBlank() &&
+ state.locationInfo.selectedDong.isNotBlank()
+ }
- _sideEffect.emit(SignUpContract.SignUpSideEffect.NavigateNext)
- }.onFailure { error ->
- Log.e("SignUpViewModel", "SignUp failed: ${error.message}")
- Log.e("SignUpViewModel", "SignUp failed: ${request}")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("회원가입 실패: ${error.message}"))
- }
- } catch (e: Exception) {
- Log.e("SignUpViewModel", "SignUp Exception: ${e.message}")
- _sideEffect.emit(SignUpContract.SignUpSideEffect.ShowSnackBar("알 수 없는 오류가 발생했습니다."))
+ SignUpStateType.REGION_MANAGEMENT -> {
+ state.locationInfo.selectedGu.isNotBlank() &&
+ state.locationInfo.selectedDong.isNotBlank()
}
}
}
+}
+
+private fun String.isValidDate(): Boolean {
+ if (this.length != 8) return false
+ return try {
+ val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
+ LocalDate.parse(this, formatter)
+ true
+ } catch (e: Exception) {
+ false
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/paw/key/presentation/ui/splash/SplashScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/splash/SplashScreen.kt
index 36e80d72..9f0d9101 100644
--- a/app/src/main/java/com/paw/key/presentation/ui/splash/SplashScreen.kt
+++ b/app/src/main/java/com/paw/key/presentation/ui/splash/SplashScreen.kt
@@ -1,23 +1,23 @@
package com.paw.key.presentation.ui.splash
+import android.app.Activity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
-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.layout.size
import androidx.compose.material3.Icon
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+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.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
@@ -47,6 +47,22 @@ fun SplashRoute(
viewModel: SplashViewModel = hiltViewModel(),
) {
val effectFlow = viewModel.sideeffect
+ val statusBarColor = PawKeyTheme.colors.green500
+
+ val context = LocalContext.current
+ val window = (context as? Activity)?.window
+ val previousNavBarColor = remember { window?.statusBarColor }
+
+ DisposableEffect(Unit) {
+ window?.statusBarColor = statusBarColor.toArgb()
+
+ onDispose {
+ // 화면에서 벗어날 때 원래 색으로 복원
+ previousNavBarColor?.let {
+ window?.statusBarColor = it
+ }
+ }
+ }
LaunchedEffect(Unit) {
effectFlow.collect { effect ->
diff --git a/app/src/main/res/drawable/ic_roundcheck_invalid.xml b/app/src/main/res/drawable/ic_roundcheck_invalid.xml
index 194239ca..c037335a 100644
--- a/app/src/main/res/drawable/ic_roundcheck_invalid.xml
+++ b/app/src/main/res/drawable/ic_roundcheck_invalid.xml
@@ -1,20 +1,11 @@
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16"
+ android:viewportHeight="16">
-
-
+ android:fillColor="#FFFFFFFF"
+ android:strokeColor="#FF9C9C9C"
+ android:strokeWidth="1"
+ android:pathData="M8 0.5A7.5 7.5 0 1 0 8 15.5 7.5 7.5 0 1 0 8 0.5z"/>
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_roundcheck_valid.xml b/app/src/main/res/drawable/ic_roundcheck_valid.xml
index b8fdbc78..e365437d 100644
--- a/app/src/main/res/drawable/ic_roundcheck_valid.xml
+++ b/app/src/main/res/drawable/ic_roundcheck_valid.xml
@@ -1,16 +1,14 @@
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16"
+ android:viewportHeight="16">
+ android:fillColor="#FFFFFFFF"
+ android:strokeColor="#FF00D281"
+ android:strokeWidth="1"
+ android:pathData="M8 0.5A7.5 7.5 0 1 0 8 15.5 7.5 7.5 0 1 0 8 0.5z"/>
-
+ android:fillColor="#FF00D281"
+ android:pathData="M8 3A5 5 0 1 0 8 13 5 5 0 1 0 8 3z"/>
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_signup_image_edit.xml b/app/src/main/res/drawable/ic_signup_image_edit.xml
new file mode 100644
index 00000000..7995ee96
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signup_image_edit.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_signup_profile_edit.xml b/app/src/main/res/drawable/ic_signup_profile_edit.xml
new file mode 100644
index 00000000..3bab1219
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signup_profile_edit.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_signup_search.xml b/app/src/main/res/drawable/ic_signup_search.xml
new file mode 100644
index 00000000..f417e12c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_signup_search.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 0957ce2d..6d25d34f 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file