diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eacc6bf6..e391541b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,6 +26,8 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "BASE_URL", properties["base.url"].toString()) + buildConfigField("String", "DEBUG_BASE_URL", properties["debug.base.url"].toString()) + buildConfigField("String", "KAKAO_NATIVE_KEY", properties["kakao.native.key"].toString()) buildConfigField("String", "KAKAO_REST_API_KEY", properties["kakao.rest.api"].toString()) buildConfigField("String", "NAVERMAP_CLIENT_SECRET", properties["NAVERMAP_CLIENT_SECRET"].toString()) @@ -59,14 +61,6 @@ android { compose = true buildConfig = true } - signingConfigs { - getByName("debug") { - keyAlias = "androiddebugkey" - keyPassword = "android" - storeFile = File("${project.rootDir.absolutePath}/keystore/debug.keystore")//project.rootProject.file("debug.keystore") - storePassword = "android" - } - } } dependencies { diff --git a/app/src/main/java/com/paw/key/core/app/AppRestarter.kt b/app/src/main/java/com/paw/key/core/app/AppRestarter.kt new file mode 100644 index 00000000..0a1065da --- /dev/null +++ b/app/src/main/java/com/paw/key/core/app/AppRestarter.kt @@ -0,0 +1,5 @@ +package com.paw.key.core.app + +interface AppRestarter { + fun restartApp() +} diff --git a/app/src/main/java/com/paw/key/core/app/AppRestarterImpl.kt b/app/src/main/java/com/paw/key/core/app/AppRestarterImpl.kt new file mode 100644 index 00000000..df2299c3 --- /dev/null +++ b/app/src/main/java/com/paw/key/core/app/AppRestarterImpl.kt @@ -0,0 +1,29 @@ +package com.paw.key.core.app + +import android.content.Context +import android.content.Intent +import android.os.Process +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import kotlin.system.exitProcess + +class AppRestarterImpl @Inject constructor( + @param:ApplicationContext private val context: Context +) : AppRestarter { + + override fun restartApp() { + val intent = context.packageManager + .getLaunchIntentForPackage(context.packageName) + ?.apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } ?: return + + context.startActivity(intent) + killCurrentProcess() + } + + private fun killCurrentProcess() { + Process.killProcess(Process.myPid()) + exitProcess(0) + } +} diff --git a/app/src/main/java/com/paw/key/core/app/di/AppModule.kt b/app/src/main/java/com/paw/key/core/app/di/AppModule.kt new file mode 100644 index 00000000..ff307692 --- /dev/null +++ b/app/src/main/java/com/paw/key/core/app/di/AppModule.kt @@ -0,0 +1,16 @@ +package com.paw.key.core.app.di + +import com.paw.key.core.app.AppRestarter +import com.paw.key.core.app.AppRestarterImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +abstract class AppModule { + + @Binds + abstract fun bindAppRestarter(impl: AppRestarterImpl): AppRestarter +} diff --git a/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt b/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt deleted file mode 100644 index 64cc47df..00000000 --- a/app/src/main/java/com/paw/key/core/designsystem/component/CourseDetail.kt +++ /dev/null @@ -1,391 +0,0 @@ -package com.paw.key.core.designsystem.component - -import android.util.Log -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex -import coil.compose.AsyncImage -import coil.request.ImageRequest -import com.paw.key.R -import com.paw.key.core.designsystem.theme.Gray100 -import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.core.extension.noRippleClickable -import com.paw.key.domain.entity.walklist.CategoryTop3Entity - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun CourseDetail( - title : String, - petName : String, - date : String, - Icon : Int, - location : String, - onClickLike: (Boolean) -> Unit, - petProfileImage : String, - routeMapImageUrl : String, - categorySummary : List, - categoryTop3 : List, - totalReviewCount : Int, - walkingImageUrls : List, - content: String, - onImageClick: (String) -> Unit, - modifier: Modifier = Modifier -) { - val context = LocalContext.current - val isLiked = remember { mutableStateOf(false) } - - LaunchedEffect(routeMapImageUrl) { - Log.d("LaunchedEffect", "routeMapImageUrl: $routeMapImageUrl") - Log.d("LaunchedEffect", "walkingImageUrls: $walkingImageUrls") - Log.d("LaunchedEffect", "petProfileImage: $petProfileImage") - } - - Column( - modifier = modifier - .fillMaxSize() - .background(color = PawKeyTheme.colors.white1) - ) { - // 이미지 영역을 별도 Box로 분리 - Box( - modifier = Modifier - .fillMaxWidth() - .height(244.dp) - ) { - AsyncImage( - model = ImageRequest.Builder(context) - .data(routeMapImageUrl) - .crossfade(true) - .build(), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .fillMaxSize() - .background(color = Gray100) - ) - Row( - modifier = Modifier - .fillMaxWidth() - .height(10.dp) - .background( - color = PawKeyTheme.colors.white1, - shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - ) - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) - .align(Alignment.BottomCenter) - ){ - - } - } - - // 컨텐츠 영역 - Column( - modifier = Modifier - .fillMaxWidth() - .background( - color = PawKeyTheme.colors.white1, - shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) - ) - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) - .padding(horizontal = 16.dp) - ) { - // 제목과 좋아요 버튼 - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - ) { - Text( - text = title, - style = PawKeyTheme.typography.head20Sb, - color = PawKeyTheme.colors.black - ) - - Icon( - imageVector = ImageVector.vectorResource(id = Icon), - contentDescription = "좋아요", - tint = Color.Unspecified, - modifier = Modifier - .size(24.dp) - .noRippleClickable { - isLiked.value = !isLiked.value - onClickLike(isLiked.value) - } - ) - } - - // 펫 정보 - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 12.dp) - ) { - AsyncImage( - model = ImageRequest.Builder(context) - .data(petProfileImage) - .crossfade(true) - .build(), - contentDescription = null, - modifier = Modifier - .size(48.dp) - .clip(CircleShape), - contentScale = ContentScale.Crop - ) - - Text( - text = petName, - style = PawKeyTheme.typography.body16Sb, - modifier = Modifier.padding(start = 8.dp) - ) - } - - // 위치와 날짜 정보 - Column(modifier = Modifier.padding(vertical = 12.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_walk_review_location), - contentDescription = "장소", - tint = Color.Unspecified - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = location, - style = PawKeyTheme.typography.body14M, - color = PawKeyTheme.colors.gray400 - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_walk_review_time), - contentDescription = "시간", - tint = Color.Unspecified - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = date, - style = PawKeyTheme.typography.body14M, - color = PawKeyTheme.colors.gray400 - ) - } - } - - // 카테고리 칩들 - FlowRow ( - horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.padding(vertical = 13.dp), - maxItemsInEachRow = 3, - ) { - categorySummary.forEach { category -> - SubChip(text = category) - } - } - - Spacer(modifier = Modifier.height(12.dp)) - - // 산책 이미지들 - if (walkingImageUrls.isNotEmpty()) { - LazyRow( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 12.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(walkingImageUrls.size) { index -> - AsyncImage( - model = ImageRequest.Builder(context) - .data(walkingImageUrls[index]) - .crossfade(true) - .build(), - contentDescription = null, - modifier = Modifier - .width(100.dp) - .height(100.dp) - .clip(RoundedCornerShape(8.dp)) - .clickable { onImageClick(walkingImageUrls[index]) }, - contentScale = ContentScale.Crop - ) - } - } - } - - // 컨텐츠 - Text( - text = content, - style = PawKeyTheme.typography.body14R, - modifier = Modifier.padding(vertical = 12.dp) - ) - - Text( - text = "본인 위치에서의 거리", - style = PawKeyTheme.typography.caption12Sb1, - color = PawKeyTheme.colors.gray200, - modifier = Modifier.padding(vertical = 12.dp) - ) - - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .height(8.dp) - .border( - width = 8.dp, - color = PawKeyTheme.colors.gray100, - ) - ) - - // 리뷰 섹션 - ReviewSection( - categoryTop3 = categoryTop3, - totalReviewCount = totalReviewCount - ) - } - } -} - -@Composable -private fun ReviewSection( - categoryTop3: List, - totalReviewCount: Int -) { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "이런 점이 좋았어요", - style = PawKeyTheme.typography.head18Sb, - color = PawKeyTheme.colors.black, - modifier = Modifier.padding(vertical = 16.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_edit), - contentDescription = "편집", - tint = Color.Unspecified - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = totalReviewCount.toString(), - style = PawKeyTheme.typography.caption12M, - color = PawKeyTheme.colors.gray200, - ) - } - - if (categoryTop3.isEmpty()) { - Text( - text = "아직은 후기가 없어요.", - style = PawKeyTheme.typography.body16Sb, - color = PawKeyTheme.colors.gray400, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - } else { - categoryTop3.forEach { tag -> - val fillRatio = (tag.percentage.coerceIn(0, 100)) / 100f - val backgroundColor = when (tag.rank) { - 1 -> PawKeyTheme.colors.green300 - 2 -> PawKeyTheme.colors.green200 - 3 -> PawKeyTheme.colors.green100 - else -> PawKeyTheme.colors.green500 - } - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) - .height(37.dp) - .background(PawKeyTheme.colors.white2, RoundedCornerShape(6.dp)) - ) { - Box( - modifier = Modifier - .fillMaxWidth(fillRatio) - .fillMaxHeight() - .background(backgroundColor, RoundedCornerShape(6.dp)) - ) - - Text( - text = tag.optionText, - color = PawKeyTheme.colors.black, - modifier = Modifier - .align(Alignment.CenterStart) - .padding(horizontal = 16.dp) - .zIndex(1f), - style = PawKeyTheme.typography.caption12Sb2, - maxLines = 1, - softWrap = false, - overflow = TextOverflow.Visible - ) - } - } - } - } -} - - -@Preview(showBackground = true) -@Composable -fun CourseDetailPreview() { - PawKeyTheme { - CourseDetail( - title = "홍대 주변 좋은 산책 코스", - petName = "핑구", - date = "2025/06/30", - Icon = R.drawable.ic_eye_linear_gray_valid, - location = "홍대입구역", - onClickLike = {}, - petProfileImage = "https://pawkey-server.com/image/profile.png", - routeMapImageUrl = "https://pawkey-server.com/image/map.png", - categoryTop3 = listOf( - CategoryTop3Entity(rank = 1, optionText = "산책로가 어쩌구 저꾸", percentage = 42, categoryName = "", categoryOptionId = 1, categoryId = 2), - CategoryTop3Entity(rank = 2, optionText = "풍경이 예뻐요", percentage = 37, categoryName = "", categoryOptionId = 1, categoryId = 2), - CategoryTop3Entity(rank = 3, optionText = "깨끗해요", percentage = 35, categoryName = "", categoryOptionId = 1, categoryId = 2) - ), - totalReviewCount = 42, - walkingImageUrls = listOf( - "https://pawkey-server.com/image/walk1.jpg", - "https://pawkey-server.com/image/walk2.jpg" - ), - categorySummary = listOf("안전", "편리성"), - content = "산책로가 깨끗하고 벚꽃이 예뻐요!", - onImageClick = {} - ) - } -} diff --git a/app/src/main/java/com/paw/key/core/designsystem/theme/Type.kt b/app/src/main/java/com/paw/key/core/designsystem/theme/Type.kt index faa757bd..f7fedff7 100644 --- a/app/src/main/java/com/paw/key/core/designsystem/theme/Type.kt +++ b/app/src/main/java/com/paw/key/core/designsystem/theme/Type.kt @@ -446,8 +446,8 @@ fun pawKeyTypography(): PawKeyTypography { letterSpacing = 0.em ), subButtonActive = pawKeyTextStyle( - fontFamily = PretendardSemiBold, - fontWeight = FontWeight.SemiBold, + fontFamily = PretendardMedium, + fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.em diff --git a/app/src/main/java/com/paw/key/core/extension/FlowExt.kt b/app/src/main/java/com/paw/key/core/extension/FlowExt.kt new file mode 100644 index 00000000..c862fbc8 --- /dev/null +++ b/app/src/main/java/com/paw/key/core/extension/FlowExt.kt @@ -0,0 +1,47 @@ +package com.paw.key.core.extension + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.flowWithLifecycle +import kotlinx.coroutines.flow.Flow + +@Composable +fun Flow.collectSideEffect( + lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + minActiveState: Lifecycle.State = Lifecycle.State.STARTED, + action: suspend (T) -> Unit +) { + val lifecycle = lifecycleOwner.lifecycle + + LaunchedEffect(this, lifecycle) { + flowWithLifecycle(lifecycle, minActiveState) + .collect(action) + } +} + +@Composable +fun Flow.collectSingleEvent( + lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + throttleTime: Long = 500L, + action: suspend (T) -> Unit +) { + val lifecycle = lifecycleOwner.lifecycle + var lastEmitTime by remember { mutableLongStateOf(0L) } + + LaunchedEffect(this, lifecycle) { + flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { value -> + val currentTime = System.currentTimeMillis() + if (currentTime - lastEmitTime > throttleTime) { + lastEmitTime = currentTime + action(value) + } + } + } +} diff --git a/app/src/main/java/com/paw/key/core/extension/IntExt.kt b/app/src/main/java/com/paw/key/core/extension/IntExt.kt new file mode 100644 index 00000000..a0091c41 --- /dev/null +++ b/app/src/main/java/com/paw/key/core/extension/IntExt.kt @@ -0,0 +1,8 @@ +package com.paw.key.core.extension + +fun Int.toTimeFormat(): String { + val hours = this / 3600 + val minutes = (this % 3600) / 60 + val seconds = this % 60 + return "%02d:%02d:%02d".format(hours, minutes, seconds) +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/core/extension/StateFlowExt.kt b/app/src/main/java/com/paw/key/core/extension/StateFlowExt.kt new file mode 100644 index 00000000..ec6fae7d --- /dev/null +++ b/app/src/main/java/com/paw/key/core/extension/StateFlowExt.kt @@ -0,0 +1,17 @@ +package com.paw.key.core.extension + +import com.paw.key.core.util.UiState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +inline fun MutableStateFlow>.updateSuccess( + crossinline onUpdate: (T) -> T +) { + update { currentState -> + if (currentState is UiState.Success) { + currentState.copy(data = onUpdate(currentState.data)) + } else { + currentState + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/core/model/Token.kt b/app/src/main/java/com/paw/key/core/model/Token.kt new file mode 100644 index 00000000..41275fd6 --- /dev/null +++ b/app/src/main/java/com/paw/key/core/model/Token.kt @@ -0,0 +1,7 @@ +package com.paw.key.core.model + +@JvmInline +value class AccessToken(val value: String) + +@JvmInline +value class RefreshToken(val value: String) diff --git a/app/src/main/java/com/paw/key/core/model/WalkingRouteUiModel.kt b/app/src/main/java/com/paw/key/core/model/WalkingRouteUiModel.kt index 5395ffbf..68757b00 100644 --- a/app/src/main/java/com/paw/key/core/model/WalkingRouteUiModel.kt +++ b/app/src/main/java/com/paw/key/core/model/WalkingRouteUiModel.kt @@ -1,61 +1,46 @@ package com.paw.key.core.model +import com.paw.key.domain.entity.posts.PostEntity + data class WalkingRouteUiModel( - val id: Int, + val postId: Int, + val regionName: String, val title: String, - val distance: String, - val time: String, val date: String, - val imageUri: String, - val location: String + val duration: Int, + val isLiked: Boolean, + val imageUrl: String? ) { companion object { val Fake = listOf( WalkingRouteUiModel( - id = 1, - title = "한강 반포공원 코스", - distance = "3.2km", - time = "45", - date = "25/11/06", - imageUri = "https://picsum.photos/300/400?random=1", - location = "서울 서초구" - ), - WalkingRouteUiModel( - id = 2, - title = "북서울 꿈의숲 코스", - distance = "2.8km", - time = "38", - date = "25/11/06", - imageUri = "https://picsum.photos/300/400?random=2", - location = "서울 강북구" + postId = 1, + regionName = "강남구 역삼동", + title = "강남구 역삼동 산책", + date = "2023-07-01", + duration = 6, + isLiked = true, + imageUrl = "" ), WalkingRouteUiModel( - id = 3, - title = "성수동 카페거리 산책로", - distance = "1.6km", - time = "25", - date = "25/11/06", - imageUri = "https://picsum.photos/300/400?random=3", - location = "서울 성동구" + postId = 2, + regionName = "강남구 역삼동", + title = "강남구 역삼동 산책", + date = "2023-07-01", + duration = 6, + isLiked = true, + imageUrl = "" ), - WalkingRouteUiModel( - id = 4, - title = "올림픽공원 호수길", - distance = "4.5km", - time = "60", - date = "25/11/06", - imageUri = "https://picsum.photos/300/400?random=4", - location = "서울 송파구" - ), - WalkingRouteUiModel( - id = 5, - title = "남산 둘레길", - distance = "5.2km", - time = "75", - date = "25/11/06", - imageUri = "https://picsum.photos/300/400?random=5", - location = "서울 중구" - ) ) } -} \ No newline at end of file +} + +fun PostEntity.toUiModel() = WalkingRouteUiModel( + postId = postId, + regionName = regionName, + title = title, + date = date.split("T").first().replace("-", "/"), + duration = durationMinutes, + isLiked = isLiked, + imageUrl = imageUrl, +) diff --git a/app/src/main/java/com/paw/key/core/util/file/ImageUriManager.kt b/app/src/main/java/com/paw/key/core/util/file/ImageUriManager.kt new file mode 100644 index 00000000..4674851e --- /dev/null +++ b/app/src/main/java/com/paw/key/core/util/file/ImageUriManager.kt @@ -0,0 +1,33 @@ +package com.paw.key.core.util.file + +import android.content.Context +import androidx.core.content.FileProvider +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.io.File +import java.io.IOException +import javax.inject.Inject + +class ImageUriManager @Inject constructor( + @param:ApplicationContext private val context: Context +) { + suspend fun createTempImageUri(): String? = withContext(Dispatchers.IO) { + try { + val directory = File(context.cacheDir, "images").apply { + if (!exists()) mkdirs() + } + val file = File.createTempFile("IMG_", ".jpg", directory) + + FileProvider.getUriForFile( + context, + "${context.packageName}.fileprovider", + file + ).toString() + } catch (e: IOException) { + Timber.e(e, "파일 생성 실패") + null + } + } +} diff --git a/app/src/main/java/com/paw/key/data/di/NetworkModule.kt b/app/src/main/java/com/paw/key/data/di/NetworkModule.kt index 7a79f1ed..07ee6ea8 100644 --- a/app/src/main/java/com/paw/key/data/di/NetworkModule.kt +++ b/app/src/main/java/com/paw/key/data/di/NetworkModule.kt @@ -2,6 +2,7 @@ package com.paw.key.data.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.paw.key.BuildConfig +import com.paw.key.data.network.TokenAuthenticator import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -13,6 +14,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter import retrofit2.Retrofit import java.util.concurrent.TimeUnit +import javax.inject.Named import javax.inject.Singleton @Module @@ -33,17 +35,29 @@ object NetworkModule { @Singleton fun providesOkHttpClient( loggingInterceptor: HttpLoggingInterceptor, - authInterceptor: AuthInterceptor + authInterceptor: AuthInterceptor, + tokenAuthenticator: TokenAuthenticator ): OkHttpClient = OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(authInterceptor) .addInterceptor(loggingInterceptor) + .authenticator(tokenAuthenticator) .build() @Provides @Singleton - fun providesConverterFactory(): Converter.Factory = Json.asConverterFactory("application/json".toMediaType()) + fun providesJson(): Json = Json { + ignoreUnknownKeys = true + coerceInputValues = true + isLenient = true + encodeDefaults = true + } + + @Provides + @Singleton + fun providesConverterFactory(json: Json): Converter.Factory = + json.asConverterFactory("application/json".toMediaType()) @Provides @Singleton @@ -51,8 +65,55 @@ object NetworkModule { client: OkHttpClient, converterFactory: Converter.Factory ): Retrofit = Retrofit.Builder() - .baseUrl(BuildConfig.BASE_URL) + .baseUrl(if (BuildConfig.DEBUG) BuildConfig.DEBUG_BASE_URL else BuildConfig.BASE_URL) //BuildConfig.BASE_URL) + .addConverterFactory(converterFactory) + .client(client) + .build() + + + @Provides + @Singleton + @Named("s3") + fun provideS3OkHttpClient( + loggingInterceptor: HttpLoggingInterceptor + ): OkHttpClient = OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(loggingInterceptor) + .build() + + @Provides + @Singleton + @Named("s3") + fun provideS3Retrofit( + @Named("s3") client: OkHttpClient + ): Retrofit = Retrofit.Builder() + .baseUrl("https://s3.amazonaws.com/") + .client(client) + .build() + + + + @Provides + @Singleton + @Named("auth") + fun provideAuthOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor + ): OkHttpClient = OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(loggingInterceptor) + .build() + + @Provides + @Singleton + @Named("auth") + fun provideAuthRetrofit( + @Named("auth") client: OkHttpClient, + converterFactory: Converter.Factory + ): Retrofit = Retrofit.Builder() + .baseUrl(if (BuildConfig.DEBUG) BuildConfig.DEBUG_BASE_URL else BuildConfig.BASE_URL) .addConverterFactory(converterFactory) .client(client) .build() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt index f7115e10..b598a6f0 100644 --- a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt @@ -1,7 +1,5 @@ package com.paw.key.data.di -import com.paw.key.domain.repository.localstorage.LocalStorageRepository -import com.paw.key.data.repositoryimpl.localstorage.LocalStorageRepositoryImpl import com.paw.key.data.remote.datasource.datasourceimpl.AuthRemoteDataSourceImpl import com.paw.key.data.remote.datasource.datasourceimpl.GoogleAuthDataSourceImpl import com.paw.key.data.remote.datasource.datasourceimpl.KakaoAuthDataSourceImpl @@ -10,40 +8,32 @@ import com.paw.key.data.remote.datasource.login.GoogleAuthDataSource import com.paw.key.data.remote.datasource.login.KakaoAuthDataSource import com.paw.key.data.repositoryimpl.ArchivedListRepositoryImpl import com.paw.key.data.repositoryimpl.LikeRepositoryImpl -import com.paw.key.data.repositoryimpl.PetProfileRepositoryImpl import com.paw.key.data.repositoryimpl.RegionRepositoryImpl import com.paw.key.data.repositoryimpl.SavedListRepositoryImpl -import com.paw.key.data.repositoryimpl.UserProfileRepositoryImpl -import com.paw.key.data.repositoryimpl.WalkCourseRepositoryImpl import com.paw.key.data.repositoryimpl.WalkSharedResultRepositoryImpl -import com.paw.key.data.repositoryimpl.filter.FilterOptionRepositoryImpl -import com.paw.key.data.repositoryimpl.home.HomeRegionRepositoryImpl +import com.paw.key.data.repositoryimpl.home.HomeRepositoryImpl import com.paw.key.data.repositoryimpl.home.RegionCurrentRepositoryImpl import com.paw.key.data.repositoryimpl.image.ImageRepositoryImpl -import com.paw.key.data.repositoryimpl.list.PostsListRepositoryImpl +import com.paw.key.data.repositoryimpl.localstorage.LocalStorageRepositoryImpl import com.paw.key.data.repositoryimpl.login.AuthRepositoryImpl -import com.paw.key.data.repositoryimpl.sharedwalk.SharedWalkRepositoryImpl +import com.paw.key.data.repositoryimpl.posts.PostsRepositoryImpl import com.paw.key.data.repositoryimpl.user.UserRepositoryImpl -import com.paw.key.data.repositoryimpl.walklist.WalkListDetailRepositoryImpl -import com.paw.key.data.repositoryimpl.walkreview.WalkReviewRepositoryImpl +import com.paw.key.data.repositoryimpl.walk.WalkRepositoryImpl +import com.paw.key.data.repositoryimpl.walkpreparation.WalkPreparationRepositoryImpl import com.paw.key.domain.repository.ArchivedListRepository import com.paw.key.domain.repository.LikeRepository import com.paw.key.domain.repository.RegionRepository import com.paw.key.domain.repository.SavedListRepository import com.paw.key.domain.repository.WalkSharedResultRepository -import com.paw.key.domain.repository.filter.FilterOptionRepository -import com.paw.key.domain.repository.home.HomeRegionRepository +import com.paw.key.domain.repository.home.HomeRepository import com.paw.key.domain.repository.home.RegionCurrentRepository import com.paw.key.domain.repository.image.ImageRepository -import com.paw.key.domain.repository.list.PostsListRepository +import com.paw.key.domain.repository.localstorage.LocalStorageRepository import com.paw.key.domain.repository.login.AuthRepository -import com.paw.key.domain.repository.petprofile.PetProfileRepository -import com.paw.key.domain.repository.sharedwalk.SharedWalkRepository +import com.paw.key.domain.repository.posts.PostsRepository import com.paw.key.domain.repository.user.UserRepository -import com.paw.key.domain.repository.userprofile.UserProfileRepository -import com.paw.key.domain.repository.walkcourse.WalkCourseRepository -import com.paw.key.domain.repository.walklist.WalkListRepository -import com.paw.key.domain.repository.walkreview.WalkReviewRepository +import com.paw.key.domain.repository.walk.WalkRepository +import com.paw.key.domain.repository.walkpreparation.WalkPreparationRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -84,44 +74,19 @@ interface RepositoryModule { regionRepositoryImpl: RegionRepositoryImpl ): RegionRepository - @Binds - @Singleton - fun bindsWalkCourseRepository( - walkCourseRepositoryImpl: WalkCourseRepositoryImpl - ): WalkCourseRepository - @Binds @Singleton fun bindsUserRepository( impl: UserRepositoryImpl ): UserRepository - /*공유 코스*/ @Binds @Singleton - fun bindsSharedWalkRepository( - impl: SharedWalkRepositoryImpl - ) : SharedWalkRepository - - @Binds - @Singleton - fun bindHomeRegionRepository( - impl: HomeRegionRepositoryImpl - ): HomeRegionRepository + fun bindHomeRepository( + impl: HomeRepositoryImpl + ): HomeRepository //마이페이지 - @Binds - @Singleton - fun bindUserProfileRepository( - impl: UserProfileRepositoryImpl - ): UserProfileRepository - - @Binds - @Singleton - fun bindPetProfileRepository( - impl: PetProfileRepositoryImpl - ): PetProfileRepository - @Binds @Singleton fun bindSavedListRepository( @@ -140,31 +105,13 @@ interface RepositoryModule { impl: LikeRepositoryImpl ): LikeRepository - @Binds - @Singleton - fun bindWalkReviewRepository( - impl: WalkReviewRepositoryImpl - ) : WalkReviewRepository - - // 리뷰 - @Binds - @Singleton - fun bindWalkListDetailRepository( - impl: WalkListDetailRepositoryImpl - ) : WalkListRepository - - @Binds - @Singleton - fun bindFilterOptionRepository( - impl: FilterOptionRepositoryImpl - ) : FilterOptionRepository //게시물 리스트 @Binds @Singleton - fun bindPostsListRepository( - impl: PostsListRepositoryImpl - ) : PostsListRepository + fun bindPostsRepository( + impl: PostsRepositoryImpl + ) : PostsRepository @Binds @Singleton @@ -186,7 +133,19 @@ interface RepositoryModule { @Binds @Singleton - abstract fun bindLocalStorageRepository( + fun bindLocalStorageRepository( impl: LocalStorageRepositoryImpl ): LocalStorageRepository -} \ No newline at end of file + + @Binds + @Singleton + fun bindWalkListRepository( + impl: WalkPreparationRepositoryImpl + ) : WalkPreparationRepository + + @Binds + @Singleton + fun bindWalkRepository( + impl: WalkRepositoryImpl + ) : WalkRepository +} diff --git a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt index f65ecd71..ec824e92 100644 --- a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt +++ b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt @@ -2,26 +2,25 @@ package com.paw.key.data.di import com.paw.key.data.service.ArchivedListService import com.paw.key.data.service.LikeService -import com.paw.key.data.service.PetProfileService -import com.paw.key.data.service.region.RegionService import com.paw.key.data.service.SavedListService -import com.paw.key.data.service.UserProfileService -import com.paw.key.data.service.filter.FilterOptionService +import com.paw.key.data.service.auth.ReissueService import com.paw.key.data.service.home.HomeRegionService import com.paw.key.data.service.image.ImageService -import com.paw.key.data.service.list.PostsListService +import com.paw.key.data.service.image.S3Service import com.paw.key.data.service.login.LoginService +import com.paw.key.data.service.posts.PostsService +import com.paw.key.data.service.region.RegionService import com.paw.key.data.service.sharedwalk.SharedWalkService import com.paw.key.data.service.user.UserService -import com.paw.key.data.service.walkcourse.WalkCourseService -import com.paw.key.data.service.walklist.WalkListDetailService -import com.paw.key.data.service.walkreview.WalkReviewService +import com.paw.key.data.service.walk.WalkService +import com.paw.key.data.service.walkpreparation.WalkPreparationService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import retrofit2.Retrofit import retrofit2.create +import javax.inject.Named import javax.inject.Singleton @Module @@ -33,11 +32,6 @@ object ServiceModule { fun providesRegionService(retrofit: Retrofit ): RegionService = retrofit.create() - @Provides - @Singleton - fun providesWalkCourseService(retrofit: Retrofit ): WalkCourseService = - retrofit.create() - @Provides @Singleton fun provideUserInfoService(retrofit: Retrofit): UserService = @@ -56,57 +50,51 @@ object ServiceModule { //마이페이지 @Provides @Singleton - fun provideUserProfileService(retrofit: Retrofit): UserProfileService = - retrofit.create() - - @Provides - @Singleton - fun providePetProfileService(retrofit: Retrofit): PetProfileService = + fun provideSavedListService(retrofit: Retrofit): SavedListService = retrofit.create() @Provides @Singleton - fun provideSavedListService(retrofit: Retrofit): SavedListService = + fun provideArchivedListService(retrofit: Retrofit): ArchivedListService = retrofit.create() @Provides @Singleton - fun provideArchivedListService(retrofit: Retrofit): ArchivedListService = + fun provideLikeService(retrofit: Retrofit): LikeService = retrofit.create() @Provides @Singleton - fun provideLikeService(retrofit: Retrofit): LikeService = + fun provideLoginService(retrofit: Retrofit): LoginService = retrofit.create() @Provides @Singleton - fun provideWalkReviewService(retrofit: Retrofit): WalkReviewService = + fun provideImageService(retrofit: Retrofit): ImageService = retrofit.create() - // 리뷰 @Provides @Singleton - fun provideWalkListDetailService(retrofit: Retrofit): WalkListDetailService = + fun provideImageS3Service(@Named("s3") retrofit: Retrofit): S3Service = retrofit.create() @Provides @Singleton - fun provideFilterOptionService(retrofit: Retrofit): FilterOptionService = + fun provideWalkPreparationService(retrofit: Retrofit): WalkPreparationService = retrofit.create() @Provides @Singleton - fun providePostsListService(retrofit: Retrofit): PostsListService = + fun provideWalkService(retrofit: Retrofit): WalkService = retrofit.create() @Provides @Singleton - fun provideLoginService(retrofit: Retrofit): LoginService = + fun provideReissueService(@Named("auth") retrofit: Retrofit): ReissueService = retrofit.create() @Provides @Singleton - fun provideImageService(retrofit: Retrofit): ImageService = + fun providePostsService(retrofit: Retrofit): PostsService = retrofit.create() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/data/di/TokenRefreshServiceModule.kt b/app/src/main/java/com/paw/key/data/di/TokenRefreshServiceModule.kt new file mode 100644 index 00000000..e9f7aa76 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/di/TokenRefreshServiceModule.kt @@ -0,0 +1,18 @@ +package com.paw.key.data.di + +import com.paw.key.data.network.TokenRefreshService +import com.paw.key.data.network.TokenRefreshServiceImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class TokenRefreshServiceModule { + + @Binds + @Singleton + abstract fun bindTokenRefreshService(tokenRefreshServiceImpl: TokenRefreshServiceImpl): TokenRefreshService +} diff --git a/app/src/main/java/com/paw/key/data/dto/request/.gitkeep b/app/src/main/java/com/paw/key/data/dto/request/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/java/com/paw/key/data/dto/request/auth/AuthReissueRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/auth/AuthReissueRequestDto.kt new file mode 100644 index 00000000..38fabcc1 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/auth/AuthReissueRequestDto.kt @@ -0,0 +1,12 @@ +package com.paw.key.data.dto.request.auth + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AuthReissueRequestDto( + @SerialName("refreshToken") + val refreshToken: String, + @SerialName("deviceId") + val deviceId: String +) diff --git a/app/src/main/java/com/paw/key/data/dto/request/list/PostsListRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/list/PostsListRequestDto.kt deleted file mode 100644 index 36c21033..00000000 --- a/app/src/main/java/com/paw/key/data/dto/request/list/PostsListRequestDto.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.paw.key.data.dto.request.list - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class PostsListRequestDto( - @SerialName("durationStart") - val durationStart: Int? = null, - @SerialName("durationEnd") - val durationEnd: Int? = null, - @SerialName("selectedOptions") - val selectedOptions: List? = null -) - -@Serializable -data class TraitList( - @SerialName("categoryId") - val categoryId: Int? = null, - @SerialName("optionsIds") - val optionIds: List? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/request/posts/PostsDataRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/posts/PostsDataRequestDto.kt new file mode 100644 index 00000000..ed67d844 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/posts/PostsDataRequestDto.kt @@ -0,0 +1,59 @@ +package com.paw.key.data.dto.request.posts + +import com.paw.key.domain.entity.posts.CategoryOptionEntity +import com.paw.key.domain.entity.posts.PostsInfoEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostsDataRequestDto( + @SerialName("title") + val title: String, + + @SerialName("description") + val description: String, + + @SerialName("isPublic") + val isPublic: Boolean, + + @SerialName("routeId") + val routeId: Int, + + @SerialName("routeImageId") + val routeImageId: Int, + + @SerialName("walkImageIds") + val walkImageIds: List, + + @SerialName("selectedOptionsForCategories") + val selectedOptionsForCategories: List, + + @SerialName("imageUrls") + val imageUrls: List +) + +fun PostsInfoEntity.toDto() = PostsDataRequestDto( + title = title, + description = description, + isPublic = isPublic, + routeId = routeId, + routeImageId = routeImageId, + walkImageIds = walkImageIds, + selectedOptionsForCategories = selectedOptionsForCategories.map { it.toDto() }, + imageUrls = imageUrls +) + +@Serializable +data class CategoryOptionDto( + @SerialName("categoryId") + val categoryId: Int, + + @SerialName("selectedOptionIds") + val selectedOptionIds: List +) + +fun CategoryOptionEntity.toDto() = CategoryOptionDto( + categoryId = categoryId, + selectedOptionIds = selectedOptionIds +) + diff --git a/app/src/main/java/com/paw/key/data/dto/request/posts/PostsFilterRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/posts/PostsFilterRequestDto.kt new file mode 100644 index 00000000..e5741a2c --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/posts/PostsFilterRequestDto.kt @@ -0,0 +1,34 @@ +package com.paw.key.data.dto.request.posts + +import com.paw.key.domain.entity.posts.FilterSelectedIOptionEntity +import com.paw.key.domain.entity.posts.FilterSelectedItemEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostsFilterRequestDto( + @SerialName("selectedOptions") + val selectedOptions: List +) + +fun FilterSelectedItemEntity.toDto() = PostsFilterRequestDto( + selectedOptions = selectedOptions.map { it.toDto() } +) + + +@Serializable +data class FilterOptionRequestDto( + @SerialName("categoryId") + val categoryId: Int?, // 카테고리 id + @SerialName("durationId") + val durationId: Int?, // 소요 시간 카테고리 id + @SerialName("optionsIds") + val optionsIds: List // 선택한 옵션 id 리스트 +) + +fun FilterSelectedIOptionEntity.toDto() = FilterOptionRequestDto( + categoryId = categoryId, + durationId = durationId, + optionsIds = optionsIds +) + diff --git a/app/src/main/java/com/paw/key/data/dto/request/user/UserWithDrawRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/user/UserWithDrawRequestDto.kt new file mode 100644 index 00000000..7304eec1 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/user/UserWithDrawRequestDto.kt @@ -0,0 +1,10 @@ +package com.paw.key.data.dto.request.user + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserWithDrawRequestDto( + @SerialName("provider") + val provider: String +) diff --git a/app/src/main/java/com/paw/key/data/dto/request/walk/WalkFinishRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/walk/WalkFinishRequestDto.kt new file mode 100644 index 00000000..9496fcb4 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/walk/WalkFinishRequestDto.kt @@ -0,0 +1,24 @@ +package com.paw.key.data.dto.request.walk + +import com.paw.key.domain.entity.walk.WalkFinish +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkFinishRequestDto( + @SerialName("distance") + val distance: Int, + @SerialName("duration") + val duration: Int, + @SerialName("stepCount") + val stepCount: Int, + @SerialName("endedAt") + val endedAt: String +) + +fun WalkFinish.toDto() = WalkFinishRequestDto( + distance = distance, + duration = duration, + stepCount = stepCount, + endedAt = endedAt +) diff --git a/app/src/main/java/com/paw/key/data/dto/request/walk/WalkPointRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/walk/WalkPointRequestDto.kt new file mode 100644 index 00000000..8b934789 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/walk/WalkPointRequestDto.kt @@ -0,0 +1,24 @@ +package com.paw.key.data.dto.request.walk + +import com.google.gson.annotations.SerializedName +import com.paw.key.domain.entity.walk.WalkPoint +import kotlinx.serialization.Serializable + +@Serializable +data class WalkPointRequestDto( + @SerializedName("routeId") + val routeId: String, + @SerializedName("lat") + val lat: Double, + @SerializedName("lng") + val lng: Double, + @SerializedName("timestamp") + val timestamp: Int +) + +fun WalkPoint.toDto() = WalkPointRequestDto( + routeId = routeId, + lat = lat, + lng = lng, + timestamp = timestamp +) diff --git a/app/src/main/java/com/paw/key/data/dto/request/walk/WalkStartRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/walk/WalkStartRequestDto.kt new file mode 100644 index 00000000..39856821 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/walk/WalkStartRequestDto.kt @@ -0,0 +1,10 @@ +package com.paw.key.data.dto.request.walk + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkStartRequestDto( + @SerialName("deviceInfo") + val deviceInfo: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/request/walkcourse/WalkCourseRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/walkcourse/WalkCourseRequestDto.kt deleted file mode 100644 index ae9992e8..00000000 --- a/app/src/main/java/com/paw/key/data/dto/request/walkcourse/WalkCourseRequestDto.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.paw.key.data.dto.request.walkcourse - -import com.paw.key.domain.entity.walkcourse.CoordinateEntity -import com.paw.key.domain.entity.walkcourse.WalkCourseEntity -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CoordinateDto( - val longitude: Double, - val latitude: Double -) - -@Serializable -data class WalkCourseRequestDto( - @SerialName("coordinates") - val coordinates: List, - val distance: Int, - val duration: Int, - val startedAt: String, - val endedAt: String, - val stepCount: Int -) { - fun toEntity(): WalkCourseEntity { - return WalkCourseEntity( - coordinates = coordinates.map { CoordinateEntity(it.latitude, it.longitude) }, - distance = distance, - duration = duration, - startedAt = startedAt, - endedAt = endedAt, - stepCount = stepCount - ) - } -} - diff --git a/app/src/main/java/com/paw/key/data/dto/request/walkpreparation/WalkPreparationRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/walkpreparation/WalkPreparationRequestDto.kt new file mode 100644 index 00000000..0c6755e0 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/walkpreparation/WalkPreparationRequestDto.kt @@ -0,0 +1,19 @@ +package com.paw.key.data.dto.request.walkpreparation + +import com.paw.key.domain.entity.walkpreparation.WalkPreparationEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkPreparationRequestDto( + @SerialName("preparation") + val preparationList: List +) { + fun toEntity() = WalkPreparationEntity( + preparationList = preparationList + ) +} + +fun WalkPreparationEntity.toDto() = WalkPreparationRequestDto( + preparationList = preparationList +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/request/walkreview/WalkCourseReviewRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/walkreview/WalkCourseReviewRequestDto.kt deleted file mode 100644 index 9c832d8a..00000000 --- a/app/src/main/java/com/paw/key/data/dto/request/walkreview/WalkCourseReviewRequestDto.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.paw.key.data.dto.request.walkreview - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - - -@Serializable -data class WalkCourseReviewRequestDto( - @SerialName("title") - val title: String, - - @SerialName("description") - val description: String, - - @SerialName("isPublic") - val isPublic: Boolean, - - @SerialName("isMine") - val isMine: Boolean, - - @SerialName("selectedOptionsForCategories") - val selectedCategories: List, - - @SerialName("routeId") - val routeId: Long -) - -@Serializable -data class SelectedCategoryDto( - @SerialName("categoryId") - val categoryId: Int, - - @SerialName("selectedOptionIds") - val selectedOptionIds: List -) diff --git a/app/src/main/java/com/paw/key/data/dto/response/DummyBaseResponse.kt b/app/src/main/java/com/paw/key/data/dto/response/DummyBaseResponse.kt deleted file mode 100644 index b45345c5..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/DummyBaseResponse.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.paw.key.data.dto.response - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class DummyBaseResponse( - @SerialName("data") - val data: List, - @SerialName("page") - val page: Int, - @SerialName("per_page") - val perPage: Int, - @SerialName("support") - val support: Support, - @SerialName("total") - val total: Int, - @SerialName("total_pages") - val totalPages: Int -) { - @Serializable - data class Support( - @SerialName("text") - val text: String, - @SerialName("url") - val url: String, - ) -} diff --git a/app/src/main/java/com/paw/key/data/dto/response/DummyResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/DummyResponseDto.kt deleted file mode 100644 index 3f46d43e..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/DummyResponseDto.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.paw.key.data.dto.response - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class DummyResponseDto( - @SerialName("avatar") - val avatar: String, - @SerialName("email") - val email: String, - @SerialName("first_name") - val firstName: String, - @SerialName("id") - val id: Int, - @SerialName("last_name") - val lastName: String, -) - diff --git a/app/src/main/java/com/paw/key/data/dto/response/auth/AuthReissueResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/auth/AuthReissueResponseDto.kt new file mode 100644 index 00000000..c43d5da8 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/auth/AuthReissueResponseDto.kt @@ -0,0 +1,12 @@ +package com.paw.key.data.dto.response.auth + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AuthReissueResponseDto( + @SerialName("refreshToken") + val refreshToken: String, + @SerialName("accessToken") + val accessToken: String, +) diff --git a/app/src/main/java/com/paw/key/data/dto/response/filter/FilterOptionResponse.kt b/app/src/main/java/com/paw/key/data/dto/response/filter/FilterOptionResponse.kt deleted file mode 100644 index 8a90f83e..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/filter/FilterOptionResponse.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.paw.key.data.dto.response.filter - -import com.paw.key.domain.entity.filter.Category -import com.paw.key.domain.entity.filter.CategoryOption -import com.paw.key.domain.entity.filter.FilterEntity -import com.paw.key.domain.entity.filter.SelectOption -import com.paw.key.domain.entity.filter.SelectOptionItem -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class FilterOptionResponse( - @SerialName("selectList") - val selectList: List, - @SerialName("categoryList") - val categoryList: List -) { - fun toEntity(): FilterEntity { - return FilterEntity( - selectList = selectList.map { it.toEntity() }, - categoryList = categoryList.map { it.toEntity() } - ) - } -} - -@Serializable -data class SelectDto( - @SerialName("selectId") - val selectId: Int? = null, - @SerialName("selectName") - val selectName: String? = null, - @SerialName("options") - val options: List? = null -) { - fun toEntity(): SelectOption { - return SelectOption( - selectId = selectId ?: 0, - selectName = selectName ?: "", - options = options?.map { it.toEntity() } ?: emptyList(), - ) - } -} - -@Serializable -data class OptionDto( - @SerialName("selectOptionId") - val selectOptionId: Int? = null, - @SerialName("selectText") - val selectText: String? = null -) { - fun toEntity(): SelectOptionItem { - return SelectOptionItem( - selectOptionId = selectOptionId ?: 0, - selectText = selectText ?: "" - ) - } -} - -@Serializable -data class CategoryDto( - @SerialName("categoryId") - val categoryId: Int? = null, - @SerialName("categoryDescription") - val categoryDescription: String? = null, - @SerialName("categoryName") - val categoryName: String? = null, - @SerialName("options") - val categoryOptions: List? = null - ) { - fun toEntity(): Category { - return Category( - categoryId = categoryId ?: 0, - categoryName = categoryName ?: "", - categoryDescription = categoryDescription ?: "", - categoryOptions = categoryOptions?.map { it.toEntity() } ?: emptyList() - ) - } -} - -@Serializable -data class CategoryOptionDto( - @SerialName("categoryOptionId") - val categoryOptionId: Int? = null, - @SerialName("optionText") - val categoryOptionText: String? = null -) { - fun toEntity(): CategoryOption { - return CategoryOption( - categoryOptionId = categoryOptionId ?: 0, - categoryOptionText = categoryOptionText ?: "" - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/home/HomeInfoResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/home/HomeInfoResponseDto.kt new file mode 100644 index 00000000..2d296ce6 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/home/HomeInfoResponseDto.kt @@ -0,0 +1,25 @@ +package com.paw.key.data.dto.response.home + +import com.paw.key.domain.entity.home.HomeInfoEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class HomeInfoResponseDto( + @SerialName("distance") + val distance: Double, + + @SerialName("totalTime") + val totalTime: Int, + + @SerialName("count") + val count: Int +) { + fun toEntity(): HomeInfoEntity { + return HomeInfoEntity( + distance = distance, + totalTime = totalTime, + count = count + ) + } +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/home/HomeRegionResponse.kt b/app/src/main/java/com/paw/key/data/dto/response/home/HomeRegionResponse.kt deleted file mode 100644 index 5a6c1cf3..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/home/HomeRegionResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paw.key.data.dto.response.home - -import kotlinx.serialization.Serializable - -@Serializable -data class HomeRegionResponse( - val code: String, - val message: String, - val data: T? = null -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/home/HomeRouteResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/home/HomeRouteResponseDto.kt new file mode 100644 index 00000000..5a162fe9 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/home/HomeRouteResponseDto.kt @@ -0,0 +1,62 @@ +package com.paw.key.data.dto.response.home + +import com.paw.key.domain.entity.home.HomeRouteEntity +import com.paw.key.domain.entity.home.RouteEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class HomeRouteResponseDto( + @SerialName("popularRoutes") + val popularRoutes: List, // 사용자 거주 지역 내 인기 리스트 + + @SerialName("similarUserRoutes") + val similarUserRoutes: List // 비슷한 이용자들이 선호하는 리스트 +) { + fun toEntity(): HomeRouteEntity { + return HomeRouteEntity( + popularRoutes = popularRoutes.map { it.toEntity() }, + similarUserRoutes = similarUserRoutes.map { it.toEntity() } + ) + } +} + +@Serializable +data class RouteDto( + @SerialName("routeId") + val routeId: Long, // 산책 루트의 고유 ID + + @SerialName("postId") + val postId: Long, // 게시물 고유 Id + + @SerialName("regionName") + val regionName: String, // 루트가 속한 지역명 (구, 동 등) + + @SerialName("title") + val title: String, // 루트에 대한 게시물의 제목 + + @SerialName("date") + val date: String, // 게시글의 마지막 기록 날짜 + + @SerialName("duration") + val duration: Int, // 총 산책 소요 시간 + + @SerialName("isLiked") + val isLiked: Boolean, // 현재 사용자가 해당 루트에 좋아요를 눌렀는지 여부 + + @SerialName("imageUrl") + val imageUrl: String // 루트 대표 이미지의 URL (CDN) +) { + fun toEntity(): RouteEntity { + return RouteEntity( + routeId = routeId, + postId = postId, + regionName = regionName, + title = title, + date = date, + duration = duration, + isLiked = isLiked, + imageUrl = imageUrl + ) + } +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/home/HomeWeatherResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/home/HomeWeatherResponseDto.kt new file mode 100644 index 00000000..b6e32f95 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/home/HomeWeatherResponseDto.kt @@ -0,0 +1,25 @@ +package com.paw.key.data.dto.response.home + +import com.paw.key.domain.entity.home.HomeWeatherEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class HomeWeatherResponseDto( + @SerialName("temperature") + val temperature: Int, // 온도 정보 + + @SerialName("rainyMm") + val rainyMm: Int, // 강수량 + + @SerialName("region") + val region: String // 유저의 설정 지역 +) { + fun toEntity(): HomeWeatherEntity { + return HomeWeatherEntity( + temperature = temperature, + rainyMm = rainyMm, + region = region + ) + } +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/list/PostsListResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/list/PostsListResponseDto.kt deleted file mode 100644 index e062d822..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/list/PostsListResponseDto.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.paw.key.data.dto.response.list - -import com.paw.key.domain.entity.list.ListEntity -import com.paw.key.domain.entity.list.PostEntity -import com.paw.key.domain.entity.list.WriterEntity -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class PostsListResponseDto ( - @SerialName("posts") - val posts : List -) - -@Serializable -data class PostDto ( - @SerialName("postId") - val postId : Int, - @SerialName("createdAt") - val createdAt : String, - @SerialName("isLike") - val isLike : Boolean, - @SerialName("title") - val title : String, - @SerialName("isMine") - val isMine : Boolean, - @SerialName("isPublic") - val isPublic : Boolean, - @SerialName("representativeImageUrl") - val representativeImageUrl : String, - @SerialName("routeId") - val routeId : Int, - @SerialName("writer") - val writer : WriterDto, - @SerialName("descriptionTags") - val descriptionTags : List -) - -@Serializable -data class WriterDto ( - @SerialName("userId") - val userId : Int, - @SerialName("petName") - val petName : String, - @SerialName("petProfileImageUrl") - val petProfileImageUrl : String, -) - -fun PostsListResponseDto.toEntity(): ListEntity { - return ListEntity( - posts = posts.map { it.toEntity() } - ) -} - -fun PostDto.toEntity(): PostEntity { - return PostEntity( - postId = postId, - createdAt = createdAt, - isLike = isLike, - title = title, - representativeImageUrl = representativeImageUrl, - routeId = routeId, - isMine = isMine, - writer = writer.toEntity(), - isPublic = isPublic, - descriptionTags = descriptionTags - ) -} - -fun WriterDto.toEntity(): WriterEntity { - return WriterEntity( - userId = userId, - petName = petName, - petProfileImageUrl = petProfileImageUrl - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/petprofile/PetProfileResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/petprofile/PetProfileResponseDto.kt index 791c21af..0f5e02e0 100644 --- a/app/src/main/java/com/paw/key/data/dto/response/petprofile/PetProfileResponseDto.kt +++ b/app/src/main/java/com/paw/key/data/dto/response/petprofile/PetProfileResponseDto.kt @@ -1,8 +1,6 @@ package com.paw.key.data.dto.response.petprofile -import androidx.core.net.toUri import com.paw.key.domain.entity.petprofile.PetProfileEntity -import com.paw.key.domain.entity.petprofile.TraitEntity import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -10,58 +8,35 @@ import kotlinx.serialization.Serializable data class PetProfileResponseDto( @SerialName("petId") val petId: Long, - + @SerialName("imageUrl") + val imageUrl: String, @SerialName("name") val name: String, - + @SerialName("birth") + val birth: String, + @SerialName("age") + val age: String, @SerialName("gender") val gender: String, - @SerialName("isNeutered") val isNeutered: Boolean, - - @SerialName("age") - val age: Int, - - @SerialName("isAgeKnown") - val isAgeKnown: Boolean, - @SerialName("breed") val breed: String, - - @SerialName("imageUrl") - val imageUrl: String, - - @SerialName("traits") - val traits: List, - - @SerialName("walkCount") - val walkCount: Int, + @SerialName("dbtiName") + val dbtiName: String?, + @SerialName("dbtiDescription") + val dbtiDescription: String? ) { fun toEntity() = PetProfileEntity( petId = petId, + imageUrl = imageUrl, name = name, + birth = birth, + age = age, gender = gender, isNeutered = isNeutered, - age = age, - isAgeKnown = isAgeKnown, breed = breed, - imageUrl = imageUrl.toUri(), - walkCount = walkCount, - traits = traits.map { it.toEntity() } - ) -} - -@Serializable -data class TraitDto( - @SerialName("category") - val category: String, - - @SerialName("option") - val option: String, -) { - fun toEntity() = TraitEntity( - category = category, - option = option + dbtiName = dbtiName, + dbtiDescription = dbtiDescription ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/posts/PostDetailResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/posts/PostDetailResponseDto.kt new file mode 100644 index 00000000..3508da5a --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/posts/PostDetailResponseDto.kt @@ -0,0 +1,112 @@ +package com.paw.key.data.dto.response.posts + +import com.paw.key.domain.entity.posts.AuthorInfoEntity +import com.paw.key.domain.entity.posts.PostsDetailEntity +import com.paw.key.domain.entity.posts.RouteDisplayEntity +import com.paw.key.domain.entity.posts.WalkImageEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostDetailResponseDto( + @SerialName("postId") + val postId: Int, + + @SerialName("title") + val title: String, + + @SerialName("description") + val description: String, + + @SerialName("isPublic") + val isPublic: Boolean, + + @SerialName("isMine") + val isMine: Boolean, + + @SerialName("authorInfo") + val authorInfo: AuthorInfoDto, + + @SerialName("routeDisplay") + val routeDisplay: RouteDisplayDto, + + @SerialName("categoryTagTexts") + val categoryTagTexts: List, + + @SerialName("walkImages") + val walkImages: List +) { + fun toEntity() = PostsDetailEntity( + postId = postId, + title = title, + description = description, + isPublic = isPublic, + isMine = isMine, + authorInfo = authorInfo.toEntity(), + routeDisplay = routeDisplay.toEntity(), + categoryTagTexts = categoryTagTexts, + walkImages = walkImages.map { it.toEntity() } + ) +} + +@Serializable +data class AuthorInfoDto( + @SerialName("authorId") + val authorId: Int, + + @SerialName("petId") + val petId: Int, + + @SerialName("petName") + val petName: String, + + @SerialName("petProfileImage") + val petProfileImage: String +) { + fun toEntity() = AuthorInfoEntity( + authorId = authorId, + petId = petId, + petName = petName, + petProfileImage = petProfileImage + ) +} + +@Serializable +data class RouteDisplayDto( + @SerialName("routeId") + val routeId: Int, + + @SerialName("locationText") + val locationText: String, + + @SerialName("dateTimeText") + val dateTimeText: String, + + @SerialName("metaTagTexts") + val metaTagTexts: List, + + @SerialName("routeImageUrl") + val routeImageUrl: String +) { + fun toEntity() = RouteDisplayEntity( + routeId = routeId, + locationText = locationText, + dateTimeText = dateTimeText, + metaTagTexts = metaTagTexts, + routeImageUrl = routeImageUrl + ) +} + +@Serializable +data class WalkImageDto( + @SerialName("imageId") + val imageId: Int, + + @SerialName("imageUrl") + val imageUrl: String +) { + fun toEntity() = WalkImageEntity( + imageId = imageId, + imageUrl = imageUrl + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/posts/PostsCategoriesResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsCategoriesResponseDto.kt new file mode 100644 index 00000000..328c2c35 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsCategoriesResponseDto.kt @@ -0,0 +1,64 @@ +package com.paw.key.data.dto.response.posts + +import com.paw.key.domain.entity.posts.FilterItemEntity +import com.paw.key.domain.entity.posts.FilterOptionEntity +import com.paw.key.domain.entity.posts.PostsCategoryEntity +import com.paw.key.domain.entity.posts.PostsFilterEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlin.collections.map + +@Serializable +data class CategoryListResponseDto( + @SerialName("categoryList") + val categoryList: List +) { + fun toEntity() = PostsCategoryEntity( + categoryList = categoryList.map { it.toEntity() } + ) +} + +@Serializable +data class FilterOptionResponseDto( + @SerialName("durationList") + val durationList: List, + @SerialName("categoryList") + val categoryList: List +) { + fun toEntity() = PostsFilterEntity( + durationList = durationList.map { it.toEntity() }, + categoryList = categoryList.map { it.toEntity() } + ) +} + +@Serializable +data class FilterItemDto( + @SerialName("id") + val id: Int, + @SerialName("name") + val name: String, + @SerialName("selectionType") + val selectionType: String, + @SerialName("options") + val options: List +) { + fun toEntity() = FilterItemEntity( + id = id, + name = name, + selectionType = selectionType, + options = options.map { it.toEntity() } + ) +} + +@Serializable +data class FilterOptionDto( + @SerialName("id") + val id: Int, + @SerialName("text") + val text: String +) { + fun toEntity() = FilterOptionEntity( + id = id, + text = text + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/posts/PostsFilterListResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsFilterListResponseDto.kt new file mode 100644 index 00000000..44ab2d5e --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsFilterListResponseDto.kt @@ -0,0 +1,51 @@ +package com.paw.key.data.dto.response.posts + +import com.paw.key.domain.entity.posts.PostEntity +import com.paw.key.domain.entity.posts.PostsEntity +import com.paw.key.domain.entity.posts.PostsResultEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostsFilterListResponseDto( + @SerialName("posts") + val posts: List, + @SerialName("nextCursor") + val nextCursor: String?, + @SerialName("hasNext") + val hasNext: Boolean +) { + fun toEntity() = PostsEntity( + posts = posts.map { it.toEntity() }, + nextCursor = nextCursor, + hasNext = hasNext + ) +} + +@Serializable +data class PostDto( + @SerialName("postId") + val postId: Int, + @SerialName("regionName") + val regionName: String, + @SerialName("title") + val title: String, + @SerialName("date") + val date: String, + @SerialName("durationMinutes") + val durationMinutes: Int, + @SerialName("isLiked") + val isLiked: Boolean, + @SerialName("imageUrl") + val imageUrl: String? +) { + fun toEntity() = PostEntity( + postId = postId, + regionName = regionName, + title = title, + date = date, + durationMinutes = durationMinutes, + isLiked = isLiked, + imageUrl = imageUrl + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/posts/PostsResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsResponseDto.kt new file mode 100644 index 00000000..8fa75629 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsResponseDto.kt @@ -0,0 +1,19 @@ +package com.paw.key.data.dto.response.posts + +import com.paw.key.domain.entity.posts.PostsResultEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostsResponseDto( + @SerialName("postId") + val postId: Int, + + @SerialName("routeId") + val routeId: Int +) { + fun toEntity() = PostsResultEntity( + postId = postId, + routeId = routeId + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/posts/PostsTop3ReviewResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsTop3ReviewResponseDto.kt new file mode 100644 index 00000000..dc2164be --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/posts/PostsTop3ReviewResponseDto.kt @@ -0,0 +1,44 @@ +package com.paw.key.data.dto.response.posts + +import com.paw.key.domain.entity.posts.PostsTop3Entity +import com.paw.key.domain.entity.posts.ReviewOptionEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostsTop3ReviewResponseDto( + @SerialName("totalReviewCount") + val totalReviewCount: Int, + @SerialName("totalSelectionSum") + val totalSelectionSum: Int, + @SerialName("top3ReviewOptions") + val top3ReviewOptions: List +) { + fun toEntity() = PostsTop3Entity( + totalReviewCount = totalReviewCount, + totalSelectionSum = totalSelectionSum, + top3ReviewOptions = top3ReviewOptions.map { it.toEntity() } + ) +} + +@Serializable +data class ReviewOptionDto( + @SerialName("reviewOptionId") + val reviewOptionId: Int, + @SerialName("reviewOptionName") + val reviewOptionName: String, + @SerialName("selectedCount") + val selectedCount: Int, + @SerialName("percentage") + val percentage: Double, + @SerialName("rank") + val rank: Int +) { + fun toEntity() = ReviewOptionEntity( + reviewOptionId = reviewOptionId, + reviewOptionName = reviewOptionName, + selectedCount = selectedCount, + percentage = percentage, + rank = rank + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/region/RegionResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/region/RegionResponseDto.kt index 50c83d46..a85b6a0b 100644 --- a/app/src/main/java/com/paw/key/data/dto/response/region/RegionResponseDto.kt +++ b/app/src/main/java/com/paw/key/data/dto/response/region/RegionResponseDto.kt @@ -1,17 +1,27 @@ package com.paw.key.data.dto.response.region +import com.paw.key.domain.entity.region.GeometryEntity +import com.paw.key.domain.entity.region.RegionDataEntity import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class RegionResponseDto( + @SerialName("regionId") + val regionId: Int, @SerialName("regionName") val regionName: String, - @SerialName("preRegionName") - val preRegionName: String, - @SerialName("geometryDto") - val geometryDto: GeometryDto -) + @SerialName("geometry") + val geometry: GeometryDto +) { + fun toEntity(): RegionDataEntity { + return RegionDataEntity( + regionId = this.regionId, + regionName = this.regionName, + geometry = this.geometry.toEntity() + ) + } +} @Serializable data class GeometryDto( @@ -19,4 +29,18 @@ data class GeometryDto( val type: String, @SerialName("coordinates") val coordinates: List>>> -) +) { + fun toEntity(): GeometryEntity { + return GeometryEntity( + type = this.type, + coordinates = this.coordinates.map { polygon -> + polygon.map { ring -> + ring.map { point -> + // 서버에서 위도 경도 다름 + Pair(point[1], point[0]) + } + } + } + ) + } +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/sharedwalk/SharedWalkResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/sharedwalk/SharedWalkResponseDto.kt deleted file mode 100644 index 5d5c0489..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/sharedwalk/SharedWalkResponseDto.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.paw.key.data.dto.response.sharedwalk - -import com.paw.key.domain.entity.sharedwalk.GeometryEntity -import com.paw.key.domain.entity.sharedwalk.SharedWalkEntity -import kotlinx.serialization.Serializable - -@Serializable -data class SharedWalkResponseDto( - val routeId: Int, - val geometryDto: GeometryDto -) { - fun toEntity(): SharedWalkEntity { - return SharedWalkEntity( - routeId = this.routeId, - geometry = this.geometryDto.toEntity() - ) - } -} - -@Serializable -data class GeometryDto( - val type: String, - val coordinates: List> -) { - fun toEntity() : GeometryEntity { - return GeometryEntity( - type = this.type, - coordinates = this.coordinates - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/userprofile/UserProfileResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/userprofile/UserProfileResponseDto.kt index 884295b8..e6aff1dc 100644 --- a/app/src/main/java/com/paw/key/data/dto/response/userprofile/UserProfileResponseDto.kt +++ b/app/src/main/java/com/paw/key/data/dto/response/userprofile/UserProfileResponseDto.kt @@ -1,28 +1,27 @@ package com.paw.key.data.dto.response.userprofile -import com.paw.key.domain.model.entity.uerprofile.UserProfileEntity +import com.paw.key.domain.entity.userprofile.UserProfileEntity import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class UserProfileResponseDto( -@SerialName("name") + @SerialName("name") val name: String, - @SerialName("gender") - val gender: String, + @SerialName("email") + val email: String, - @SerialName("age") - val age: Int, + @SerialName("birth") + val birth: String?, - @SerialName("activeRegion") - val activeRegion: String -) -{ + @SerialName("gender") + val gender: String +) { fun toEntity() = UserProfileEntity( name = name, gender = gender, - age = age, - activeRegion = activeRegion + email = email, + birth = birth ) } diff --git a/app/src/main/java/com/paw/key/data/dto/response/walk/WalkCompleteResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walk/WalkCompleteResponseDto.kt new file mode 100644 index 00000000..90070a1f --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/walk/WalkCompleteResponseDto.kt @@ -0,0 +1,36 @@ +package com.paw.key.data.dto.response.walk + +import com.paw.key.domain.entity.walk.WalkCompleteEntity +import com.paw.key.domain.entity.walk.WalkCompleteGeometryEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkCompleteResponseDto( + @SerialName("routeId") + val routeId: String, + @SerialName("geometry") + val geometry: WalkCompleteGeometryDto +) { + fun toEntity(): WalkCompleteEntity { + return WalkCompleteEntity( + routeId = routeId, + geometry = geometry.toEntity() + ) + } +} + +@Serializable +data class WalkCompleteGeometryDto( + @SerialName("type") + val type: String, + @SerialName("coordinates") + val coordinates: List> +) { + fun toEntity(): WalkCompleteGeometryEntity { + return WalkCompleteGeometryEntity( + type = type, + coordinates = coordinates + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/walk/WalkFinishResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walk/WalkFinishResponseDto.kt new file mode 100644 index 00000000..4ff5d26e --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/walk/WalkFinishResponseDto.kt @@ -0,0 +1,47 @@ +package com.paw.key.data.dto.response.walk + +import com.paw.key.domain.entity.walk.WalkFinishEntity +import com.paw.key.domain.entity.walk.WalkInfoEntity +import com.paw.key.domain.entity.walk.WalkPetProfileEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkFinishResponseDto( + @SerialName("routeId") + val routeId: Int, + @SerialName("petProfile") + val petProfile: PetProfileResponseDto, + @SerialName("walkInfo") + val walkInfo: WalkInfoResponseDto +) { + fun toEntity() = WalkFinishEntity( + routeId = routeId, + petProfile = petProfile.toEntity(), + walkInfo = walkInfo.toEntity() + ) +} + +@Serializable +data class WalkInfoResponseDto( + @SerialName("startAt") + val startAt: String +) { + fun toEntity() = WalkInfoEntity( + startAt = startAt + ) +} + +@Serializable +data class PetProfileResponseDto( + @SerialName("name") + val petName: String, + @SerialName("imageUrl") + val petProfileImageUrl: String +) { + fun toEntity() = WalkPetProfileEntity( + petName = petName, + petProfileImageUrl = petProfileImageUrl + ) +} + diff --git a/app/src/main/java/com/paw/key/data/dto/response/walk/WalkStartResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walk/WalkStartResponseDto.kt new file mode 100644 index 00000000..c090b19c --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/walk/WalkStartResponseDto.kt @@ -0,0 +1,18 @@ +package com.paw.key.data.dto.response.walk + +import com.paw.key.domain.entity.walk.WalkStartEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkStartResponseDto( + @SerialName("routeId") + val routeId: String, + @SerialName("issuedAt") + val issuedAt: Long, +) { + fun toEntity() = WalkStartEntity( + routeId = routeId, + issuedAt = issuedAt, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/walkcourse/WalkCourseResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walkcourse/WalkCourseResponseDto.kt deleted file mode 100644 index 10cd790d..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/walkcourse/WalkCourseResponseDto.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.paw.key.data.dto.response.walkcourse - -import com.paw.key.domain.entity.walkcourse.WalkCourseRegionIdEntity -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class WalkCourseResponseDto( - @SerialName("routeId") - val routeId : Int -) { - fun toEntity(): WalkCourseRegionIdEntity { - return WalkCourseRegionIdEntity( - regionId = routeId - ) - } -} diff --git a/app/src/main/java/com/paw/key/data/dto/response/walklist/WalkReviewDetailResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walklist/WalkReviewDetailResponseDto.kt deleted file mode 100644 index 552db9ca..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/walklist/WalkReviewDetailResponseDto.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.paw.key.data.dto.response.walklist - -import com.paw.key.domain.entity.walklist.AuthorInfoEntity -import com.paw.key.domain.entity.walklist.CategoryTagsEntity -import com.paw.key.domain.entity.walklist.WalkListDetailEntity -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class WalkReviewDetailResponseDto( - @SerialName("postId") - val postId: Int, - @SerialName("routeId") - val routeId: Int, - @SerialName("title") - val title: String, - @SerialName("content") - val content: String, - @SerialName("isLike") - val isLike: Boolean, - @SerialName("authorInfo") - val authorInfo: AuthorInfoDto, - @SerialName("categoryTags") - val categoryTags: CategoryTagsDto, - @SerialName("regionName") - val regionName: String, - @SerialName("createdAt") - val createdAt: String, - @SerialName("routeMapImageUrl") - val routeMapImageUrl: String, - @SerialName("walkingImageUrls") - val walkingImageUrls: List -) { - fun toEntity(): WalkListDetailEntity { - return WalkListDetailEntity( - postId = postId, - routeId = routeId, - title = title, - content = content, - isLike = isLike, - authorInfo = authorInfo.toEntity(), - categoryTags = categoryTags.toEntity(), - regionName = regionName, - createdAt = createdAt, - routeMapImageUrl = routeMapImageUrl, - walkingImageUrls = walkingImageUrls - ) - } -} - -@Serializable -data class AuthorInfoDto( - @SerialName("authorId") - val authorId: Int, - @SerialName("petId") - val petId: Int, - @SerialName("petName") - val petName: String, - @SerialName("petProfileImage") - val petProfileImage: String -) { - fun toEntity(): AuthorInfoEntity { - return AuthorInfoEntity( - authorId = authorId, - petId = petId, - petName = petName, - petProfileImage = petProfileImage - ) - } -} - -@Serializable -data class CategoryTagsDto( - @SerialName("categoryOptionSummary") - val categoryOptionSummary: List -) { - fun toEntity(): CategoryTagsEntity { - return CategoryTagsEntity( - categoryOptionSummary = categoryOptionSummary - ) - } -} diff --git a/app/src/main/java/com/paw/key/data/dto/response/walklist/WalkReviewSummaryResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walklist/WalkReviewSummaryResponseDto.kt deleted file mode 100644 index 77a9fe13..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/walklist/WalkReviewSummaryResponseDto.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.paw.key.data.dto.response.walklist - -import com.paw.key.domain.entity.walklist.CategoryTop3Entity -import com.paw.key.domain.entity.walklist.WalkReviewSummaryEntity -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class WalkReviewSummaryResponseDto( - @SerialName("postId") - val postId: Int, - - @SerialName("totalReviewCount") - val totalReviewCount: Int, - - @SerialName("categoryTop3") - val categoryTop3: List -) { - fun toEntity(): WalkReviewSummaryEntity { - return WalkReviewSummaryEntity( - postId = postId, - totalReviewCount = totalReviewCount, - categoryTop3 = categoryTop3.map { it.toEntity() }, - ) - } -} - -@Serializable -data class CategoryTop3ResponseDto( - @SerialName("categoryId") - val categoryId: Int, - - @SerialName("categoryName") - val categoryName: String, - - @SerialName("categoryOptionId") - val categoryOptionId: Int, - - @SerialName("optionText") - val optionText: String, - - @SerialName("rank") - val rank: Int, - - @SerialName("percentage") - val percentage: Int -) { - fun toEntity(): CategoryTop3Entity { - return CategoryTop3Entity( - categoryId = categoryId, - categoryName = categoryName, - categoryOptionId = categoryOptionId, - optionText = optionText, - rank = rank, - percentage = percentage - ) - } -} diff --git a/app/src/main/java/com/paw/key/data/dto/response/walkpreparation/WalkPreparationMessageResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walkpreparation/WalkPreparationMessageResponseDto.kt new file mode 100644 index 00000000..89ff87bc --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/walkpreparation/WalkPreparationMessageResponseDto.kt @@ -0,0 +1,18 @@ +package com.paw.key.data.dto.response.walkpreparation + +import com.paw.key.domain.entity.walkpreparation.WalkPreparationMessageEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkPreparationMessageResponseDto( + @SerialName("mainMessage") + val mainMessage: String, + @SerialName("subMessage") + val subMessage: String +) { + fun toEntity() = WalkPreparationMessageEntity( + mainMessage = mainMessage, + subMessage = subMessage + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/walkpreparation/WalkPreparationResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walkpreparation/WalkPreparationResponseDto.kt new file mode 100644 index 00000000..db9b89c2 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/walkpreparation/WalkPreparationResponseDto.kt @@ -0,0 +1,15 @@ +package com.paw.key.data.dto.response.walkpreparation + +import com.paw.key.domain.entity.walkpreparation.WalkPreparationEntity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WalkPreparationResponseDto( + @SerialName("preparation") + val preparationList: List +) { + fun toEntity() = WalkPreparationEntity( + preparationList = preparationList + ) +} diff --git a/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewCategoryResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewCategoryResponseDto.kt deleted file mode 100644 index 2deec624..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewCategoryResponseDto.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.paw.key.data.dto.response.walkreview - -import com.paw.key.domain.entity.walkreview.WalkReviewCategoryEntity -import com.paw.key.domain.entity.walkreview.WalkReviewCategoryListEntity -import com.paw.key.domain.entity.walkreview.WalkReviewOptionOptionsResponseEntity -import kotlinx.serialization.Serializable - -@Serializable -data class WalkReviewCategoryResponseDto( - val categoryList : List -) { - fun toEntity() = WalkReviewCategoryListEntity( - categoryList = categoryList.map { it.toEntity() } - ) -} - -@Serializable -data class CategoryResponseDto( - val categoryId : Int, - val categoryDescription : String, - val categoryName : String, - val categoryOptions : List -) { - fun toEntity() = WalkReviewCategoryEntity( - categoryId = categoryId, - categoryDescription = categoryDescription, - categoryName = categoryName, - options = categoryOptions.map { it.toEntity() } - ) - -} - -@Serializable -data class OptionsResponseDto( - val categoryOptionId : Int, - val categoryOptionText : String -) { - fun toEntity() = WalkReviewOptionOptionsResponseEntity( - categoryOptionId = categoryOptionId, - optionText = categoryOptionText - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewInfoResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewInfoResponseDto.kt deleted file mode 100644 index 1af4e895..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewInfoResponseDto.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.paw.key.data.dto.response.walkreview - -import com.paw.key.domain.entity.walkreview.WalkReviewInfoEntity -import com.paw.key.domain.entity.walkreview.WalkReviewRouteInfoEntity -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class WalkReviewInfoResponseDto( - @SerialName("routeDto") - val routeDto: WalkReviewRouteInfoResponseDto, - @SerialName("petName") - val petName: String -) { - fun toEntity() = WalkReviewInfoEntity( - routeDto = routeDto.toEntity(), - petName = petName - ) -} - -@Serializable -data class WalkReviewRouteInfoResponseDto( - val id: Int, - @SerialName("locationDescription") - val locationDescription: String, - @SerialName("dateDescription") - val dateDescription: String, - @SerialName("descriptionTags") - val descriptionTags: List -) { - fun toEntity() = WalkReviewRouteInfoEntity( - id = id, - locationDescription = locationDescription, - dateDescription = dateDescription, - descriptionTags = descriptionTags - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewResponseDto.kt deleted file mode 100644 index 00db2f2a..00000000 --- a/app/src/main/java/com/paw/key/data/dto/response/walkreview/WalkReviewResponseDto.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.paw.key.data.dto.response.walkreview - -import com.paw.key.domain.entity.walkreview.WalkReviewIdEntity -import kotlinx.serialization.Serializable - -@Serializable -data class WalkReviewResponseDto( - val postId: Int, - val routeId : Int -) { - fun toEntity(): WalkReviewIdEntity { - return WalkReviewIdEntity( - postId = postId, - routeId = routeId - ) - } -} diff --git a/app/src/main/java/com/paw/key/data/mapper/RegionMapper.kt b/app/src/main/java/com/paw/key/data/mapper/RegionMapper.kt deleted file mode 100644 index 7ed05404..00000000 --- a/app/src/main/java/com/paw/key/data/mapper/RegionMapper.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.paw.key.data.mapper - -import com.paw.key.data.dto.response.region.GeometryDto -import com.paw.key.data.dto.response.region.RegionResponseDto -import com.paw.key.domain.entity.region.GeometryEntity -import com.paw.key.domain.entity.region.RegionDataEntity -import javax.inject.Inject - -class RegionMapper @Inject constructor() { - fun mapDtoToEntity(dto: RegionResponseDto): RegionDataEntity { - return RegionDataEntity( - regionName = dto.regionName, - preRegionName = dto.preRegionName, - geometry = dto.geometryDto.toEntity() - ) - } - - private fun GeometryDto.toEntity(): GeometryEntity { - return GeometryEntity( - type = this.type, - coordinates = this.coordinates.map { polygon -> - polygon.map { ring -> - ring.map { point -> - // 서버에서 위도 경도 다름 - Pair(point[1], point[0]) - } - } - } - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/network/TokenAuthenticator.kt b/app/src/main/java/com/paw/key/data/network/TokenAuthenticator.kt new file mode 100644 index 00000000..d21fda79 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/network/TokenAuthenticator.kt @@ -0,0 +1,67 @@ +package com.paw.key.data.network + +import com.paw.key.core.app.AppRestarter +import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import okhttp3.Authenticator +import okhttp3.Request +import okhttp3.Response +import okhttp3.Route +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TokenAuthenticator @Inject constructor( + private val tokenManager: LocalStorageRepository, + private val appRestarter: AppRestarter, + private val refreshService: TokenRefreshService +) : Authenticator { + private val mutex = Mutex() + + override fun authenticate(route: Route?, response: Response): Request? = runBlocking { + mutex.withLock { + val currentToken = tokenManager.getAccessToken() + + // 이미 갱신되었는지 확인 + val reqToken = response.request.header("Authorization")?.substringAfter("Bearer ") + if (reqToken != currentToken && currentToken.isNotEmpty()) { + Timber.d("토큰 이미 갱신됨. 새 토큰으로 재시도: Bearer $currentToken") + return@withLock response.request.newBuilder() + .header("Authorization", "Bearer $currentToken") + .build() + } + + val refreshToken = tokenManager.getRefreshToken() + val deviceId = tokenManager.getDeviceId() + if (refreshToken.isEmpty()) { + appRestarter.restartApp() + return@withLock null + } + + try { + val tokenDto = refreshService.refresh(refreshToken, deviceId).getOrNull() + ?: run { + tokenManager.clearInfo() + appRestarter.restartApp() + return@withLock null + } + + val (accessToken, refreshToken) = tokenDto + + tokenManager.saveTokens(accessToken.value, refreshToken.value) + + return@withLock response.request.newBuilder() + .header("Authorization", "Bearer ${accessToken.value}") + .build() + } catch (e: Exception) { + Timber.e(e, "토큰 갱신 실패 - 앱 재시작") + tokenManager.clearInfo() + appRestarter.restartApp() + return@withLock null + } + } + } +} diff --git a/app/src/main/java/com/paw/key/data/network/TokenRefreshService.kt b/app/src/main/java/com/paw/key/data/network/TokenRefreshService.kt new file mode 100644 index 00000000..4d5ee91e --- /dev/null +++ b/app/src/main/java/com/paw/key/data/network/TokenRefreshService.kt @@ -0,0 +1,9 @@ +package com.paw.key.data.network + +import com.paw.key.core.model.AccessToken +import com.paw.key.core.model.RefreshToken + + +interface TokenRefreshService { + suspend fun refresh(refreshToken: String, deviceId: String): Result> +} diff --git a/app/src/main/java/com/paw/key/data/network/TokenRefreshServiceImpl.kt b/app/src/main/java/com/paw/key/data/network/TokenRefreshServiceImpl.kt new file mode 100644 index 00000000..e15ebef4 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/network/TokenRefreshServiceImpl.kt @@ -0,0 +1,35 @@ +package com.paw.key.data.network + +import com.paw.key.core.model.AccessToken +import com.paw.key.core.model.RefreshToken +import com.paw.key.core.util.suspendRunCatching +import com.paw.key.data.dto.request.auth.AuthReissueRequestDto +import com.paw.key.data.service.auth.ReissueService +import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import javax.inject.Inject + +class TokenRefreshServiceImpl @Inject constructor( + private val reissueService: ReissueService, + private val tokenManager: LocalStorageRepository +) : TokenRefreshService { + override suspend fun refresh( + refreshToken: String, + deviceId: String + ): Result> = suspendRunCatching { + val response = reissueService.reissueToken( + body = AuthReissueRequestDto( + refreshToken = refreshToken, + deviceId = deviceId + ) + ) + + val data = response + + tokenManager.saveTokens(data.accessToken, data.refreshToken) + + Pair( + AccessToken(data.accessToken), + RefreshToken(data.refreshToken) + ) + } +} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/PetProfileDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/PetProfileDataSource.kt deleted file mode 100644 index 8f16027f..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/PetProfileDataSource.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paw.key.data.remote.datasource - -import com.paw.key.data.service.PetProfileService -import javax.inject.Inject - -class PetProfileDataSource @Inject constructor( - private val petprofileservice: PetProfileService -) { - suspend fun getPetProfiles(userId: Int) = petprofileservice.getPetProfiles(userId) -} \ 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 1885f479..c3826bea 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 @@ -6,7 +6,7 @@ import javax.inject.Inject class RegionDataSource @Inject constructor ( private val regionService: RegionService ) { - suspend fun getRegionGeometry(userId: Int, regionId: Int) = regionService.getRegionGeometry(userId, regionId) + suspend fun getRegionGeometry(regionId: Int) = regionService.getRegionGeometry(regionId) suspend fun getRegionsList() = regionService.getRegionsList() } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/UserProfileDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/UserProfileDataSource.kt deleted file mode 100644 index 23b60071..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/UserProfileDataSource.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paw.key.data.remote.datasource - -import com.paw.key.data.service.UserProfileService -import javax.inject.Inject - -class UserProfileDataSource @Inject constructor( - private val userprofileservice: UserProfileService -) { - suspend fun getUserProfiles(userId: Int) = userprofileservice.getUserProfiles(userId) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/WalkCourseDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/WalkCourseDataSource.kt deleted file mode 100644 index d6756404..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/WalkCourseDataSource.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.paw.key.data.remote.datasource - -import com.paw.key.data.dto.request.walkcourse.WalkCourseRequestDto -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.walkcourse.WalkCourseResponseDto -import com.paw.key.data.service.walkcourse.WalkCourseService -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.MultipartBody -import okhttp3.RequestBody.Companion.toRequestBody -import javax.inject.Inject - -class WalkCourseDataSource @Inject constructor( - private val walkCourseService: WalkCourseService -) { - suspend fun postWalkCourse( - userId: Int, - file: MultipartBody.Part, - walkCourseRequestDto: WalkCourseRequestDto - ): BaseResponse { - val jsonString = Json.encodeToString(WalkCourseRequestDto.serializer(), walkCourseRequestDto) - val requestBody = jsonString.toRequestBody("application/json".toMediaType()) - - return walkCourseService.postWalkCourse(userId, file, requestBody) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/filter/FilterOptionDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/filter/FilterOptionDataSource.kt deleted file mode 100644 index 96cd4561..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/filter/FilterOptionDataSource.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.paw.key.data.remote.datasource.filter - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.filter.FilterOptionResponse -import com.paw.key.data.service.filter.FilterOptionService -import javax.inject.Inject - - -class FilterOptionDataSource @Inject constructor( - private val filterOptionService: FilterOptionService -) { - suspend fun getFilterOptions(userId: Int) = filterOptionService.getFilterOptions(userId).data -} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/home/HomeRegionDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/home/HomeRegionDataSource.kt index 4af6c97e..cb58f203 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/home/HomeRegionDataSource.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/home/HomeRegionDataSource.kt @@ -9,5 +9,14 @@ class HomeRegionDataSource @Inject constructor( ) { suspend fun patchRegion(userId: Int, regionId: Int) = service.patchRegion(userId, HomeRegionRequest(regionId)) + + suspend fun getHomeInfo() = + service.getHomeInfo() + + suspend fun getHomeWeather() = + service.getHomeWeather() + + suspend fun getHomeRecommended() = + service.getHomeRecommended() } diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageDataSource.kt index ff36c21f..d99a3ec2 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageDataSource.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageDataSource.kt @@ -3,7 +3,6 @@ package com.paw.key.data.remote.datasource.image import com.paw.key.data.dto.image.presigned.ImagePresignedRequestDto import com.paw.key.data.dto.image.register.ImageRegisterRequestDto import com.paw.key.data.service.image.ImageService -import okhttp3.RequestBody import javax.inject.Inject class ImageDataSource @Inject constructor( @@ -20,12 +19,4 @@ class ImageDataSource @Inject constructor( ) = imageService.presignedImage( body = dto ) - - suspend fun uploadS3( - presignedUrl : String, - file : RequestBody - ) = imageService.uploadToS3( - presignedUrl = presignedUrl, - file = file - ) } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageLocalDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageLocalDataSource.kt index 3f3e53e4..831a07f4 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageLocalDataSource.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/image/ImageLocalDataSource.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Build import androidx.core.net.toUri import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream @@ -79,9 +80,22 @@ class ImageLocalDataSource @Inject constructor( return options.outWidth to options.outHeight } + // 개별 삭제 함수 + fun deleteOriginalUri(uriString: String) { + val uri = uriString.split("#").last().toUri() + + if (uri.toString().contains("media/picker")) return + + try { + context.contentResolver.delete(uri, null, null) + } catch (e: Exception) { + Timber.e(e, "원본 파일 삭제 실패") + } + } + companion object { private const val DIRECTORY = "image_cache" private const val MAX_SIZE = 1024 private const val WEBP_QUALITY = 80 } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/image/S3DataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/image/S3DataSource.kt new file mode 100644 index 00000000..fbd1a0a6 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/image/S3DataSource.kt @@ -0,0 +1,17 @@ +package com.paw.key.data.remote.datasource.image + +import com.paw.key.data.service.image.S3Service +import okhttp3.RequestBody +import javax.inject.Inject + +class S3DataSource @Inject constructor( + private val s3Service: S3Service +) { + suspend fun uploadS3( + presignedUrl : String, + file : RequestBody + ) = s3Service.uploadToS3( + presignedUrl = presignedUrl, + file = file + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/list/PostsListDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/list/PostsListDataSource.kt deleted file mode 100644 index b86d8d54..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/list/PostsListDataSource.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.paw.key.data.remote.datasource.list - -import com.paw.key.data.dto.request.list.PostsListRequestDto -import com.paw.key.data.service.list.PostsListService -import javax.inject.Inject - -class PostsListDataSource @Inject constructor( - private val service: PostsListService -) { - suspend fun postList(userId: Int, request: PostsListRequestDto) = - service.postList(userId, request) - - suspend fun getAllPosts(userId: Int) = - service.postList(userId, PostsListRequestDto( - durationStart = 0, - durationEnd = 0, - selectedOptions = emptyList() - )) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/posts/PostsDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/posts/PostsDataSource.kt new file mode 100644 index 00000000..05574777 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/posts/PostsDataSource.kt @@ -0,0 +1,34 @@ +package com.paw.key.data.remote.datasource.posts + +import com.paw.key.data.dto.request.posts.PostsDataRequestDto +import com.paw.key.data.dto.request.posts.PostsFilterRequestDto +import com.paw.key.data.service.posts.PostsService +import javax.inject.Inject + +class PostsDataSource @Inject constructor( + private val service: PostsService +) { + suspend fun postPosts(request: PostsDataRequestDto) = service.postPosts(request) + + suspend fun getCategories() = service.getCategories() + + suspend fun getPostsDetail(postId: Int) = service.getPostsDetail(postId) + + suspend fun getTop3Reviews(routeId: Int) = service.getTop3Reviews(routeId) + + suspend fun getPostsFilter( + sortBy: String = "latest", + cursor: String? = null, + size: Int = 10, + request: PostsFilterRequestDto + ) = service.getPostsFilter( + sortBy = sortBy, + cursor = cursor, + size = size, + request = request + ) + + suspend fun getCategoriesFilter() = service.getCategoriesFilter() + + suspend fun postLike(postId: Int) = service.postLike(postId) +} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/sharedwalk/SharedWalkDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/sharedwalk/SharedWalkDataSource.kt deleted file mode 100644 index 95197b36..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/sharedwalk/SharedWalkDataSource.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.paw.key.data.remote.datasource.sharedwalk - -import com.paw.key.data.dto.request.sharedwalk.SharedWalkReviewRequestDto -import com.paw.key.data.service.sharedwalk.SharedWalkService -import javax.inject.Inject - -class SharedWalkDataSource @Inject constructor( - private val sharedWalkService: SharedWalkService -) { - suspend fun getSharedWalkTrack(userId: Int, routeId: Int) = sharedWalkService.getSharedWalkTrack(userId, routeId) - - suspend fun postSharedWalkReviewRegister(userId: Int, reviewDto: SharedWalkReviewRequestDto) = - sharedWalkService.postSharedWalkReview(userId, reviewDto) - - -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/user/UserDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/user/UserDataSource.kt index 3570805a..22d82c4a 100644 --- a/app/src/main/java/com/paw/key/data/remote/datasource/user/UserDataSource.kt +++ b/app/src/main/java/com/paw/key/data/remote/datasource/user/UserDataSource.kt @@ -1,6 +1,8 @@ package com.paw.key.data.remote.datasource.user +import com.paw.key.core.util.suspendRunCatching import com.paw.key.data.dto.request.user.UserInfoRequestDto +import com.paw.key.data.dto.request.user.UserWithDrawRequestDto import com.paw.key.data.service.user.UserService import javax.inject.Inject @@ -10,4 +12,19 @@ class UserDataSource @Inject constructor( suspend fun createUser(dto: UserInfoRequestDto) = userService.createUser(dto) suspend fun getPetBreeds() = userService.getPetBreeds() -} \ No newline at end of file + + suspend fun getPetProfiles(petId: Int) = userService.getPetProfiles(petId) + + suspend fun deleteUser(dto: UserWithDrawRequestDto) { + val response = userService.deleteUser(dto) + if (!response.isSuccessful) { + throw Exception("회원탈퇴 실패: ${response.code()}") + } + } + + suspend fun getUserProfiles() = userService.getUserProfiles() + + suspend fun getNicknameDifference(nickname: String): Boolean = suspendRunCatching { + userService.getNicknameDifference(nickname).code == "U40901" + }.getOrDefault(false) +} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/walk/WalkDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/walk/WalkDataSource.kt new file mode 100644 index 00000000..019eebd6 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/walk/WalkDataSource.kt @@ -0,0 +1,37 @@ +package com.paw.key.data.remote.datasource.walk + +import com.paw.key.data.dto.request.walk.WalkFinishRequestDto +import com.paw.key.data.dto.request.walk.WalkPointRequestDto +import com.paw.key.data.dto.request.walk.WalkStartRequestDto +import com.paw.key.data.service.walk.WalkService +import javax.inject.Inject + +class WalkDataSource @Inject constructor( + private val walkService: WalkService +) { + suspend fun startWalk( + dto : WalkStartRequestDto + ) = walkService.startWalk( + body = dto + ) + + suspend fun pointWalk( + dto : WalkPointRequestDto + ) = walkService.pointWalk( + body = dto + ) + + suspend fun finishWalk( + routeId : String, + dto : WalkFinishRequestDto + ) = walkService.finishWalk( + body = dto, + routeId = routeId + ) + + suspend fun completeWalk( + routeId : String + ) = walkService.getRouteGeometry( + routeId = routeId + ) +} diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/walklist/WalkListDetailDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/walklist/WalkListDetailDataSource.kt deleted file mode 100644 index e302f02e..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/walklist/WalkListDetailDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.paw.key.data.remote.datasource.walklist - -import com.paw.key.data.service.walklist.WalkListDetailService -import javax.inject.Inject - -class WalkListDetailDataSource @Inject constructor( - private val walkListDetailService: WalkListDetailService -) { - suspend fun getWalkListDetail(userId: Int, postId: Int) = walkListDetailService.getWalkListDetail(userId, postId) - - suspend fun getWalkReviewSummary(userId: Int, postId: Int) = walkListDetailService.getWalkReviewSummary(userId, postId) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/walkpreparation/WalkPreparationDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/walkpreparation/WalkPreparationDataSource.kt new file mode 100644 index 00000000..d0275fb5 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/walkpreparation/WalkPreparationDataSource.kt @@ -0,0 +1,19 @@ +package com.paw.key.data.remote.datasource.walkpreparation + +import com.paw.key.data.dto.request.walkpreparation.WalkPreparationRequestDto +import com.paw.key.data.service.walkpreparation.WalkPreparationService +import javax.inject.Inject + +class WalkPreparationDataSource @Inject constructor( + private val walkPreparationService: WalkPreparationService +) { + suspend fun getWalkPreparation() = walkPreparationService.getWalkPreparation() + + suspend fun patchWalkPreparation( + body : WalkPreparationRequestDto + ) = walkPreparationService.patchWalkPreparation( + body = body + ) + + suspend fun getWalkPreparationMessage() = walkPreparationService.getWalkPreparationMessage() +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/walkreview/WalkReviewDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/walkreview/WalkReviewDataSource.kt deleted file mode 100644 index a9c8574b..00000000 --- a/app/src/main/java/com/paw/key/data/remote/datasource/walkreview/WalkReviewDataSource.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.paw.key.data.remote.datasource.walkreview - -import com.paw.key.data.dto.request.walkcourse.WalkCourseRequestDto -import com.paw.key.data.dto.request.walkreview.WalkCourseReviewRequestDto -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.walkreview.WalkReviewResponseDto -import com.paw.key.data.service.walkreview.WalkReviewService -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.MultipartBody -import okhttp3.RequestBody.Companion.toRequestBody -import javax.inject.Inject - -class WalkReviewDataSource @Inject constructor( - private val service: WalkReviewService -) { - suspend fun postWalkReview( - userId: Int, - imageFiles: List, - walkReviewRequestDto: WalkCourseReviewRequestDto - ) : BaseResponse { - val jsonString = Json.encodeToString(WalkCourseReviewRequestDto.serializer(), walkReviewRequestDto) - val requestBody = jsonString.toRequestBody("application/json".toMediaType()) - - return service.postWalkReview( - userId = userId, - imageFiles = imageFiles, - data = requestBody - ) - } - - suspend fun getWalkReviewInfo( - userId: Int, - routeId: Int - ) = service.getWalkReviewInfo( - userId = userId, - routeId = routeId - ) - - suspend fun getWalkReviewCategory( - userId: Int - ) = service.getWalkReviewCategory( - userId = userId - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/PetProfileRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/PetProfileRepositoryImpl.kt deleted file mode 100644 index 6f4b0254..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/PetProfileRepositoryImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.paw.key.data.repositoryimpl - -import com.paw.key.data.remote.datasource.PetProfileDataSource -import com.paw.key.domain.entity.petprofile.PetProfileEntity -import com.paw.key.domain.repository.petprofile.PetProfileRepository -import javax.inject.Inject - -class PetProfileRepositoryImpl @Inject constructor( - private val dataSource: PetProfileDataSource, -) : PetProfileRepository { - - override suspend fun getPetProfiles(userId: Int): Result> = runCatching { - dataSource.getPetProfiles(userId).data.map { it.toEntity() } - } -} \ 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 337b235c..5f377295 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 @@ -1,7 +1,7 @@ package com.paw.key.data.repositoryimpl +import com.paw.key.core.util.suspendRunCatching import com.paw.key.data.dto.response.region.toEntity -import com.paw.key.data.mapper.RegionMapper import com.paw.key.data.remote.datasource.RegionDataSource import com.paw.key.domain.entity.region.RegionDataEntity import com.paw.key.domain.entity.signup.DistrictEntity @@ -10,15 +10,13 @@ import javax.inject.Inject class RegionRepositoryImpl @Inject constructor( private val regionDataSource: RegionDataSource, - private val mapper: RegionMapper ) : RegionRepository { - override suspend fun getRegionGeometry(userId: Int, regionId: Int): Result = runCatching { - regionDataSource.getRegionGeometry(userId, regionId).data.let { - mapper.mapDtoToEntity(it) + override suspend fun getRegionGeometry(regionId: Int): Result = + suspendRunCatching { + regionDataSource.getRegionGeometry(regionId).data.toEntity() } - } - override suspend fun getRegionList(): Result> = runCatching { + override suspend fun getRegionList(): Result> = suspendRunCatching { regionDataSource.getRegionsList().data.districtDtos.map { it.toEntity() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/UserProfileRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/UserProfileRepositoryImpl.kt deleted file mode 100644 index 953fb82e..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/UserProfileRepositoryImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.paw.key.data.repositoryimpl - -import com.paw.key.data.remote.datasource.UserProfileDataSource -import com.paw.key.domain.model.entity.uerprofile.UserProfileEntity -import com.paw.key.domain.repository.userprofile.UserProfileRepository -import javax.inject.Inject - -class UserProfileRepositoryImpl @Inject constructor( - private val dataSource: UserProfileDataSource, -) : UserProfileRepository { - - override suspend fun getUserProfiles(userId: Int): Result = runCatching { - dataSource.getUserProfiles(userId).data.toEntity() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/WalkCourseRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/WalkCourseRepositoryImpl.kt deleted file mode 100644 index 19c47cab..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/WalkCourseRepositoryImpl.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.paw.key.data.repositoryimpl - -import com.paw.key.data.dto.request.walkcourse.WalkCourseRequestDto -import com.paw.key.data.remote.datasource.WalkCourseDataSource -import com.paw.key.domain.entity.walkcourse.WalkCourseRegionIdEntity -import com.paw.key.domain.repository.walkcourse.WalkCourseRepository -import okhttp3.MultipartBody -import javax.inject.Inject - -class WalkCourseRepositoryImpl @Inject constructor( - private val walkCourseDataSource: WalkCourseDataSource -) : WalkCourseRepository { - override suspend fun postWalkCourse( - userId: Int, - image: MultipartBody.Part, - routeRequestDto: WalkCourseRequestDto - ): Result = runCatching { - walkCourseDataSource.postWalkCourse(userId, image, routeRequestDto) - .data.toEntity() - } -} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/filter/FilterOptionRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/filter/FilterOptionRepositoryImpl.kt deleted file mode 100644 index 5b4ef0ed..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/filter/FilterOptionRepositoryImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.paw.key.data.repositoryimpl.filter - -import com.paw.key.data.remote.datasource.filter.FilterOptionDataSource -import com.paw.key.domain.entity.filter.FilterEntity -import com.paw.key.domain.repository.filter.FilterOptionRepository -import javax.inject.Inject - -class FilterOptionRepositoryImpl @Inject constructor( - private val dataSource: FilterOptionDataSource -) : FilterOptionRepository { - - override suspend fun getFilterOptions(userId: Int): Result = runCatching { - dataSource.getFilterOptions(userId).toEntity() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/home/HomeRegionRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/home/HomeRegionRepositoryImpl.kt index b429c859..90add40f 100644 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/home/HomeRegionRepositoryImpl.kt +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/home/HomeRegionRepositoryImpl.kt @@ -1,13 +1,17 @@ package com.paw.key.data.repositoryimpl.home +import com.paw.key.core.util.suspendRunCatching import com.paw.key.data.remote.datasource.home.HomeRegionDataSource +import com.paw.key.domain.entity.home.HomeInfoEntity import com.paw.key.domain.entity.home.HomeRegionDataEntity -import com.paw.key.domain.repository.home.HomeRegionRepository +import com.paw.key.domain.entity.home.HomeRouteEntity +import com.paw.key.domain.entity.home.HomeWeatherEntity +import com.paw.key.domain.repository.home.HomeRepository import javax.inject.Inject -class HomeRegionRepositoryImpl @Inject constructor( +class HomeRepositoryImpl @Inject constructor( private val dataSource: HomeRegionDataSource, -) : HomeRegionRepository { +) : HomeRepository { override suspend fun patchRegion(userId: Int, regionId: Int): Result { return runCatching { @@ -19,4 +23,16 @@ class HomeRegionRepositoryImpl @Inject constructor( } } } + + override suspend fun getHomeInfo(): Result = suspendRunCatching { + dataSource.getHomeInfo().data.toEntity() + } + + override suspend fun getHomeWeather(): Result = suspendRunCatching { + dataSource.getHomeWeather().data.toEntity() + } + + override suspend fun getHomeRecommended(): Result = suspendRunCatching { + dataSource.getHomeRecommended().data.toEntity() + } } diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/image/ImageRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/image/ImageRepositoryImpl.kt index 436f6709..39338684 100644 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/image/ImageRepositoryImpl.kt +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/image/ImageRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.paw.key.data.dto.image.presigned.toDto import com.paw.key.data.dto.image.register.toDto import com.paw.key.data.remote.datasource.image.ImageDataSource import com.paw.key.data.remote.datasource.image.ImageLocalDataSource +import com.paw.key.data.remote.datasource.image.S3DataSource import com.paw.key.domain.entity.image.ImageDomainType import com.paw.key.domain.entity.image.ImagePresignedEntity import com.paw.key.domain.entity.image.ImagePresignedResultEntity @@ -13,10 +14,12 @@ import com.paw.key.domain.entity.image.ImageRegisterResultEntity import com.paw.key.domain.repository.image.ImageRepository import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.asRequestBody +import timber.log.Timber import javax.inject.Inject class ImageRepositoryImpl @Inject constructor( private val imageDataSource: ImageDataSource, + private val s3DataSource: S3DataSource, private val imageLocalDataSource: ImageLocalDataSource ) : ImageRepository { override suspend fun registerImage( @@ -24,25 +27,33 @@ class ImageRepositoryImpl @Inject constructor( domainType: ImageDomainType, ): Result = suspendRunCatching{ - val optimizedFile = imageLocalDataSource.getOptimizedFile(uriString.split("#").last()) + val parts = uriString.split("#") + val remoteImageUrl = parts.first() + val localUriString = parts.last() + Timber.e("registerImage: $parts") + + val optimizedFile = imageLocalDataSource.getOptimizedFile(localUriString) val (width, height) = imageLocalDataSource.getImageSize(optimizedFile) try { val registerEntity = ImageRegisterEntity( - imageUrl = uriString.split("#").first(), - contentType = optimizedFile.extension, + imageUrl = remoteImageUrl, + contentType = "image/${optimizedFile.extension}", width = width, height = height, domain = domainType ) imageDataSource.registerImage( - dto = registerEntity - .copy().toDto() + dto = registerEntity.toDto() ).data.toEntity() } finally { - imageLocalDataSource.clearCache() + imageLocalDataSource.deleteOriginalUri(uriString) + + if (optimizedFile.exists()) { + optimizedFile.delete() + } } } @@ -61,13 +72,13 @@ class ImageRepositoryImpl @Inject constructor( val requestBody = file.asRequestBody("image/webp".toMediaTypeOrNull()) - val response = imageDataSource.uploadS3(presignedUrl, requestBody) - - imageLocalDataSource.clearCache() + val response = s3DataSource.uploadS3(presignedUrl, requestBody) if (!response.isSuccessful) { throw Exception("S3 Upload Failed: ${response.code()}") } + + imageLocalDataSource.clearCache() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/list/PostsListRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/list/PostsListRepositoryImpl.kt deleted file mode 100644 index e4cc77c7..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/list/PostsListRepositoryImpl.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.paw.key.data.repositoryimpl.list - -import com.paw.key.data.dto.request.list.PostsListRequestDto -import com.paw.key.data.dto.response.list.toEntity -import com.paw.key.data.remote.datasource.list.PostsListDataSource -import com.paw.key.domain.entity.list.ListEntity -import com.paw.key.domain.repository.list.PostsListRepository -import javax.inject.Inject - -class PostsListRepositoryImpl @Inject constructor( - private val dataSource: PostsListDataSource, -) : PostsListRepository { - override suspend fun postList(userId: Int, request: PostsListRequestDto) - : Result = runCatching { - val response = dataSource.postList(userId, request) - if (response.code == "S000") { - response.data.toEntity() - } else { - throw Exception(response.message) - } - } - - override suspend fun getAllPosts(userId: Int): Result = runCatching { - val response = dataSource.getAllPosts(userId) - if (response.code == "S000") { - response.data.toEntity() ?: throw Exception("Data is null") - } else { - throw Exception(response.message) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/localstorage/LocalStorageRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/localstorage/LocalStorageRepositoryImpl.kt index 57fccf98..5b0a7395 100644 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/localstorage/LocalStorageRepositoryImpl.kt +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/localstorage/LocalStorageRepositoryImpl.kt @@ -2,10 +2,12 @@ package com.paw.key.data.repositoryimpl.localstorage import android.content.Context import android.content.SharedPreferences +import androidx.core.content.edit import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import com.paw.key.domain.repository.localstorage.LocalStorageRepository import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.File import java.util.UUID import javax.inject.Inject import javax.inject.Singleton @@ -15,17 +17,48 @@ class LocalStorageRepositoryImpl @Inject constructor( @ApplicationContext private val context: Context ) : LocalStorageRepository { private val sharedPreferences: SharedPreferences by lazy { - val masterKey = MasterKey.Builder(context) + createEncryptedSharedPreferences() ?: recreateAndCreate() + } + + private fun buildMasterKey(): MasterKey { + return MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() + } - EncryptedSharedPreferences.create( - context, - PREFERENCES_NAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) + private fun createEncryptedSharedPreferences(): SharedPreferences? { + return try { + EncryptedSharedPreferences.create( + context, + PREFERENCES_NAME, + buildMasterKey(), + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } catch (e: Exception) { + // 복호화 실패 (키 불일치, 재설치, 백업 복원 등) + null + } + } + + // 손상된 SharedPreferences 파일 삭제 + private fun recreateAndCreate(): SharedPreferences { + try { + context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + .edit(commit = true) { clear() } + + val prefsFile = File( + context.filesDir.parent, + "shared_prefs/$PREFERENCES_NAME.xml" + ) + if (prefsFile.exists()) prefsFile.delete() + } catch (e: Exception) { + // 삭제 실패해도 계속 진행 + } + + // 파일 삭제 후 재생성 + return createEncryptedSharedPreferences() + ?: throw IllegalStateException("EncryptedSharedPreferences 생성에 실패했습니다.") } override suspend fun saveTokens(accessToken: String, refreshToken: String) { @@ -37,11 +70,11 @@ class LocalStorageRepositoryImpl @Inject constructor( } override suspend fun getAccessToken(): String { - return sharedPreferences.getString(ACCESS_TOKEN, "") ?: "" + return sharedPreferences.getString(ACCESS_TOKEN, "").orEmpty() } override suspend fun getRefreshToken(): String { - return sharedPreferences.getString(REFRESH_TOKEN, "") ?: "" + return sharedPreferences.getString(REFRESH_TOKEN, "").orEmpty() } override suspend fun removeTokens() { @@ -63,6 +96,18 @@ class LocalStorageRepositoryImpl @Inject constructor( return sharedPreferences.getInt(USER_ID, -1) } + override suspend fun saveUserProvider(provider: String) { + sharedPreferences.edit().apply { + putString(USER_PROVIDER, provider) + apply() + } + } + + override suspend fun getUserProvider(): String { + return sharedPreferences + .getString(USER_PROVIDER, "").orEmpty() + } + override suspend fun savePetId(petId: Int) { sharedPreferences.edit().apply { putInt(PET_ID, petId) @@ -74,6 +119,17 @@ class LocalStorageRepositoryImpl @Inject constructor( return sharedPreferences.getInt(PET_ID, -1) } + override suspend fun savePetName(petName: String) { + sharedPreferences.edit().apply { + putString(PET_NAME, petName) + apply() + } + } + + override suspend fun getPetName(): String { + return sharedPreferences.getString(PET_NAME, "").orEmpty() + } + override suspend fun saveDeviceId(deviceId: String) { sharedPreferences.edit().apply { putString(DEVICE_ID, deviceId) @@ -103,6 +159,8 @@ class LocalStorageRepositoryImpl @Inject constructor( remove(DEVICE_ID) remove(ACCESS_TOKEN) remove(REFRESH_TOKEN) + remove(USER_PROVIDER) + remove(PET_NAME) apply() } } @@ -114,5 +172,7 @@ class LocalStorageRepositoryImpl @Inject constructor( private const val DEVICE_ID = "device_id" private const val USER_ID = "user_id" private const val PET_ID = "pet_id" + private const val USER_PROVIDER = "user_provider" + private const val PET_NAME = "pet_name" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/posts/PostsRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/posts/PostsRepositoryImpl.kt new file mode 100644 index 00000000..6d657403 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/posts/PostsRepositoryImpl.kt @@ -0,0 +1,60 @@ +package com.paw.key.data.repositoryimpl.posts + + +import com.paw.key.core.util.suspendRunCatching +import com.paw.key.data.dto.request.posts.toDto +import com.paw.key.data.remote.datasource.posts.PostsDataSource +import com.paw.key.domain.entity.posts.FilterSelectedItemEntity +import com.paw.key.domain.entity.posts.PostsCategoryEntity +import com.paw.key.domain.entity.posts.PostsDetailEntity +import com.paw.key.domain.entity.posts.PostsEntity +import com.paw.key.domain.entity.posts.PostsFilterEntity +import com.paw.key.domain.entity.posts.PostsInfoEntity +import com.paw.key.domain.entity.posts.PostsResultEntity +import com.paw.key.domain.entity.posts.PostsTop3Entity +import com.paw.key.domain.repository.posts.PostsRepository +import javax.inject.Inject + +class PostsRepositoryImpl @Inject constructor( + private val dataSource: PostsDataSource, +) : PostsRepository { + override suspend fun postPosts( + postsInfo: PostsInfoEntity + ): Result = suspendRunCatching{ + dataSource.postPosts(postsInfo.toDto()).data.toEntity() + } + + override suspend fun getPostsDetail(postId: Int): Result = suspendRunCatching { + dataSource.getPostsDetail(postId).data.toEntity() + } + + override suspend fun getPostsFilter( + sortBy: String, + cursor: String?, + size: Int, + postsFilter: FilterSelectedItemEntity + ): Result = suspendRunCatching { + dataSource.getPostsFilter( + sortBy = sortBy, + cursor = cursor, + size = size, + request = postsFilter.toDto() + ).data.toEntity() + } + + override suspend fun getCategoriesFilter(): Result = suspendRunCatching { + dataSource.getCategoriesFilter().data.toEntity() + } + + override suspend fun postLike(postId: Int): Result = suspendRunCatching { + dataSource.postLike(postId).data + } + + override suspend fun getTop3Reviews(routeId: Int): Result = suspendRunCatching { + dataSource.getTop3Reviews(routeId).data.toEntity() + } + + override suspend fun getCategories(): Result = suspendRunCatching { + dataSource.getCategories().data.toEntity() + } +} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/sharedwalk/SharedWalkRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/sharedwalk/SharedWalkRepositoryImpl.kt deleted file mode 100644 index ef7e5d69..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/sharedwalk/SharedWalkRepositoryImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.paw.key.data.repositoryimpl.sharedwalk - -import com.paw.key.data.remote.datasource.sharedwalk.SharedWalkDataSource -import com.paw.key.domain.entity.sharedwalk.SharedWalkEntity -import com.paw.key.domain.entity.sharedwalk.SharedWalkReviewEntity -import com.paw.key.domain.repository.sharedwalk.SharedWalkRepository -import javax.inject.Inject - -class SharedWalkRepositoryImpl @Inject constructor( - private val sharedWalkDataSource: SharedWalkDataSource -) : SharedWalkRepository { - override suspend fun getSharedWalkTrack(userId: Int, routeId: Int) : Result = runCatching { - sharedWalkDataSource.getSharedWalkTrack(userId, routeId).data.toEntity() - } - - override suspend fun postSharedWalkReviewRegister( - userId: Int, - review: SharedWalkReviewEntity - ): Result { - return runCatching { - sharedWalkDataSource.postSharedWalkReviewRegister(userId, review.toDto()).data - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/user/UserRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/user/UserRepositoryImpl.kt index 18834e85..671b2e5b 100644 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/user/UserRepositoryImpl.kt +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/user/UserRepositoryImpl.kt @@ -1,11 +1,14 @@ package com.paw.key.data.repositoryimpl.user import com.paw.key.core.util.suspendRunCatching +import com.paw.key.data.dto.request.user.UserWithDrawRequestDto import com.paw.key.data.dto.request.user.toDto import com.paw.key.data.remote.datasource.user.UserDataSource +import com.paw.key.domain.entity.petprofile.PetProfileEntity import com.paw.key.domain.entity.user.PetBreedsEntity import com.paw.key.domain.entity.user.UserInfoEntity import com.paw.key.domain.entity.user.UserInfoResultEntity +import com.paw.key.domain.entity.userprofile.UserProfileEntity import com.paw.key.domain.repository.user.UserRepository import javax.inject.Inject @@ -13,15 +16,37 @@ class UserRepositoryImpl @Inject constructor( private val userDataSource: UserDataSource ) : UserRepository { override suspend fun createUser(userInfoEntity: UserInfoEntity): Result = - suspendRunCatching{ + suspendRunCatching { userDataSource.createUser( dto = userInfoEntity.toDto() ).data.toEntity() } + override suspend fun deleteUser(provider: String): Result = + suspendRunCatching { + userDataSource.deleteUser( + dto = UserWithDrawRequestDto( + provider = provider + ) + ) + } + override suspend fun getPetBreeds(): Result = suspendRunCatching { userDataSource.getPetBreeds().data.toEntity() } -} \ No newline at end of file + override suspend fun getPetProfiles(petId: Int): Result = + suspendRunCatching { + userDataSource.getPetProfiles(petId).data.toEntity() + } + + override suspend fun getUserProfiles(): Result = + suspendRunCatching { + userDataSource.getUserProfiles().data.toEntity() + } + + override suspend fun checkNickname(nickname: String): Result = suspendRunCatching { + userDataSource.getNicknameDifference(nickname) + } +} diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/walk/WalkRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/walk/WalkRepositoryImpl.kt new file mode 100644 index 00000000..fd16b9e4 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/walk/WalkRepositoryImpl.kt @@ -0,0 +1,61 @@ +package com.paw.key.data.repositoryimpl.walk + +import com.paw.key.core.util.suspendRunCatching +import com.paw.key.data.dto.request.walk.WalkStartRequestDto +import com.paw.key.data.dto.request.walk.toDto +import com.paw.key.data.remote.datasource.walk.WalkDataSource +import com.paw.key.domain.entity.walk.WalkCompleteEntity +import com.paw.key.domain.entity.walk.WalkFinish +import com.paw.key.domain.entity.walk.WalkFinishEntity +import com.paw.key.domain.entity.walk.WalkPoint +import com.paw.key.domain.entity.walk.WalkStartEntity +import com.paw.key.domain.repository.walk.WalkRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +class WalkRepositoryImpl @Inject constructor( + private val dataSource: WalkDataSource, +) : WalkRepository { + private val _finishResult = MutableStateFlow(null) + override val finishResult =_finishResult.asStateFlow() + + private val _finishWalkInfo = MutableStateFlow(null) + override val finishWalkInfo = _finishWalkInfo.asStateFlow() + + override suspend fun startWalk(deviceInfo: String?): Result = + suspendRunCatching { + dataSource.startWalk( + dto = WalkStartRequestDto(deviceInfo = "ANDROID") + ).data.toEntity() + } + + override suspend fun pointWalk(walkPoint: WalkPoint): Result = + suspendRunCatching { + dataSource.pointWalk( + dto = walkPoint.toDto() + ).data + } + + override suspend fun finishWalk( + routeId: String, + walkFinish: WalkFinish + ): Result = suspendRunCatching { + val result = dataSource.finishWalk( + routeId = routeId, + dto = walkFinish.toDto() + ).data.toEntity() + + _finishWalkInfo.value = walkFinish + _finishResult.value = result + + result + } + + override suspend fun completeWalk(routeId: String): Result = + suspendRunCatching { + dataSource.completeWalk( + routeId = routeId + ).data.toEntity() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/walklist/WalkListDetailRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/walklist/WalkListDetailRepositoryImpl.kt deleted file mode 100644 index c326b663..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/walklist/WalkListDetailRepositoryImpl.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.paw.key.data.repositoryimpl.walklist - -import com.paw.key.data.remote.datasource.walklist.WalkListDetailDataSource -import com.paw.key.domain.entity.walklist.WalkListDetailEntity -import com.paw.key.domain.entity.walklist.WalkReviewSummaryEntity -import com.paw.key.domain.repository.walklist.WalkListRepository -import javax.inject.Inject - -class WalkListDetailRepositoryImpl @Inject constructor( - private val walkListDetailDataSource: WalkListDetailDataSource -) : WalkListRepository { - override suspend fun getWalkListDetail(userId: Int, postId: Int): Result { - return runCatching { - walkListDetailDataSource.getWalkListDetail(userId, postId).data.toEntity() - } - } - - override suspend fun getWalkTopPopular(userId: Int, postId: Int) : Result { - return runCatching { - walkListDetailDataSource.getWalkReviewSummary(userId, postId).data.toEntity() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/walkpreparation/WalkPreparationRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/walkpreparation/WalkPreparationRepositoryImpl.kt new file mode 100644 index 00000000..aa4ae0fd --- /dev/null +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/walkpreparation/WalkPreparationRepositoryImpl.kt @@ -0,0 +1,27 @@ +package com.paw.key.data.repositoryimpl.walkpreparation + +import com.paw.key.core.util.suspendRunCatching +import com.paw.key.data.dto.request.walkpreparation.toDto +import com.paw.key.data.remote.datasource.walkpreparation.WalkPreparationDataSource +import com.paw.key.domain.entity.walkpreparation.WalkPreparationEntity +import com.paw.key.domain.entity.walkpreparation.WalkPreparationMessageEntity +import com.paw.key.domain.repository.walkpreparation.WalkPreparationRepository +import javax.inject.Inject + +class WalkPreparationRepositoryImpl @Inject constructor( + private val walkPreparationDataSource: WalkPreparationDataSource +) : WalkPreparationRepository { + override suspend fun getWalkPreparation(): Result = suspendRunCatching{ + walkPreparationDataSource.getWalkPreparation().data.toEntity() + } + + override suspend fun patchWalkPreparation(entity: WalkPreparationEntity): Result = suspendRunCatching{ + walkPreparationDataSource.patchWalkPreparation( + body = entity.toDto() + ).data.toEntity() + } + + override suspend fun getWalkPreparationMessage(): Result = suspendRunCatching{ + walkPreparationDataSource.getWalkPreparationMessage().data.toEntity() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/walkreview/WalkReviewRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/walkreview/WalkReviewRepositoryImpl.kt deleted file mode 100644 index e353cdb8..00000000 --- a/app/src/main/java/com/paw/key/data/repositoryimpl/walkreview/WalkReviewRepositoryImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.paw.key.data.repositoryimpl.walkreview - -import com.paw.key.data.remote.datasource.walkreview.WalkReviewDataSource -import com.paw.key.domain.entity.walkreview.WalkReviewCategoryListEntity -import com.paw.key.domain.entity.walkreview.WalkReviewIdEntity -import com.paw.key.domain.entity.walkreview.WalkReviewInfoEntity -import com.paw.key.domain.entity.walkreview.WalkReviewRecordEntity -import com.paw.key.domain.repository.walkreview.WalkReviewRepository -import okhttp3.MultipartBody -import javax.inject.Inject - -class WalkReviewRepositoryImpl @Inject constructor( - private val dataSource: WalkReviewDataSource -) : WalkReviewRepository { - override suspend fun postWalkReview( - userId: Int, - imageFiles: List, - walkReviewRequest: WalkReviewRecordEntity - ): Result { - return runCatching { - dataSource.postWalkReview( - userId = userId, - imageFiles = imageFiles, - walkReviewRequestDto = walkReviewRequest.toDto() - ).data.toEntity() - } - } - - override suspend fun getWalkReviewInfo( - userId: Int, - routeId: Int - ): Result { - return runCatching { - dataSource.getWalkReviewInfo( - userId = userId, - routeId = routeId - ).data.toEntity() - } - } - - override suspend fun getWalkReviewCategory( - userId: Int - ): Result { - return runCatching { - dataSource.getWalkReviewCategory( - userId = userId - ).data.toEntity() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/PetProfileService.kt b/app/src/main/java/com/paw/key/data/service/PetProfileService.kt deleted file mode 100644 index 39585f8f..00000000 --- a/app/src/main/java/com/paw/key/data/service/PetProfileService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.paw.key.data.service - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.petprofile.PetProfileResponseDto -import retrofit2.http.GET -import retrofit2.http.Header - -interface PetProfileService { - @GET("users/me/pets") - suspend fun getPetProfiles( - @Header("X-USER-ID") userId: Int - ): BaseResponse> -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/UserProfileService.kt b/app/src/main/java/com/paw/key/data/service/UserProfileService.kt deleted file mode 100644 index 47aa5515..00000000 --- a/app/src/main/java/com/paw/key/data/service/UserProfileService.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.paw.key.data.service - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.petprofile.PetProfileResponseDto -import com.paw.key.data.dto.response.userprofile.UserProfileResponseDto -import retrofit2.http.GET -import retrofit2.http.Header - -interface UserProfileService { - @GET("users/me/userInfo") - suspend fun getUserProfiles( - @Header("X-USER-ID") userId: Int - ): BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/auth/ReissueService.kt b/app/src/main/java/com/paw/key/data/service/auth/ReissueService.kt new file mode 100644 index 00000000..44d997ae --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/auth/ReissueService.kt @@ -0,0 +1,13 @@ +package com.paw.key.data.service.auth + +import com.paw.key.data.dto.request.auth.AuthReissueRequestDto +import com.paw.key.data.dto.response.auth.AuthReissueResponseDto +import retrofit2.http.Body +import retrofit2.http.POST + +interface ReissueService { + @POST("auth/refresh") + suspend fun reissueToken( + @Body body: AuthReissueRequestDto, + ): AuthReissueResponseDto +} diff --git a/app/src/main/java/com/paw/key/data/service/filter/FilterOptionService.kt b/app/src/main/java/com/paw/key/data/service/filter/FilterOptionService.kt deleted file mode 100644 index 40469b82..00000000 --- a/app/src/main/java/com/paw/key/data/service/filter/FilterOptionService.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.paw.key.data.service.filter - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.filter.FilterOptionResponse -import retrofit2.http.GET -import retrofit2.http.Header - - -interface FilterOptionService { - @GET ("posts/filter") - suspend fun getFilterOptions( - @Header("X-USER-ID") userId: Int, - ): BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt b/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt index 4276b144..5dbf4415 100644 --- a/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt +++ b/app/src/main/java/com/paw/key/data/service/home/HomeRegionService.kt @@ -2,7 +2,9 @@ package com.paw.key.data.service.home import com.paw.key.data.dto.request.home.HomeRegionRequest import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.home.HomeRegionResponse +import com.paw.key.data.dto.response.home.HomeInfoResponseDto +import com.paw.key.data.dto.response.home.HomeRouteResponseDto +import com.paw.key.data.dto.response.home.HomeWeatherResponseDto import com.paw.key.data.dto.response.home.RegionCurrentResponseDto import retrofit2.http.Body import retrofit2.http.GET @@ -15,10 +17,25 @@ interface HomeRegionService { suspend fun patchRegion( @Header("X-USER-ID") userId: Int, @Body request: HomeRegionRequest, - ): HomeRegionResponse + ): BaseResponse @GET("regions/current") suspend fun regionCurrent( @Header("X-USER-ID") userId: Int ): BaseResponse + + @GET("home/info") + suspend fun getHomeInfo( + + ): BaseResponse + + @GET("home/weather") + suspend fun getHomeWeather( + + ): BaseResponse + + @GET("home/recommendation") + suspend fun getHomeRecommended( + + ): BaseResponse } diff --git a/app/src/main/java/com/paw/key/data/service/image/ImageService.kt b/app/src/main/java/com/paw/key/data/service/image/ImageService.kt index 8fa38b7e..8dec3572 100644 --- a/app/src/main/java/com/paw/key/data/service/image/ImageService.kt +++ b/app/src/main/java/com/paw/key/data/service/image/ImageService.kt @@ -5,12 +5,8 @@ import com.paw.key.data.dto.image.presigned.ImagePresignedResponseDto import com.paw.key.data.dto.image.register.ImageRegisterRequestDto import com.paw.key.data.dto.image.register.ImageRegisterResponseDto import com.paw.key.data.dto.response.BaseResponse -import okhttp3.RequestBody -import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST -import retrofit2.http.PUT -import retrofit2.http.Url interface ImageService { @POST("images/register") @@ -22,10 +18,4 @@ interface ImageService { suspend fun presignedImage( @Body body: ImagePresignedRequestDto ): BaseResponse - - @PUT - suspend fun uploadToS3( - @Url presignedUrl: String, - @Body file: RequestBody - ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/image/S3Service.kt b/app/src/main/java/com/paw/key/data/service/image/S3Service.kt new file mode 100644 index 00000000..707b96c8 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/image/S3Service.kt @@ -0,0 +1,15 @@ +package com.paw.key.data.service.image + +import okhttp3.RequestBody +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.PUT +import retrofit2.http.Url + +interface S3Service { + @PUT + suspend fun uploadToS3( + @Url presignedUrl: String, + @Body file: RequestBody + ): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/list/PostsListService.kt b/app/src/main/java/com/paw/key/data/service/list/PostsListService.kt deleted file mode 100644 index 54a2c0ae..00000000 --- a/app/src/main/java/com/paw/key/data/service/list/PostsListService.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.paw.key.data.service.list - -import com.paw.key.data.dto.request.list.PostsListRequestDto -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.list.PostsListResponseDto -import retrofit2.http.Body -import retrofit2.http.Header -import retrofit2.http.POST - -interface PostsListService { - @POST("posts/filter") - suspend fun postList( - @Header("X-USER-ID") userId: Int, - @Body request: PostsListRequestDto - ): BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/posts/PostsService.kt b/app/src/main/java/com/paw/key/data/service/posts/PostsService.kt new file mode 100644 index 00000000..23d24691 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/posts/PostsService.kt @@ -0,0 +1,55 @@ +package com.paw.key.data.service.posts + +import com.paw.key.data.dto.request.posts.PostsDataRequestDto +import com.paw.key.data.dto.request.posts.PostsFilterRequestDto +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.posts.CategoryListResponseDto +import com.paw.key.data.dto.response.posts.FilterOptionResponseDto +import com.paw.key.data.dto.response.posts.PostDetailResponseDto +import com.paw.key.data.dto.response.posts.PostsFilterListResponseDto +import com.paw.key.data.dto.response.posts.PostsResponseDto +import com.paw.key.data.dto.response.posts.PostsTop3ReviewResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface PostsService { + // 산책 게시물 등록 + @POST("posts") + suspend fun postPosts( + @Body request: PostsDataRequestDto + ): BaseResponse + + // Todo : 게시물 수정, 삭제 3스 때.. + + @GET("posts/categories") + suspend fun getCategories(): BaseResponse // 게시물 작성할 때 필터링 카테고리 조회 + + @GET("posts/{postId}") + suspend fun getPostsDetail( + @Path("postId") postId: Int + ): BaseResponse + + @GET("posts/{routeId}/reviews/top3") + suspend fun getTop3Reviews( + @Path("routeId") routeId: Int + ): BaseResponse + + @POST("posts/filter") + suspend fun getPostsFilter( + @Query("sortBy") sortBy: String = "latest", + @Query("cursor") cursor: String? = null, + @Query("size") size: Int = 10, + @Body request: PostsFilterRequestDto + ): BaseResponse + + @GET("posts/filter") + suspend fun getCategoriesFilter(): BaseResponse // 커뮤니티 화면에서 필터링 카테고리 조회 + + @POST("posts/{postId}/likes") + suspend fun postLike( + @Path("postId") postId: Int + ): BaseResponse +} diff --git a/app/src/main/java/com/paw/key/data/service/region/RegionService.kt b/app/src/main/java/com/paw/key/data/service/region/RegionService.kt index ee1102a5..379e8cc9 100644 --- a/app/src/main/java/com/paw/key/data/service/region/RegionService.kt +++ b/app/src/main/java/com/paw/key/data/service/region/RegionService.kt @@ -5,12 +5,10 @@ import com.paw.key.data.dto.response.region.DistrictDataDto import com.paw.key.data.dto.response.region.RegionResponseDto import retrofit2.http.GET import retrofit2.http.Path -import retrofit2.http.Query interface RegionService { @GET("regions/{regionId}/geometry") suspend fun getRegionGeometry( - @Query("userId") userId: Int, @Path("regionId") regionId: Int, ): BaseResponse diff --git a/app/src/main/java/com/paw/key/data/service/sharedwalk/SharedWalkService.kt b/app/src/main/java/com/paw/key/data/service/sharedwalk/SharedWalkService.kt index 554355a7..f82a171a 100644 --- a/app/src/main/java/com/paw/key/data/service/sharedwalk/SharedWalkService.kt +++ b/app/src/main/java/com/paw/key/data/service/sharedwalk/SharedWalkService.kt @@ -2,7 +2,6 @@ package com.paw.key.data.service.sharedwalk import com.paw.key.data.dto.request.sharedwalk.SharedWalkReviewRequestDto import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.sharedwalk.SharedWalkResponseDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -14,7 +13,7 @@ interface SharedWalkService { suspend fun getSharedWalkTrack( @Header("X-USER-ID") userId: Int, @Path("routeId") routeId: Int, - ) : BaseResponse + ) : BaseResponse @POST("reviews") suspend fun postSharedWalkReview( diff --git a/app/src/main/java/com/paw/key/data/service/user/UserService.kt b/app/src/main/java/com/paw/key/data/service/user/UserService.kt index a2ac3095..288cf0d2 100644 --- a/app/src/main/java/com/paw/key/data/service/user/UserService.kt +++ b/app/src/main/java/com/paw/key/data/service/user/UserService.kt @@ -1,12 +1,19 @@ package com.paw.key.data.service.user import com.paw.key.data.dto.request.user.UserInfoRequestDto +import com.paw.key.data.dto.request.user.UserWithDrawRequestDto import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.petprofile.PetProfileResponseDto import com.paw.key.data.dto.response.user.PetBreedsResponseDto import com.paw.key.data.dto.response.user.UserInfoResponseDto +import com.paw.key.data.dto.response.userprofile.UserProfileResponseDto +import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.HTTP import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query interface UserService { @POST("users") @@ -14,7 +21,24 @@ interface UserService { @Body body: UserInfoRequestDto ): BaseResponse + @HTTP(method = "DELETE", path = "auth/withdraw", hasBody = true) + suspend fun deleteUser( + @Body request: UserWithDrawRequestDto + ) : Response + @GET("pets/breeds") suspend fun getPetBreeds(): BaseResponse -} \ No newline at end of file + @GET("pets/{petId}") + suspend fun getPetProfiles( + @Path("petId") petId: Int + ): BaseResponse + + @GET("users/me/userInfo") + suspend fun getUserProfiles(): BaseResponse + + @GET("users") + suspend fun getNicknameDifference( + @Query("nickname") nickname: String + ): BaseResponse +} diff --git a/app/src/main/java/com/paw/key/data/service/walk/WalkService.kt b/app/src/main/java/com/paw/key/data/service/walk/WalkService.kt new file mode 100644 index 00000000..c1fd064b --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/walk/WalkService.kt @@ -0,0 +1,39 @@ +package com.paw.key.data.service.walk + +import com.paw.key.data.dto.request.walk.WalkFinishRequestDto +import com.paw.key.data.dto.request.walk.WalkPointRequestDto +import com.paw.key.data.dto.request.walk.WalkStartRequestDto +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.walk.WalkCompleteResponseDto +import com.paw.key.data.dto.response.walk.WalkFinishResponseDto +import com.paw.key.data.dto.response.walk.WalkStartResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + +interface WalkService { + @POST("walks/stream/start") + suspend fun startWalk( + @Body body : WalkStartRequestDto + ) : BaseResponse + + @POST("walks/stream/point") + suspend fun pointWalk( + @Body body : WalkPointRequestDto + ) : BaseResponse + + @POST("routes/{routeId}/finish") + suspend fun finishWalk( + @Path("routeId") routeId : String, + @Body body : WalkFinishRequestDto + ) : BaseResponse + + // 산책 완료 후 complete용 좌표 + @GET("routes/{routeId}/geometry") + suspend fun getRouteGeometry( + @Path("routeId") routeId : String + ) : BaseResponse + + +} diff --git a/app/src/main/java/com/paw/key/data/service/walkcourse/WalkCourseService.kt b/app/src/main/java/com/paw/key/data/service/walkcourse/WalkCourseService.kt deleted file mode 100644 index 47b137aa..00000000 --- a/app/src/main/java/com/paw/key/data/service/walkcourse/WalkCourseService.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.paw.key.data.service.walkcourse - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.walkcourse.WalkCourseResponseDto -import okhttp3.MultipartBody -import okhttp3.RequestBody -import retrofit2.http.Header -import retrofit2.http.Multipart -import retrofit2.http.POST -import retrofit2.http.Part - -interface WalkCourseService { - @Multipart - @POST("routes") - suspend fun postWalkCourse( - @Header("X-USER-ID") userId: Int, - @Part trackingImage: MultipartBody.Part, - @Part("routeRequest") routeRequest: RequestBody - ): BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/walklist/WalkListDetailService.kt b/app/src/main/java/com/paw/key/data/service/walklist/WalkListDetailService.kt deleted file mode 100644 index f3d02cd3..00000000 --- a/app/src/main/java/com/paw/key/data/service/walklist/WalkListDetailService.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.paw.key.data.service.walklist - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.walklist.WalkReviewDetailResponseDto -import com.paw.key.data.dto.response.walklist.WalkReviewSummaryResponseDto -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Path - -interface WalkListDetailService { - @GET("posts/{postId}") - suspend fun getWalkListDetail( - @Header("X-USER-ID") userId: Int, - @Path("postId") postId: Int - ): BaseResponse - - @GET("posts/{routeId}/reviews/top") - suspend fun getWalkReviewSummary( - @Header("X-USER-ID") userId: Int, - @Path("routeId") routeId: Int - ): BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/walkpreparation/WalkPreparationService.kt b/app/src/main/java/com/paw/key/data/service/walkpreparation/WalkPreparationService.kt new file mode 100644 index 00000000..885e686e --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/walkpreparation/WalkPreparationService.kt @@ -0,0 +1,23 @@ +package com.paw.key.data.service.walkpreparation + +import com.paw.key.data.dto.request.walkpreparation.WalkPreparationRequestDto +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.walkpreparation.WalkPreparationMessageResponseDto +import com.paw.key.data.dto.response.walkpreparation.WalkPreparationResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.POST + +interface WalkPreparationService { + @GET("walk/preparation") + suspend fun getWalkPreparation() : BaseResponse + + @PATCH("walk/preparation") + suspend fun patchWalkPreparation( + @Body body : WalkPreparationRequestDto + ) : BaseResponse + + @GET("walk/preparation/message") + suspend fun getWalkPreparationMessage() : BaseResponse +} diff --git a/app/src/main/java/com/paw/key/data/service/walkreview/WalkReviewService.kt b/app/src/main/java/com/paw/key/data/service/walkreview/WalkReviewService.kt deleted file mode 100644 index 68df59ca..00000000 --- a/app/src/main/java/com/paw/key/data/service/walkreview/WalkReviewService.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.paw.key.data.service.walkreview - -import com.paw.key.data.dto.response.BaseResponse -import com.paw.key.data.dto.response.walkreview.WalkReviewCategoryResponseDto -import com.paw.key.data.dto.response.walkreview.WalkReviewInfoResponseDto -import com.paw.key.data.dto.response.walkreview.WalkReviewResponseDto -import okhttp3.MultipartBody -import okhttp3.RequestBody -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Multipart -import retrofit2.http.POST -import retrofit2.http.Part -import retrofit2.http.Path - -interface WalkReviewService { - @Multipart - @POST("posts") - suspend fun postWalkReview( - @Header("X-USER-ID") userId: Int, - @Part imageFiles: List, - @Part("data") data: RequestBody - ): BaseResponse - - @GET("posts/categories") - suspend fun getWalkReviewCategory( - @Header("X-USER-ID") userId: Int, - ): BaseResponse - - @GET("routes/{routeId}/info") - suspend fun getWalkReviewInfo( - @Header("X-USER-ID") userId: Int, - @Path("routeId") routeId: Int - ): BaseResponse -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/filter/FilterEntity.kt b/app/src/main/java/com/paw/key/domain/entity/filter/FilterEntity.kt deleted file mode 100644 index 89cf115b..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/filter/FilterEntity.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.paw.key.domain.entity.filter - -data class FilterEntity( - val selectList: List? = null, - val categoryList: List? = null -) - -data class SelectOption( - val selectId: Int = 0, - val selectName: String = "", - val options: List? = null -) - -data class SelectOptionItem( - val selectOptionId: Int = 0, - val selectText: String = "" -) - -data class Category( - val categoryId: Int = 0, - val categoryName: String = "", - val categoryDescription: String? = null, - val categoryOptions: List? = null -) - -data class CategoryOption( - val categoryOptionId: Int = 0, - val categoryOptionText: String = "" -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/home/HomeInfoEntity.kt b/app/src/main/java/com/paw/key/domain/entity/home/HomeInfoEntity.kt new file mode 100644 index 00000000..0ad72408 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/home/HomeInfoEntity.kt @@ -0,0 +1,7 @@ +package com.paw.key.domain.entity.home + +data class HomeInfoEntity( + val distance: Double, + val totalTime: Int, + val count: Int +) diff --git a/app/src/main/java/com/paw/key/domain/entity/home/HomeRouteEntity.kt b/app/src/main/java/com/paw/key/domain/entity/home/HomeRouteEntity.kt new file mode 100644 index 00000000..2ea63f5c --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/home/HomeRouteEntity.kt @@ -0,0 +1,17 @@ +package com.paw.key.domain.entity.home + +data class HomeRouteEntity( + val popularRoutes: List, // 사용자 거주 지역 내 인기 리스트 + val similarUserRoutes: List +) + +data class RouteEntity( + val routeId: Long, // 산책 루트의 고유 ID + val postId: Long, // 게시물 고유 Id + val regionName: String, // 루트가 속한 지역명 (구, 동 등) + val title: String, // 루트에 대한 게시물의 제목 + val date: String, // 게시글의 마지막 기록 날짜 + val duration: Int, // 총 산책 소요 시간 + val isLiked: Boolean, // 현재 사용자가 해당 루트에 좋아요를 눌렀는지 여부 + val imageUrl: String // 루트 대표 이미지의 URL (CDN) +) diff --git a/app/src/main/java/com/paw/key/domain/entity/home/HomeWeatherEntity.kt b/app/src/main/java/com/paw/key/domain/entity/home/HomeWeatherEntity.kt new file mode 100644 index 00000000..0b123f8a --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/home/HomeWeatherEntity.kt @@ -0,0 +1,7 @@ +package com.paw.key.domain.entity.home + +data class HomeWeatherEntity( + val temperature: Int, + val rainyMm: Int, + val region: String +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/list/ListEntity.kt b/app/src/main/java/com/paw/key/domain/entity/list/ListEntity.kt deleted file mode 100644 index 3514c634..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/list/ListEntity.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.paw.key.domain.entity.list - -data class ListEntity( - val posts: List -) - -data class PostEntity( - val postId: Int, - val createdAt: String, - val isLike: Boolean, - val isMine: Boolean, - val isPublic: Boolean, - val title: String, - val representativeImageUrl: String, - val routeId: Int, - val writer: WriterEntity, - val descriptionTags: List -) - -data class WriterEntity( - val userId: Int, - val petName: String, - val petProfileImageUrl: String -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt b/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt index 01958ec7..8d68a59b 100644 --- a/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt +++ b/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt @@ -1,21 +1,14 @@ package com.paw.key.domain.entity.petprofile -import android.net.Uri - data class PetProfileEntity( val petId: Long, + val imageUrl: String, val name: String, + val birth: String, + val age: String, val gender: String, val isNeutered: Boolean, - val age: Int, - val isAgeKnown: Boolean, val breed: String, - val imageUrl: Uri, - val traits: List, - val walkCount: Int + val dbtiName: String?, + val dbtiDescription: String?, ) - -data class TraitEntity( - val category: String, - val option: String -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/posts/PostsDetailEntity.kt b/app/src/main/java/com/paw/key/domain/entity/posts/PostsDetailEntity.kt new file mode 100644 index 00000000..d24c7dfe --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/posts/PostsDetailEntity.kt @@ -0,0 +1,33 @@ +package com.paw.key.domain.entity.posts + +data class PostsDetailEntity( + val postId: Int, + val title: String, + val description: String, + val isPublic: Boolean, + val isMine: Boolean, + val authorInfo: AuthorInfoEntity, + val routeDisplay: RouteDisplayEntity, + val categoryTagTexts: List, + val walkImages: List +) + +data class AuthorInfoEntity( + val authorId: Int, + val petId: Int, + val petName: String, + val petProfileImage: String +) + +data class RouteDisplayEntity( + val routeId: Int, + val locationText: String, + val dateTimeText: String, + val metaTagTexts: List, + val routeImageUrl: String +) + +data class WalkImageEntity( + val imageId: Int, + val imageUrl: String +) diff --git a/app/src/main/java/com/paw/key/domain/entity/posts/PostsEntity.kt b/app/src/main/java/com/paw/key/domain/entity/posts/PostsEntity.kt new file mode 100644 index 00000000..ab019057 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/posts/PostsEntity.kt @@ -0,0 +1,17 @@ +package com.paw.key.domain.entity.posts + +data class PostsEntity( + val posts: List, + val nextCursor: String?, + val hasNext: Boolean +) + +data class PostEntity( + val postId: Int, + val regionName: String, + val title: String, + val date: String, + val durationMinutes: Int, + val isLiked: Boolean, + val imageUrl: String?, +) diff --git a/app/src/main/java/com/paw/key/domain/entity/posts/PostsFilterEntity.kt b/app/src/main/java/com/paw/key/domain/entity/posts/PostsFilterEntity.kt new file mode 100644 index 00000000..c93e926e --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/posts/PostsFilterEntity.kt @@ -0,0 +1,34 @@ +package com.paw.key.domain.entity.posts + +data class PostsCategoryEntity( + val categoryList: List +) + +data class PostsFilterEntity( + val durationList: List, + val categoryList: List +) + +data class FilterItemEntity( + val id: Int, + val name: String, + val selectionType: String, + val options: List +) + +data class FilterOptionEntity( + val id: Int, + val text: String +) + +data class FilterSelectedItemEntity( + val selectedOptions: List +) { + +} + +data class FilterSelectedIOptionEntity( + val durationId: Int? = null, + val categoryId: Int? = null, + val optionsIds: List +) diff --git a/app/src/main/java/com/paw/key/domain/entity/posts/PostsInfoEntity.kt b/app/src/main/java/com/paw/key/domain/entity/posts/PostsInfoEntity.kt new file mode 100644 index 00000000..fa300ff0 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/posts/PostsInfoEntity.kt @@ -0,0 +1,22 @@ +package com.paw.key.domain.entity.posts + +data class PostsInfoEntity( + val title: String, + val description: String, + val isPublic: Boolean, + val routeId: Int, + val routeImageId: Int, + val walkImageIds: List, + val selectedOptionsForCategories: List, + val imageUrls: List +) + +data class CategoryOptionEntity( + val categoryId: Int, + val selectedOptionIds: List +) + +data class PostsResultEntity( + val postId: Int, + val routeId: Int, +) diff --git a/app/src/main/java/com/paw/key/domain/entity/posts/PostsTop3Entity.kt b/app/src/main/java/com/paw/key/domain/entity/posts/PostsTop3Entity.kt new file mode 100644 index 00000000..4f1055d4 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/posts/PostsTop3Entity.kt @@ -0,0 +1,15 @@ +package com.paw.key.domain.entity.posts + +data class PostsTop3Entity( + val totalReviewCount: Int, + val totalSelectionSum: Int, + val top3ReviewOptions: List +) + +data class ReviewOptionEntity( + val reviewOptionId: Int, + val reviewOptionName: String, + val selectedCount: Int, + val percentage: Double, + val rank: Int +) diff --git a/app/src/main/java/com/paw/key/domain/entity/region/RegionEntity.kt b/app/src/main/java/com/paw/key/domain/entity/region/RegionEntity.kt index 6d2e2331..f871a8b7 100644 --- a/app/src/main/java/com/paw/key/domain/entity/region/RegionEntity.kt +++ b/app/src/main/java/com/paw/key/domain/entity/region/RegionEntity.kt @@ -1,8 +1,8 @@ package com.paw.key.domain.entity.region data class RegionDataEntity( - val regionName: String, - val preRegionName : String, + val regionId: Int, + val regionName : String, val geometry: GeometryEntity ) diff --git a/app/src/main/java/com/paw/key/domain/entity/userprofile/UserProfileEntity.kt b/app/src/main/java/com/paw/key/domain/entity/userprofile/UserProfileEntity.kt index e4999c35..d4e130f0 100644 --- a/app/src/main/java/com/paw/key/domain/entity/userprofile/UserProfileEntity.kt +++ b/app/src/main/java/com/paw/key/domain/entity/userprofile/UserProfileEntity.kt @@ -1,8 +1,8 @@ -package com.paw.key.domain.model.entity.uerprofile +package com.paw.key.domain.entity.userprofile data class UserProfileEntity( val name: String, - val gender: String, - val age: Int, - val activeRegion: String -) \ No newline at end of file + val email: String, + val birth: String?, + val gender: String +) diff --git a/app/src/main/java/com/paw/key/domain/entity/walk/WalkCompleteEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walk/WalkCompleteEntity.kt new file mode 100644 index 00000000..8bfc1e7b --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/walk/WalkCompleteEntity.kt @@ -0,0 +1,11 @@ +package com.paw.key.domain.entity.walk + +data class WalkCompleteEntity( + val routeId: String, + val geometry: WalkCompleteGeometryEntity +) + +data class WalkCompleteGeometryEntity( + val type: String, + val coordinates: List> +) diff --git a/app/src/main/java/com/paw/key/domain/entity/walk/WalkFinishEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walk/WalkFinishEntity.kt new file mode 100644 index 00000000..2e0f872c --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/walk/WalkFinishEntity.kt @@ -0,0 +1,23 @@ +package com.paw.key.domain.entity.walk + +data class WalkFinishEntity( + val routeId: Int, + val petProfile: WalkPetProfileEntity, + val walkInfo: WalkInfoEntity +) + +data class WalkPetProfileEntity( + val petName: String, + val petProfileImageUrl: String +) + +data class WalkInfoEntity( + val startAt: String +) + +data class WalkFinish( + val distance: Int, + val duration: Int, + val stepCount: Int, + val endedAt: String +) diff --git a/app/src/main/java/com/paw/key/domain/entity/walk/WalkPointEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walk/WalkPointEntity.kt new file mode 100644 index 00000000..67e254e3 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/walk/WalkPointEntity.kt @@ -0,0 +1,8 @@ +package com.paw.key.domain.entity.walk + +data class WalkPoint( + val routeId: String, + val lat: Double, + val lng: Double, + val timestamp: Int +) diff --git a/app/src/main/java/com/paw/key/domain/entity/walk/WalkStartEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walk/WalkStartEntity.kt new file mode 100644 index 00000000..d871b7eb --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/walk/WalkStartEntity.kt @@ -0,0 +1,6 @@ +package com.paw.key.domain.entity.walk + +data class WalkStartEntity( + val routeId: String, + val issuedAt: Long +) diff --git a/app/src/main/java/com/paw/key/domain/entity/walkcourse/WalkCourseEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walkcourse/WalkCourseEntity.kt deleted file mode 100644 index 57b5d3c0..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walkcourse/WalkCourseEntity.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.paw.key.domain.entity.walkcourse - -import com.paw.key.data.dto.request.walkcourse.CoordinateDto -import com.paw.key.data.dto.request.walkcourse.WalkCourseRequestDto - -data class WalkCourseEntity ( - val coordinates: List, - val distance: Int, - val duration: Int, - val startedAt: String, - val endedAt: String, - val stepCount: Int -) { - fun toDto(): WalkCourseRequestDto { - return WalkCourseRequestDto( - coordinates = coordinates.map { it.toDto() }, - distance = distance, - duration = duration, - startedAt = startedAt, - endedAt = endedAt, - stepCount = stepCount - ) - } -} - -data class CoordinateEntity( - val latitude: Double, - val longitude: Double -) { - fun toDto(): CoordinateDto { - return CoordinateDto( - latitude = latitude, - longitude = longitude - ) - } -} - -data class WalkCourseRegionIdEntity( - val regionId: Int -) - diff --git a/app/src/main/java/com/paw/key/domain/entity/walklist/WalkListDetailEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walklist/WalkListDetailEntity.kt deleted file mode 100644 index 679deac7..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walklist/WalkListDetailEntity.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.paw.key.domain.entity.walklist - -data class WalkListDetailEntity( - val postId: Int, - val routeId: Int, - val title: String, - val content: String, - val isLike: Boolean, - val authorInfo: AuthorInfoEntity, - val categoryTags: CategoryTagsEntity, - val regionName: String, - val createdAt: String, - val routeMapImageUrl: String, - val walkingImageUrls: List -) - -data class AuthorInfoEntity( - val authorId: Int, - val petId: Int, - val petName: String, - val petProfileImage: String -) - -data class CategoryTagsEntity( - val categoryOptionSummary: List -) diff --git a/app/src/main/java/com/paw/key/domain/entity/walklist/WalkReviewSummaryEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walklist/WalkReviewSummaryEntity.kt deleted file mode 100644 index 9dcaff7e..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walklist/WalkReviewSummaryEntity.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.paw.key.domain.entity.walklist - -data class WalkReviewSummaryEntity( - val postId: Int, - val totalReviewCount: Int, - val categoryTop3: List -) - -data class CategoryTop3Entity( - val categoryId: Int, - val categoryName: String, - val categoryOptionId: Int, - val optionText: String, - val rank: Int, - val percentage: Int -) diff --git a/app/src/main/java/com/paw/key/domain/entity/walkpreparation/WalkPreparationEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walkpreparation/WalkPreparationEntity.kt new file mode 100644 index 00000000..9b24a467 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/walkpreparation/WalkPreparationEntity.kt @@ -0,0 +1,10 @@ +package com.paw.key.domain.entity.walkpreparation + +data class WalkPreparationEntity( + val preparationList: List +) + +data class WalkPreparationMessageEntity( + val mainMessage: String, + val subMessage: String +) diff --git a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewCategoryEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewCategoryEntity.kt deleted file mode 100644 index e560408d..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewCategoryEntity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.paw.key.domain.entity.walkreview - -data class WalkReviewCategoryListEntity( - val categoryList : List -) - -data class WalkReviewCategoryEntity( - val categoryId : Int, - val categoryDescription : String, - val categoryName : String, - val options : List -) - -data class WalkReviewOptionOptionsResponseEntity( - val categoryOptionId : Int, - val optionText : String -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewEntity.kt deleted file mode 100644 index 539f70eb..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewEntity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.paw.key.domain.entity.walkreview - -data class WalkReviewInfoEntity( - val routeDto : WalkReviewRouteInfoEntity, - val petName : String, -) - -data class WalkReviewRouteInfoEntity( - val id : Int, - val locationDescription : String, - val dateDescription : String, - val descriptionTags : List, -) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewIdEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewIdEntity.kt deleted file mode 100644 index 7bb79b92..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewIdEntity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.paw.key.domain.entity.walkreview - -data class WalkReviewIdEntity( - val postId: Int, - val routeId : Int -) diff --git a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewRecordEntity.kt b/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewRecordEntity.kt deleted file mode 100644 index 6609ebf1..00000000 --- a/app/src/main/java/com/paw/key/domain/entity/walkreview/WalkReviewRecordEntity.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.paw.key.domain.entity.walkreview - -import com.paw.key.data.dto.request.walkreview.SelectedCategoryDto -import com.paw.key.data.dto.request.walkreview.WalkCourseReviewRequestDto - -data class WalkReviewRecordEntity( - val title: String, - val description: String, - val isPublic: Boolean, - val isMine: Boolean, - val categories: List, - val routeId: Long -) { - fun toDto(): WalkCourseReviewRequestDto { - return WalkCourseReviewRequestDto( - title = title, - description = description, - isPublic = isPublic, - selectedCategories = categories.map { category -> - SelectedCategoryDto( - categoryId = category.categoryId, - selectedOptionIds = category.selectedOptionIds - ) - }, - isMine = isMine, - routeId = routeId, - ) - } -} - -data class WalkReviewRecordCategory( - val categoryId: Int, - val selectedOptionIds: List -) { - fun toDto(): SelectedCategoryDto { - return SelectedCategoryDto( - categoryId = categoryId, - selectedOptionIds = selectedOptionIds - ) - } -} \ 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 a72c8386..9dfc54bf 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 @@ -4,6 +4,6 @@ import com.paw.key.domain.entity.region.RegionDataEntity import com.paw.key.domain.entity.signup.DistrictEntity interface RegionRepository { - suspend fun getRegionGeometry(userId: Int, regionId: Int): Result + suspend fun getRegionGeometry(regionId: Int): Result suspend fun getRegionList(): Result> } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/filter/FilterOptionRepository.kt b/app/src/main/java/com/paw/key/domain/repository/filter/FilterOptionRepository.kt deleted file mode 100644 index 01fe2fb1..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/filter/FilterOptionRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.paw.key.domain.repository.filter - -import com.paw.key.domain.entity.filter.FilterEntity - - -interface FilterOptionRepository { - suspend fun getFilterOptions(userId: Int): Result -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/home/HomeRegionRepository.kt b/app/src/main/java/com/paw/key/domain/repository/home/HomeRegionRepository.kt deleted file mode 100644 index 5f563ace..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/home/HomeRegionRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.paw.key.domain.repository.home - -import com.paw.key.domain.entity.home.HomeRegionDataEntity - -interface HomeRegionRepository { - suspend fun patchRegion(userId: Int, regionId: Int): Result -} diff --git a/app/src/main/java/com/paw/key/domain/repository/home/HomeRepository.kt b/app/src/main/java/com/paw/key/domain/repository/home/HomeRepository.kt new file mode 100644 index 00000000..842481b6 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/home/HomeRepository.kt @@ -0,0 +1,16 @@ +package com.paw.key.domain.repository.home + +import com.paw.key.domain.entity.home.HomeInfoEntity +import com.paw.key.domain.entity.home.HomeRegionDataEntity +import com.paw.key.domain.entity.home.HomeRouteEntity +import com.paw.key.domain.entity.home.HomeWeatherEntity + +interface HomeRepository { + suspend fun patchRegion(userId: Int, regionId: Int): Result + + suspend fun getHomeInfo(): Result + + suspend fun getHomeWeather(): Result + + suspend fun getHomeRecommended(): Result +} diff --git a/app/src/main/java/com/paw/key/domain/repository/list/PostsListRepository.kt b/app/src/main/java/com/paw/key/domain/repository/list/PostsListRepository.kt deleted file mode 100644 index c7cea381..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/list/PostsListRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.paw.key.domain.repository.list - -import com.paw.key.data.dto.request.list.PostsListRequestDto -import com.paw.key.domain.entity.list.ListEntity - -interface PostsListRepository { - suspend fun postList(userId: Int, request: PostsListRequestDto): Result - suspend fun getAllPosts(userId: Int): Result -} diff --git a/app/src/main/java/com/paw/key/domain/repository/localstorage/LocalStorageRepository.kt b/app/src/main/java/com/paw/key/domain/repository/localstorage/LocalStorageRepository.kt index eaa37ea6..90dceab8 100644 --- a/app/src/main/java/com/paw/key/domain/repository/localstorage/LocalStorageRepository.kt +++ b/app/src/main/java/com/paw/key/domain/repository/localstorage/LocalStorageRepository.kt @@ -10,10 +10,15 @@ interface LocalStorageRepository { // 사용자 정보 관련 suspend fun saveUserId(userId: Int) suspend fun getUserId(): Int + suspend fun saveUserProvider(provider: String) + suspend fun getUserProvider(): String // 펫 정보 관련 suspend fun savePetId(petId: Int) suspend fun getPetId(): Int + suspend fun savePetName(petName: String) + suspend fun getPetName(): String + // 기기 정보 관련 suspend fun saveDeviceId(deviceId: String) @@ -21,4 +26,4 @@ interface LocalStorageRepository { // 전체 초기화 (로그아웃/탈퇴 시) suspend fun clearInfo() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt b/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt index bbf771d4..84f9909b 100644 --- a/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt +++ b/app/src/main/java/com/paw/key/domain/repository/login/AuthRepository.kt @@ -8,4 +8,4 @@ interface AuthRepository { suspend fun signInWithKakao(context: Context): Result suspend fun login(idToken: String, deviceId: String): Result suspend fun loginKakao(idToken: String, deviceId: String): Result -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/domain/repository/posts/PostsRepository.kt b/app/src/main/java/com/paw/key/domain/repository/posts/PostsRepository.kt new file mode 100644 index 00000000..aa2c7265 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/posts/PostsRepository.kt @@ -0,0 +1,32 @@ +package com.paw.key.domain.repository.posts + +import com.paw.key.domain.entity.posts.FilterSelectedItemEntity +import com.paw.key.domain.entity.posts.PostsCategoryEntity +import com.paw.key.domain.entity.posts.PostsDetailEntity +import com.paw.key.domain.entity.posts.PostsEntity +import com.paw.key.domain.entity.posts.PostsFilterEntity +import com.paw.key.domain.entity.posts.PostsInfoEntity +import com.paw.key.domain.entity.posts.PostsResultEntity +import com.paw.key.domain.entity.posts.PostsTop3Entity + +interface PostsRepository { + suspend fun postPosts(postsInfo: PostsInfoEntity): Result // 게시물 등록 + + suspend fun getPostsDetail(postId: Int): Result // 상세 조회 + + suspend fun getPostsFilter( // route 리스트 조회 + sortBy: String = "latest", // popular + cursor: String? = null, + size: Int = 10, + postsFilter : FilterSelectedItemEntity + ): Result + + /** 커뮤니티 화면에서 필터링 카테고리 조회 */ + suspend fun getCategoriesFilter(): Result + + suspend fun postLike(postId: Int): Result + + suspend fun getTop3Reviews(routeId: Int): Result + + suspend fun getCategories(): Result // 사용자가 게시물 작성할 때 사용 +} diff --git a/app/src/main/java/com/paw/key/domain/repository/sharedwalk/SharedWalkRepository.kt b/app/src/main/java/com/paw/key/domain/repository/sharedwalk/SharedWalkRepository.kt deleted file mode 100644 index a5ae998b..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/sharedwalk/SharedWalkRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paw.key.domain.repository.sharedwalk - -import com.paw.key.domain.entity.sharedwalk.SharedWalkEntity -import com.paw.key.domain.entity.sharedwalk.SharedWalkReviewEntity - -interface SharedWalkRepository { - suspend fun getSharedWalkTrack(userId: Int, routeId: Int): Result - - suspend fun postSharedWalkReviewRegister(userId: Int, review: SharedWalkReviewEntity): Result -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/user/UserRepository.kt b/app/src/main/java/com/paw/key/domain/repository/user/UserRepository.kt index 09211200..a99ae10b 100644 --- a/app/src/main/java/com/paw/key/domain/repository/user/UserRepository.kt +++ b/app/src/main/java/com/paw/key/domain/repository/user/UserRepository.kt @@ -1,13 +1,25 @@ package com.paw.key.domain.repository.user +import com.paw.key.domain.entity.petprofile.PetProfileEntity import com.paw.key.domain.entity.user.PetBreedsEntity import com.paw.key.domain.entity.user.UserInfoEntity import com.paw.key.domain.entity.user.UserInfoResultEntity +import com.paw.key.domain.entity.userprofile.UserProfileEntity interface UserRepository { suspend fun createUser( userInfoEntity: UserInfoEntity ): Result + suspend fun deleteUser( + provider: String + ): Result + suspend fun getPetBreeds(): Result + + suspend fun getPetProfiles(petId: Int): Result + + suspend fun getUserProfiles(): Result + + suspend fun checkNickname(nickname: String): Result } diff --git a/app/src/main/java/com/paw/key/domain/repository/userprofile/UserProfileRepository.kt b/app/src/main/java/com/paw/key/domain/repository/userprofile/UserProfileRepository.kt deleted file mode 100644 index 3a08ba7c..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/userprofile/UserProfileRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.paw.key.domain.repository.userprofile - -import com.paw.key.domain.model.entity.uerprofile.UserProfileEntity - -interface UserProfileRepository { - suspend fun getUserProfiles(userId: Int): Result -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/walk/WalkRepository.kt b/app/src/main/java/com/paw/key/domain/repository/walk/WalkRepository.kt new file mode 100644 index 00000000..647b2c23 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/walk/WalkRepository.kt @@ -0,0 +1,30 @@ +package com.paw.key.domain.repository.walk + +import com.paw.key.domain.entity.walk.WalkCompleteEntity +import com.paw.key.domain.entity.walk.WalkFinish +import com.paw.key.domain.entity.walk.WalkFinishEntity +import com.paw.key.domain.entity.walk.WalkPoint +import com.paw.key.domain.entity.walk.WalkStartEntity +import kotlinx.coroutines.flow.StateFlow + +interface WalkRepository { + suspend fun startWalk( + deviceInfo: String? + ) : Result + + suspend fun pointWalk( + walkPoint: WalkPoint + ) : Result + + suspend fun finishWalk( + routeId: String, + walkFinish: WalkFinish + ) : Result + + suspend fun completeWalk( + routeId: String + ) : Result + + val finishResult: StateFlow + val finishWalkInfo: StateFlow +} diff --git a/app/src/main/java/com/paw/key/domain/repository/walkcourse/WalkCourseRepository.kt b/app/src/main/java/com/paw/key/domain/repository/walkcourse/WalkCourseRepository.kt deleted file mode 100644 index 4901fef0..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/walkcourse/WalkCourseRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.paw.key.domain.repository.walkcourse - -import com.paw.key.data.dto.request.walkcourse.WalkCourseRequestDto -import com.paw.key.domain.entity.walkcourse.WalkCourseRegionIdEntity -import okhttp3.MultipartBody - -interface WalkCourseRepository { - suspend fun postWalkCourse( - userId: Int, - image: MultipartBody.Part, - routeRequestDto: WalkCourseRequestDto - ): Result -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/walklist/WalkListRepository.kt b/app/src/main/java/com/paw/key/domain/repository/walklist/WalkListRepository.kt deleted file mode 100644 index e8f3b884..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/walklist/WalkListRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.paw.key.domain.repository.walklist - -import com.paw.key.domain.entity.walklist.WalkListDetailEntity -import com.paw.key.domain.entity.walklist.WalkReviewSummaryEntity - -interface WalkListRepository { - suspend fun getWalkListDetail(userId: Int, postId: Int): Result - - suspend fun getWalkTopPopular(userId: Int, postId: Int) : Result -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/walkpreparation/WalkPreparationRepository.kt b/app/src/main/java/com/paw/key/domain/repository/walkpreparation/WalkPreparationRepository.kt new file mode 100644 index 00000000..ac0467b0 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/walkpreparation/WalkPreparationRepository.kt @@ -0,0 +1,14 @@ +package com.paw.key.domain.repository.walkpreparation + +import com.paw.key.domain.entity.walkpreparation.WalkPreparationEntity +import com.paw.key.domain.entity.walkpreparation.WalkPreparationMessageEntity + +interface WalkPreparationRepository { + suspend fun getWalkPreparation() : Result + + suspend fun patchWalkPreparation( + entity : WalkPreparationEntity + ) : Result + + suspend fun getWalkPreparationMessage() : Result +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/walkreview/WalkReviewRepository.kt b/app/src/main/java/com/paw/key/domain/repository/walkreview/WalkReviewRepository.kt deleted file mode 100644 index 8d8b69e2..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/walkreview/WalkReviewRepository.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.paw.key.domain.repository.walkreview - -import com.paw.key.domain.entity.walkreview.WalkReviewCategoryListEntity -import com.paw.key.domain.entity.walkreview.WalkReviewIdEntity -import com.paw.key.domain.entity.walkreview.WalkReviewInfoEntity -import com.paw.key.domain.entity.walkreview.WalkReviewRecordEntity -import okhttp3.MultipartBody - -interface WalkReviewRepository { - suspend fun postWalkReview( - userId: Int, - imageFiles: List, - walkReviewRequest: WalkReviewRecordEntity - ) : Result - - suspend fun getWalkReviewInfo( - userId: Int, - routeId: Int - ) : Result - - suspend fun getWalkReviewCategory( - userId: Int - ) : Result -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/usecase/LoginUseCase.kt b/app/src/main/java/com/paw/key/domain/usecase/auth/LoginUseCase.kt similarity index 97% rename from app/src/main/java/com/paw/key/domain/usecase/LoginUseCase.kt rename to app/src/main/java/com/paw/key/domain/usecase/auth/LoginUseCase.kt index eb97aae8..c413792d 100644 --- a/app/src/main/java/com/paw/key/domain/usecase/LoginUseCase.kt +++ b/app/src/main/java/com/paw/key/domain/usecase/auth/LoginUseCase.kt @@ -1,4 +1,4 @@ -package com.paw.key.domain.usecase +package com.paw.key.domain.usecase.auth import android.content.Context import com.paw.key.domain.repository.localstorage.LocalStorageRepository @@ -37,4 +37,4 @@ class LoginUseCase @Inject constructor( response.isNewUser } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/domain/usecase/PostCreateUserUseCase.kt b/app/src/main/java/com/paw/key/domain/usecase/user/PostCreateUserUseCase.kt similarity index 74% rename from app/src/main/java/com/paw/key/domain/usecase/PostCreateUserUseCase.kt rename to app/src/main/java/com/paw/key/domain/usecase/user/PostCreateUserUseCase.kt index 96905c58..ef297625 100644 --- a/app/src/main/java/com/paw/key/domain/usecase/PostCreateUserUseCase.kt +++ b/app/src/main/java/com/paw/key/domain/usecase/user/PostCreateUserUseCase.kt @@ -1,4 +1,4 @@ -package com.paw.key.domain.usecase +package com.paw.key.domain.usecase.user import com.paw.key.core.util.suspendRunCatching import com.paw.key.domain.entity.image.ImageDomainType @@ -27,21 +27,20 @@ class PostCreateUserUseCase @Inject constructor( ) ).getOrThrow() - /*imageRepository.uploadS3( - presignedUrl = presignedInfo.imageUrl, + imageRepository.uploadS3( + presignedUrl = presignedResult.uploadUrl, uriString = petImageUri - ).getOrThrow()*/ + ).getOrThrow() + val registerImage = imageRepository.registerImage( uriString = "${presignedResult.imageUrl}#${petImageUri}", domainType = ImageDomainType.PET_PROFILE, - ).onFailure(Timber::e) + ).onFailure{Timber.e(it)}.getOrThrow() - if (!registerImage.isSuccess) { - throw Exception("이미지 업로드에 실패했습니다.") - } - registerImage.getOrThrow().imageId + registerImage.imageId } else { + Timber.e("petImageUri is null") -1 } @@ -53,11 +52,12 @@ class PostCreateUserUseCase @Inject constructor( pet = finalPetInfo ) - userRepository.createUser( + val createUser = userRepository.createUser( userInfoEntity = finalUserInfo - ).onSuccess { - localRepository.saveUserId(userId = it.userId) - localRepository.savePetId(petId = it.petId) - } + ).getOrThrow() + + Timber.e("createUser: $createUser") + localRepository.saveUserId(userId = createUser.userId) + localRepository.savePetId(petId = createUser.petId) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/domain/usecase/walk/GetWalkFinishResultUseCase.kt b/app/src/main/java/com/paw/key/domain/usecase/walk/GetWalkFinishResultUseCase.kt new file mode 100644 index 00000000..4e48d6a7 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/usecase/walk/GetWalkFinishResultUseCase.kt @@ -0,0 +1,15 @@ +package com.paw.key.domain.usecase.walk + +import com.paw.key.domain.entity.walk.WalkFinishEntity +import com.paw.key.domain.repository.walk.WalkRepository +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +// finish 후 complete에서 데이터 공유하기 위함 +class GetWalkFinishResultUseCase @Inject constructor( + private val repository: WalkRepository +) { + operator fun invoke(): StateFlow { + return repository.finishResult + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/usecase/walk/GetWalkInfoUseCase.kt b/app/src/main/java/com/paw/key/domain/usecase/walk/GetWalkInfoUseCase.kt new file mode 100644 index 00000000..377a1309 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/usecase/walk/GetWalkInfoUseCase.kt @@ -0,0 +1,15 @@ +package com.paw.key.domain.usecase.walk + +import com.paw.key.domain.entity.walk.WalkFinish +import com.paw.key.domain.repository.walk.WalkRepository +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +// 걸음 수, 거리, 시간 등을 가져오기 위한 usecase +class GetWalkInfoUseCase @Inject constructor( + private val repository: WalkRepository +) { + operator fun invoke(): StateFlow { + return repository.finishWalkInfo + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/community/CommunityContract.kt b/app/src/main/java/com/paw/key/presentation/ui/community/CommunityContract.kt index e65a3e07..d098b0fa 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/community/CommunityContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/community/CommunityContract.kt @@ -1,39 +1,60 @@ package com.paw.key.presentation.ui.community import com.paw.key.core.model.WalkingRouteUiModel +import com.paw.key.domain.entity.posts.FilterSelectedItemEntity +import com.paw.key.presentation.ui.community.model.FilterCategoryUiModel +import com.paw.key.presentation.ui.community.model.FilterSelectedUiModel +import com.paw.key.presentation.ui.community.model.PostsFilterUiModel +import com.paw.key.presentation.ui.community.model.SelectionType import com.paw.key.presentation.ui.community.model.SortedType -import com.paw.key.presentation.ui.course.walkreview.model.WalkReviewFilterModel -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf data class CommunityState( - val filterList: ImmutableList = persistentListOf(), - val communityRouteList: ImmutableList = persistentListOf(), + val communityRouteList: PersistentList = persistentListOf(), val selectedSortedType: SortedType = SortedType.LATEST, - - val communityFilterModel: WalkReviewFilterModel = WalkReviewFilterModel(), - val communitySelectedFilterData: PersistentList = persistentListOf(), + val filterUiModel: PostsFilterUiModel = PostsFilterUiModel(), // 게시물 조회 시 사용하는 필터-스크린용 + val selectedOptionIds: PersistentMap> = persistentMapOf(), // 사용자가 필터 선택 시 + val nextCursor: String? = null, + val hasNext: Boolean = false ) { - fun getSingleFilterSelection(categoryList: List): String { - return communitySelectedFilterData.firstOrNull { categoryList.contains(it) }.orEmpty() + fun getSelectedOptionIds(category: FilterCategoryUiModel): PersistentList { + return selectedOptionIds[category.id] ?: persistentListOf() } - fun getUpdatedFilterList( - selectedItem: String, - categoryList: List, - isSingleSelect: Boolean - ): PersistentList { - return if (isSingleSelect) { - communitySelectedFilterData - .removeAll(categoryList) - .add(selectedItem) + fun getUpdatedOptionIds( + optionId: Int, + category: FilterCategoryUiModel + ): PersistentMap> { + return if (category.selectionType == SelectionType.SINGLE) { + selectedOptionIds.put(category.id, persistentListOf(optionId)) } else { - if (communitySelectedFilterData.contains(selectedItem)) { - communitySelectedFilterData.remove(selectedItem) + val current = selectedOptionIds[category.id] ?: persistentListOf() + val updated = if (current.contains(optionId)) { + current.remove(optionId) + } else { + current.add(optionId) + } + if (updated.isEmpty()) { + selectedOptionIds.remove(category.id) } else { - communitySelectedFilterData.add(selectedItem) + selectedOptionIds.put(category.id, updated) } } } -} \ No newline at end of file + + fun toFilterEntity(): FilterSelectedItemEntity { + val options = selectedOptionIds.map { (categoryId, optionIds) -> + val isDuration = filterUiModel.durationList.any { it.id == categoryId } + + FilterSelectedUiModel( + durationId = if (isDuration) categoryId else null, + categoryId = if (!isDuration) categoryId else null, + optionsIds = optionIds + ).toEntity() + } + return FilterSelectedItemEntity(selectedOptions = options) + } +} 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 91715602..b99b5e1b 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 @@ -41,7 +41,6 @@ import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.component.routeitem.RouteItem import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.extension.noRippleClickable -import com.paw.key.core.model.WalkingRouteUiModel import com.paw.key.presentation.ui.community.component.CommunityTopImageHolder import com.paw.key.presentation.ui.community.component.FilterScreen import com.paw.key.presentation.ui.community.model.SortedType @@ -73,7 +72,7 @@ fun CommunityRoute( // 루트 추천 onFilterClick = viewModel::onFilterClick, onCompleted = { isFilterSheetVisible = false - viewModel.postFilter() + viewModel.fetchPosts() }, onBackClick = { isFilterSheetVisible = false }, onClickSuffix = { @@ -84,7 +83,8 @@ fun CommunityRoute( // 루트 추천 CommunityScreen( paddingValues = paddingValues, state = state, - onShowFilterSheet = { isFilterSheetVisible = true } + onShowFilterSheet = { isFilterSheetVisible = true }, + onClickSort = viewModel::onSortTypeChanged ) } } @@ -94,10 +94,9 @@ fun CommunityScreen( paddingValues: PaddingValues, state: CommunityState, onShowFilterSheet: () -> Unit = {}, + onClickSort: (SortedType) -> Unit = {} ) { - // Todo: 서버 내용으로 수정 - val filterList = listOf( - "산책 소요 시간", "혼잡도", "강아지 교류 빈도", "안전") + val filterList = state.filterUiModel.allCategories.map { it.name }.toImmutableList() var selectedFilters by remember { mutableStateOf(setOf()) } var isSortMenuExpanded by remember { mutableStateOf(false) } @@ -243,7 +242,7 @@ fun CommunityScreen( ) }, onClick = { - // Todo: 정렬 타입 변경 로직 + onClickSort(option) isSortMenuExpanded = false } ) @@ -262,10 +261,10 @@ fun CommunityScreen( items(state.communityRouteList.size) { RouteItem( routeTitle = state.communityRouteList[it].title, - routeTime = state.communityRouteList[it].time, + routeTime = state.communityRouteList[it].duration.toString(), routeDate = state.communityRouteList[it].date, - location = state.communityRouteList[it].location, - routeImage = state.communityRouteList[it].imageUri, + location = state.communityRouteList[it].regionName, + routeImage = state.communityRouteList[it].imageUrl!!, onClickHeart = {}, onClick = {} ) @@ -282,7 +281,7 @@ private fun CommunityScreenPreview() { PawKeyTheme { CommunityScreen( paddingValues = PaddingValues(), - state = CommunityState(communityRouteList = WalkingRouteUiModel.Fake.toImmutableList()) + state = CommunityState() ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/community/CommunityViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/community/CommunityViewModel.kt index 04606734..f20cc419 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/community/CommunityViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/community/CommunityViewModel.kt @@ -1,43 +1,93 @@ package com.paw.key.presentation.ui.community import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.paw.key.core.model.toUiModel +import com.paw.key.domain.repository.posts.PostsRepository +import com.paw.key.presentation.ui.community.model.FilterCategoryUiModel +import com.paw.key.presentation.ui.community.model.SortedType +import com.paw.key.presentation.ui.community.model.toUiModel import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class CommunityViewModel @Inject constructor( - + private val postsRepository: PostsRepository ) : ViewModel() { private val _state = MutableStateFlow(CommunityState()) val state = _state.asStateFlow() - fun onFilterClick( - item: String, - categoryList: List, - isSingle: Boolean + init { + fetchPostsFilter() + } + + fun fetchPosts( + cursor: String? = null ) { - val newFilterList = _state.value.getUpdatedFilterList(item, categoryList, isSingle) + viewModelScope.launch { + postsRepository.getPostsFilter( + sortBy = _state.value.selectedSortedType.name.lowercase(), + cursor = cursor, + size = 10, + postsFilter = _state.value.toFilterEntity() + ).onSuccess { result -> + _state.update { state -> + val newList = if (cursor == null) { + result.posts.map { it.toUiModel() }.toPersistentList() + } else { + (state.communityRouteList + result.posts.map { it.toUiModel() }).toPersistentList() + } + state.copy( + communityRouteList = newList, + nextCursor = result.nextCursor, + hasNext = result.hasNext + ) + } + }.onFailure(Timber::e) + } + } - _state.update { - it.copy( - communitySelectedFilterData = newFilterList - ) + + private fun fetchPostsFilter() { + viewModelScope.launch { + postsRepository.getCategoriesFilter() + .onSuccess { result -> + _state.update { + it.copy( + filterUiModel = result.toUiModel() + ) + } + } + .onFailure(Timber::e) } } - fun onRefreshFilter() { + + fun onFilterClick(optionId: Int, category: FilterCategoryUiModel) { _state.update { - it.copy( - communitySelectedFilterData = persistentListOf() - ) + it.copy(selectedOptionIds = it.getUpdatedOptionIds(optionId, category)) } } - fun postFilter() { + fun onRefreshFilter() { + _state.update { it.copy(selectedOptionIds = persistentMapOf()) } + } + fun onSortTypeChanged(sortedType: SortedType) { + _state.update { it.copy(selectedSortedType = sortedType) } + fetchPosts(cursor = null) + } + + fun loadMore() { + if (_state.value.hasNext) { + fetchPosts(cursor = _state.value.nextCursor) + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/community/component/FilterScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/community/component/FilterScreen.kt index da9b487c..1f0a7359 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/community/component/FilterScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/community/component/FilterScreen.kt @@ -18,21 +18,25 @@ import com.paw.key.core.designsystem.component.DokiButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.community.CommunityState +import com.paw.key.presentation.ui.community.model.FilterCategoryUiModel +import com.paw.key.presentation.ui.community.model.SelectionType import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewMultipleFilter import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewSingleFilter +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList @Composable fun FilterScreen( paddingValues: PaddingValues, state: CommunityState, - onFilterClick: (String, List, Boolean) -> Unit, + onFilterClick: (Int, FilterCategoryUiModel) -> Unit, onCompleted: () -> Unit, onBackClick: () -> Unit, onClickSuffix: () -> Unit, modifier: Modifier = Modifier ) { - // Todo : 서버 내용으로 변경 - Column ( + Column( modifier = modifier .fillMaxSize() .background(PawKeyTheme.colors.background) @@ -47,7 +51,7 @@ fun FilterScreen( suffix = R.drawable.ic_course_list_refresh ) - Column ( + Column( modifier = Modifier .weight(1f) .padding(horizontal = 16.dp) @@ -55,84 +59,29 @@ fun FilterScreen( ) { Spacer(modifier = Modifier.height(26.dp)) - WalkReviewSingleFilter( - title = "혼잡도", - filterList = state.communityFilterModel.confusionSingleFilterList, - selectedItem = state.getSingleFilterSelection(state.communityFilterModel.confusionSingleFilterList), - onItemSelected = { - onFilterClick( - it, - state.communityFilterModel.confusionSingleFilterList, - true - ) - } - ) - - Spacer(modifier = Modifier.height(40.dp)) - - WalkReviewSingleFilter( - title = "강아지 교류 빈도", - filterList = state.communityFilterModel.frequencySingleFilterList, - selectedItem = state.getSingleFilterSelection(state.communityFilterModel.frequencySingleFilterList), - onItemSelected = { - onFilterClick( - it, - state.communityFilterModel.frequencySingleFilterList, - true - ) - } - ) - - Spacer(modifier = Modifier.height(40.dp)) - - // Todo : 어떻게 필터값을 받을 지 몰라서 보류 - WalkReviewMultipleFilter( - title = "안전", - filterList = state.communityFilterModel.safetyMultipleFilterList, - selectedItems = state.communitySelectedFilterData, - onItemClick = { - onFilterClick( - it, - state.communityFilterModel.safetyMultipleFilterList, - false - ) - } - ) - - Spacer(modifier = Modifier.height(40.dp)) - - WalkReviewMultipleFilter( - title = "편의성", - filterList = state.communityFilterModel.comfortMultipleFilterList, - selectedItems = state.communitySelectedFilterData, - onItemClick = { - onFilterClick( - it, - state.communityFilterModel.comfortMultipleFilterList, - false - ) - } - ) - - Spacer(modifier = Modifier.height(40.dp)) - - WalkReviewMultipleFilter( - title = "환경", - filterList = state.communityFilterModel.environmentMultipleFilterList, - selectedItems = state.communitySelectedFilterData, - onItemClick = { - onFilterClick( - it, - state.communityFilterModel.environmentMultipleFilterList, - false - ) - } - ) + // durationList + state.filterUiModel.durationList.forEach { category -> + FilterCategorySection( + category = category, + selectedOptionIds = state.getSelectedOptionIds(category), + onFilterClick = onFilterClick + ) + Spacer(modifier = Modifier.height(40.dp)) + } + + // categoryList + state.filterUiModel.categoryList.forEach { category -> + FilterCategorySection( + category = category, + selectedOptionIds = state.getSelectedOptionIds(category), + onFilterClick = onFilterClick + ) + Spacer(modifier = Modifier.height(40.dp)) + } Spacer(modifier = Modifier.height(24.dp)) } - DokiButton( text = "적용하기", enabled = true, @@ -142,8 +91,38 @@ fun FilterScreen( .padding(bottom = 24.dp) ) } +} - +@Composable +private fun FilterCategorySection( + category: FilterCategoryUiModel, + selectedOptionIds: PersistentList, + onFilterClick: (Int, FilterCategoryUiModel) -> Unit +) { + if (category.selectionType == SelectionType.SINGLE) { + WalkReviewSingleFilter( + title = category.name, + filterList = category.options.map { it.text }.toImmutableList(), + selectedItem = category.options + .firstOrNull { selectedOptionIds.contains(it.id) }?.text.orEmpty(), + onItemSelected = { selectedText -> + val optionId = category.options.first { it.text == selectedText }.id + onFilterClick(optionId, category) + } + ) + } else { + WalkReviewMultipleFilter( + title = category.name, + filterList = category.options.map { it.text }.toImmutableList(), + selectedItems = selectedOptionIds + .mapNotNull { id -> category.options.firstOrNull { it.id == id }?.text } + .toPersistentList(), + onItemClick = { selectedText -> + val optionId = category.options.first { it.text == selectedText }.id + onFilterClick(optionId, category) + } + ) + } } @Preview @@ -152,11 +131,11 @@ private fun FilterScreenPreview() { PawKeyTheme { FilterScreen( state = CommunityState(), - onFilterClick = { _, _, _ -> }, + onFilterClick = { _, _ -> }, onCompleted = {}, onBackClick = {}, onClickSuffix = {}, paddingValues = PaddingValues() ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/community/model/FilterUiModel.kt b/app/src/main/java/com/paw/key/presentation/ui/community/model/FilterUiModel.kt new file mode 100644 index 00000000..1e59f318 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/community/model/FilterUiModel.kt @@ -0,0 +1,57 @@ +package com.paw.key.presentation.ui.community.model + +import androidx.compose.runtime.Immutable +import com.paw.key.domain.entity.posts.FilterItemEntity +import com.paw.key.domain.entity.posts.FilterSelectedIOptionEntity +import com.paw.key.domain.entity.posts.PostsFilterEntity +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +@Immutable +data class FilterCategoryUiModel( + val id: Int, + val name: String, + val selectionType: SelectionType, + val options: List +) + +data class FilterOptionUiModel( + val id: Int, + val text: String +) + +enum class SelectionType { + SINGLE, MULTI +} + +fun PostsFilterEntity.toUiModel() = PostsFilterUiModel( + durationList = durationList.map { it.toUiModel() }.toImmutableList(), + categoryList = categoryList.map { it.toUiModel() }.toImmutableList() +) + +fun FilterItemEntity.toUiModel() = FilterCategoryUiModel( + id = id, + name = name, + selectionType = if (selectionType == "SINGLE") SelectionType.SINGLE else SelectionType.MULTI, + options = options.map { FilterOptionUiModel(id = it.id, text = it.text) } +) + +data class PostsFilterUiModel( + val durationList: ImmutableList = persistentListOf(), + val categoryList: ImmutableList = persistentListOf() +) { + val allCategories get() = durationList + categoryList +} + +data class FilterSelectedUiModel( + val durationId: Int? = null, + val categoryId: Int? = null, + val optionsIds: ImmutableList = persistentListOf() +) { + fun toEntity() = FilterSelectedIOptionEntity( + durationId = durationId, + categoryId = categoryId, + optionsIds = optionsIds + ) +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/community/model/SortedType.kt b/app/src/main/java/com/paw/key/presentation/ui/community/model/SortedType.kt index 08876ae3..c77cfea8 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/community/model/SortedType.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/community/model/SortedType.kt @@ -3,9 +3,6 @@ package com.paw.key.presentation.ui.community.model enum class SortedType( val label: String, ) { - // Todo : 서버 내용으로 수정 LATEST("최신순"), - POPULARITY("인기순"), - DISTANCE("거리순"), - TIME("시간순"), -} \ No newline at end of file + POPULAR("인기순"), +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseGraph.kt b/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseGraph.kt index da8eb79e..d054b50c 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseGraph.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseGraph.kt @@ -24,7 +24,9 @@ fun NavGraphBuilder.walkCourseGraph( composable { WalkPrepareRoute( paddingValues = paddingValues, - navigateWalkCourse = navController::navigateWalkCourse + navigateWalkCourse = { + navController.navigateWalkCourse(routeId = it) + } ) } @@ -32,7 +34,9 @@ fun NavGraphBuilder.walkCourseGraph( WalkCourseRoute( paddingValues = paddingValues, navigateUp = navController::navigateUp, - navigateWalkComplete = navController::navigateWalkComplete, + navigateWalkComplete = { + navController.navigateWalkComplete(routeId = it) + }, navigateReview = navigateWalkReview ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseNavigation.kt index 0316f456..cf366c3f 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseNavigation.kt @@ -5,8 +5,9 @@ import androidx.navigation.NavOptions fun NavController.navigateWalkCourse( navOptions: NavOptions? = null, + routeId: String ) { - navigate(WalkCourse, navOptions) + navigate(WalkCourse(routeId), navOptions) } fun NavController.navigateWalkPrepare( @@ -17,6 +18,7 @@ fun NavController.navigateWalkPrepare( fun NavController.navigateWalkComplete( navOptions: NavOptions? = null, + routeId: String ) { - navigate(WalkComplete, navOptions) + navigate(WalkComplete(routeId), navOptions) } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseRoute.kt b/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseRoute.kt index 70b0f6f8..20d153ff 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseRoute.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/navigation/WalkCourseRoute.kt @@ -9,8 +9,12 @@ sealed interface WalkRoute : MainTabRoute data object WalkPrepare: WalkRoute @Serializable -data object WalkCourse: WalkRoute +data class WalkCourse( + val routeId: String +): WalkRoute @Serializable -data object WalkComplete: WalkRoute +data class WalkComplete( + val routeId: String +): WalkRoute diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/util/rememberFusedLocationSource.kt b/app/src/main/java/com/paw/key/presentation/ui/course/util/rememberFusedLocationSource.kt index bfb392f9..0ed10d30 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/util/rememberFusedLocationSource.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/util/rememberFusedLocationSource.kt @@ -10,7 +10,10 @@ import androidx.annotation.UiThread import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.core.app.ActivityCompat import com.google.android.gms.location.FusedLocationProviderClient @@ -28,6 +31,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -76,9 +80,10 @@ fun rememberCustomFusedLocationSource( FusedLocationSource(context, fusedLocationClient, useTestPoints) } + var hasMovedToInitialLocation by remember { mutableStateOf(false) } + LaunchedEffect(hasLocationPermission) { - if (hasLocationPermission) { - Log.d("rememberCustomFusedLocationSource", "hasLocationPermission: $hasLocationPermission") + if (hasLocationPermission && !hasMovedToInitialLocation) { if (ActivityCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION @@ -92,9 +97,11 @@ fun rememberCustomFusedLocationSource( fusedLocationClient.lastLocation.addOnSuccessListener { location -> location?.let { - Log.d("rememberCustomFusedLocationSource", "lastLocation: $it") + /*Log.d("rememberCustomFusedLocationSource", "lastLocation: $it") val latLng = LatLng(it.latitude, it.longitude) - cameraPositionState.move(CameraUpdate.scrollTo(latLng)) + cameraPositionState.move(CameraUpdate.scrollTo(latLng))*/ + cameraPositionState.move(CameraUpdate.scrollTo(LatLng(it.latitude, it.longitude))) + hasMovedToInitialLocation = true } } } @@ -118,7 +125,7 @@ class FusedLocationSource( private var isListening = false // 테스트를 위한 코루틴 - private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + private var coroutineScope: CoroutineScope? = null private var simulationJob: Job? = null private val locationCallback = object : LocationCallback() { @@ -168,6 +175,7 @@ class FusedLocationSource( if (!isListening) { isListening = true if (useTestPoints) { + coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) startSimulation() } else { startRealLocationUpdates() @@ -176,7 +184,7 @@ class FusedLocationSource( } private fun startSimulation() { - simulationJob = coroutineScope.launch { + simulationJob = coroutineScope?.launch { for (point in testPoints) { val mockLocation = Location("TestProvider").apply { latitude = point.latitude @@ -213,6 +221,8 @@ class FusedLocationSource( override fun deactivate() { if (isListening) { if (useTestPoints) { + coroutineScope?.cancel() + coroutineScope = null simulationJob?.cancel() } else { fusedLocationClient.removeLocationUpdates(locationCallback) @@ -224,9 +234,9 @@ class FusedLocationSource( } companion object { - private val locationRequest = - LocationRequest.Builder(1000) - .setPriority(Priority.PRIORITY_HIGH_ACCURACY) - .build() + private val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000L).apply { + setMinUpdateIntervalMillis(1000L) + setMinUpdateDistanceMeters(2.0f) + }.build() } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/WalkCourseScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/WalkCourseScreen.kt index 81a46a2e..4d9d48ef 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/WalkCourseScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/WalkCourseScreen.kt @@ -95,7 +95,7 @@ fun WalkCourseRoute( paddingValues: PaddingValues, navigateUp: () -> Unit = {}, navigateReview: () -> Unit = {}, - navigateWalkComplete: () -> Unit = {}, + navigateWalkComplete: (routeId: String) -> Unit = {}, viewModel: WalkCourseViewModel = hiltViewModel(), ) { val lifecycleOwner = LocalLifecycleOwner.current @@ -134,7 +134,7 @@ fun WalkCourseRoute( WalkCourseSideEffect.NavigateReview -> navigateReview() - WalkCourseSideEffect.NavigateComplete -> navigateWalkComplete() + is WalkCourseSideEffect.NavigateComplete -> navigateWalkComplete(sideEffect.routeId) else -> {} } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/model/MapState.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/model/MapState.kt index 1a66f2b7..0135dbfa 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/model/MapState.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/model/MapState.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import androidx.compose.runtime.Immutable import com.naver.maps.geometry.LatLng import com.paw.key.core.util.UiState +import com.paw.key.domain.entity.walk.WalkPoint import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -16,4 +17,16 @@ data class MapState( val isTrackingEnabled: Boolean = true, // 카메라 추적 모드 val shouldCaptureMap: Boolean = false, val capturedMapBitmap: Bitmap? = null -) \ No newline at end of file +) + +fun LatLng.toEntity( + routeId: String, + timestamp: Int +): WalkPoint { + return WalkPoint( + routeId = routeId, + lat = this.latitude, + lng = this.longitude, + timestamp = timestamp + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/state/WalkCourseContract.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/state/WalkCourseContract.kt index 6845f8e7..93c601e7 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/state/WalkCourseContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/state/WalkCourseContract.kt @@ -3,6 +3,7 @@ package com.paw.key.presentation.ui.course.walkcourse.state import androidx.annotation.StringRes import androidx.compose.runtime.Immutable import com.paw.key.R +import com.paw.key.domain.entity.walk.WalkFinish import com.paw.key.presentation.ui.course.walkcourse.model.MapState import com.paw.key.presentation.ui.course.walkcourse.model.RecordingState import com.paw.key.presentation.ui.course.walkcourse.model.StepCounterState @@ -22,6 +23,13 @@ data class WalkCourseState( val formattedDistance: String get() = formatDistance(this.mapState.totalDistance) + + fun toEntity() = WalkFinish( + distance = this.mapState.totalDistance.toInt(), + duration = this.totalTimeMillis.toInt(), + stepCount = this.stepCounterState.sessionSteps.toInt(), + endedAt = this.recordingState.endedAt + ) } sealed interface WalkCourseSideEffect { @@ -31,7 +39,7 @@ sealed interface WalkCourseSideEffect { data class NavigateNext(val regionId: Int): WalkCourseSideEffect data object NavigateReview: WalkCourseSideEffect - data object NavigateComplete: WalkCourseSideEffect + data class NavigateComplete(val routeId: String): WalkCourseSideEffect } sealed class WalkCourseRecord ( diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/util/PermissionConstants.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/util/PermissionConstants.kt new file mode 100644 index 00000000..ada4afc9 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/util/PermissionConstants.kt @@ -0,0 +1,15 @@ +package com.paw.key.presentation.ui.course.walkcourse.util + +import android.Manifest +import android.os.Build + +object PermissionConstants { + val REQUIRED_PERMISSIONS = mutableListOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + add(Manifest.permission.ACTIVITY_RECOGNITION) + } + }.toTypedArray() +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/viewmodel/WalkCourseViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/viewmodel/WalkCourseViewModel.kt index 8ab52bfb..7f024f51 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/viewmodel/WalkCourseViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/viewmodel/WalkCourseViewModel.kt @@ -1,16 +1,17 @@ package com.paw.key.presentation.ui.course.walkcourse.viewmodel import android.location.Location +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute import com.paw.key.core.extension.toLatLng -import com.paw.key.core.util.PhotoUtils import com.paw.key.core.util.UiState -import com.paw.key.domain.entity.walkcourse.CoordinateEntity -import com.paw.key.domain.entity.walkcourse.WalkCourseEntity import com.paw.key.domain.repository.WalkSharedResultRepository -import com.paw.key.domain.repository.walkcourse.WalkCourseRepository +import com.paw.key.domain.repository.walk.WalkRepository +import com.paw.key.presentation.ui.course.navigation.WalkCourse import com.paw.key.presentation.ui.course.util.RealTimeLocationListener +import com.paw.key.presentation.ui.course.walkcourse.model.toEntity import com.paw.key.presentation.ui.course.walkcourse.state.WalkCourseSideEffect import com.paw.key.presentation.ui.course.walkcourse.state.WalkCourseState import dagger.hilt.android.lifecycle.HiltViewModel @@ -24,15 +25,18 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import java.time.LocalDateTime import javax.inject.Inject @HiltViewModel class WalkCourseViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val walkRepository: WalkRepository, private val walkSharedResultRepository: WalkSharedResultRepository, - private val walkCourseRepository: WalkCourseRepository ) : ViewModel(), RealTimeLocationListener { // Todo : saveStateHandle로 isShared 받아서 처리하기 + private val routeId = savedStateHandle.toRoute().routeId private val _state = MutableStateFlow(WalkCourseState()) val state: StateFlow = _state.asStateFlow() @@ -40,6 +44,7 @@ class WalkCourseViewModel @Inject constructor( val sideEffect: SharedFlow = _sideEffect.asSharedFlow() private var timerJob: Job? = null + private var pointSyncJob: Job? = null // 5초 마다 서버 보낼 용도 private var initialSensorSteps: Long = -1L private var lastLocation: Location? = null @@ -78,6 +83,7 @@ class WalkCourseViewModel @Inject constructor( ) } startTimer() + startPointSyncTimer() } fun pauseTracking() { @@ -92,6 +98,7 @@ class WalkCourseViewModel @Inject constructor( ) } stopTimer() + stopPointSyncTimer() } private fun startTimer() { @@ -109,15 +116,29 @@ class WalkCourseViewModel @Inject constructor( } } + // 5초마다 좌표 서버 전송 타이머 시작 + private fun startPointSyncTimer() { + if (pointSyncJob?.isActive == true) return + + pointSyncJob = viewModelScope.launch { + while (true) { + delay(5000L) + syncCurrentLocation() + } + } + } + private fun stopTimer() { timerJob?.cancel() + timerJob = null } - override fun onCleared() { - super.onCleared() - stopTimer() + private fun stopPointSyncTimer() { + pointSyncJob?.cancel() + pointSyncJob = null } + fun fetchTrackingEnable() { _state.update { currentState -> currentState.copy( @@ -156,66 +177,61 @@ class WalkCourseViewModel @Inject constructor( } } - // 서버 통신 - fun postWalkCourseData(userId: Int) = viewModelScope.launch { - val bitmap = _state.value.mapState.capturedMapBitmap - if (bitmap == null) { - _sideEffect.emit(WalkCourseSideEffect.ShowSnackBar("산책 이미지가 없습니다.")) - return@launch - } - - try { - // PhotoUtils 사용 - val imagePart = PhotoUtils.createBitmapMultipart( - bitmap = bitmap, - partName = "trackingImage" - ) - - if (imagePart == null) { - _sideEffect.emit(WalkCourseSideEffect.ShowSnackBar("이미지 변환 실패")) - return@launch - } + // 실제 서버 통신 함수 5초 + private fun syncCurrentLocation() { + val currentState = _state.value - val routeEntity = WalkCourseEntity( - coordinates = _state.value.mapState.poiPoints.map { - CoordinateEntity(it.latitude, it.longitude) - }, - distance = _state.value.mapState.totalDistance.toInt(), - duration = (_state.value.totalTimeMillis / 1000).toInt(), - startedAt = _state.value.recordingState.startedAt, - endedAt = _state.value.recordingState.endedAt, - stepCount = _state.value.stepCounterState.sessionSteps.toInt() - ) + // 기록 중이 아니거나 현재 위치가 없으면 보내지 않음 + if (!currentState.recordingState.isRecording) return + val currentLocation = currentState.mapState.currentLocation ?: return - val result = walkCourseRepository.postWalkCourse( - userId = userId, - image = imagePart, - routeRequestDto = routeEntity.toDto() - ) + val currentTimestamp = (System.currentTimeMillis() / 1000).toInt() + val walkPointEntity = currentLocation.toEntity( + routeId = routeId, + timestamp = currentTimestamp + ) - result.onSuccess { response -> - _sideEffect.emit(WalkCourseSideEffect.NavigateNext(response.regionId)) - }.onFailure { throwable -> - _sideEffect.emit(WalkCourseSideEffect.ShowSnackBar("업로드 실패: ${throwable.message}")) - } - - } catch (e: Exception) { - _sideEffect.emit(WalkCourseSideEffect.ShowSnackBar("오류 발생: ${e.localizedMessage}")) + // 5초 뒤 다시 시도되기 때문에 에러 로그 발생 후 무시 + viewModelScope.launch { + walkRepository.pointWalk(walkPointEntity) + .onFailure { throwable -> + Timber.e(throwable) + } } } // Todo: 서버 내용 확인하고 넘기기 fun stopTracking() { viewModelScope.launch { - if (_state.value.isStopTracking) { - _sideEffect.emit(WalkCourseSideEffect.NavigateComplete) + _state.update { currentState -> + currentState.copy( + recordingState = currentState.recordingState.copy( + isRecording = false, + endedAt = LocalDateTime.now().toString() + ) + ) } - _state.update { - it.copy( - isStopTracking = true - ) + val currentState = _state.value + + walkRepository.finishWalk( + routeId = routeId, + walkFinish = currentState.toEntity() + ).onSuccess { + _state.update { + it.copy( + isStopTracking = true + ) + } + + if (_state.value.isStopTracking) { + _sideEffect.emit(WalkCourseSideEffect.NavigateComplete(routeId)) + } + }.onFailure { + Timber.e(it) + _sideEffect.emit(WalkCourseSideEffect.ShowSnackBar("산책 종료 실패")) } + } } @@ -269,6 +285,12 @@ class WalkCourseViewModel @Inject constructor( } } + override fun onCleared() { + super.onCleared() + stopTimer() + stopPointSyncTimer() + } + companion object { private const val LOCATION_ACCURACY_THRESHOLD = 25f } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkComplete.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkComplete.kt index 82cb09d9..34b36af3 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkComplete.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkComplete.kt @@ -6,12 +6,14 @@ 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.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,21 +22,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.dropShadow -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import coil.compose.AsyncImage import com.paw.key.R import com.paw.key.core.designsystem.component.DokiButton import com.paw.key.core.designsystem.component.TopBar +import com.paw.key.core.designsystem.component.UrlImage import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.course.walkcourse.component.WalkRecordItem -import com.paw.key.presentation.ui.course.walkcourse.model.WalkInfoState +import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model.WalkInfoModel import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.state.WalkCompleteState // Todo : 나중에 서버에서 줌 @@ -93,15 +94,13 @@ private fun WalkCompleteScreen( verticalAlignment = Alignment.CenterVertically ) { // 프로필사진 - /*AsyncImage( - model = "", - contentDescription = null, + UrlImage( + url = "", modifier = Modifier - .clip(CircleShape) - .background( - color = PawKeyTheme.colors.defaultMiddle - ) - )*/ + .size(36.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(50.dp)) + ) Spacer(modifier = Modifier.width(10.dp)) @@ -121,26 +120,37 @@ private fun WalkCompleteScreen( } // 지도 사진 + UrlImage( + url = "", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.87f) + .padding(horizontal = 16.dp) + .clip(RoundedCornerShape(16.dp)) + ) Row( modifier = Modifier .fillMaxWidth() - .padding(top = 32.dp, start = 16.dp, end = 16.dp), + .padding(horizontal = 16.dp, vertical = 14.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceAround ) { - WalkRecordItem( - recordTitle = R.string.course_record_distance, - recordContent = state.walkInfo.distanceMeters.toString() - ) - WalkRecordItem( - recordTitle = R.string.course_record_time, - recordContent = state.walkInfo.timeMillis.toString() - ) - WalkRecordItem( - recordTitle = R.string.course_record_step, - recordContent = state.walkInfo.stepCount.toString() - ) + with(state.walkCompleteFinishInfo) { + WalkRecordItem( + recordTitle = R.string.course_record_distance, + recordContent = distance.toString() + ) + WalkRecordItem( + recordTitle = R.string.course_record_time, + recordContent = duration.toString() + ) + WalkRecordItem( + recordTitle = R.string.course_record_step, + recordContent = stepCount.toString() + ) + } } } @@ -165,10 +175,10 @@ private fun WalkCompletePreview() { WalkCompleteScreen( paddingValues = PaddingValues(), state = WalkCompleteState( - walkInfo = WalkInfoState( - distanceMeters = 1000f, - timeMillis = 1000L, - stepCount = 1 + walkCompleteFinishInfo = WalkInfoModel( + distance = 1000, + duration = 1000, + stepCount = 1000 ) ) ) diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkCompleteViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkCompleteViewModel.kt index 2a4c6876..f0c49fc8 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkCompleteViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/WalkCompleteViewModel.kt @@ -1,18 +1,84 @@ package com.paw.key.presentation.ui.course.walkcourse.walkcomplete +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.paw.key.domain.repository.walk.WalkRepository +import com.paw.key.domain.usecase.walk.GetWalkFinishResultUseCase +import com.paw.key.domain.usecase.walk.GetWalkInfoUseCase +import com.paw.key.presentation.ui.course.navigation.WalkComplete +import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model.toUiModel import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.state.WalkCompleteState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class WalkCompleteViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val walkCompleteRepository: WalkRepository, + private val finishResultUseCase: GetWalkFinishResultUseCase, + private val finishWalkInfoUseCase: GetWalkInfoUseCase ) : ViewModel() { + private val routeId = savedStateHandle.toRoute().routeId private val _state = MutableStateFlow(WalkCompleteState()) val state: StateFlow = _state.asStateFlow() + init { + // 좌표값 + fetchWalkComplete() + // 유저 정보 및 산책 정보 + fetchWalkCompleteData() + } + + private fun fetchWalkCompleteData() { + viewModelScope.launch { + launch { + finishResultUseCase().collect { entity -> + entity?.let { + _state.update { currentState -> + currentState.copy( + walkCompleteUserInfo = it.toUiModel() // WalkFinishEntity -> WalkFinishModel + ) + } + } + } + } + + launch { + finishWalkInfoUseCase().collect { walkFinish -> + walkFinish?.let { + _state.update { currentState -> + currentState.copy( + walkCompleteFinishInfo = it.toUiModel() // WalkFinish -> WalkInfoModel + ) + } + } + } + } + } + } + + fun fetchWalkComplete() { + viewModelScope.launch { + walkCompleteRepository.completeWalk(routeId) + .onSuccess { result -> + _state.update { + it.copy( + walkCompleteMapInfo = result.geometry.toUiModel() + ) + } + } + .onFailure { + Timber.e(it) + } + } + } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkFinishModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkFinishModel.kt new file mode 100644 index 00000000..8329b5e4 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkFinishModel.kt @@ -0,0 +1,42 @@ +package com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model + +import com.paw.key.domain.entity.walk.WalkFinishEntity +import com.paw.key.domain.entity.walk.WalkInfoEntity +import com.paw.key.domain.entity.walk.WalkPetProfileEntity + +data class WalkFinishModel( + val routeId: Int = -1, + val petProfile: WalkCompletePetProfileModel = WalkCompletePetProfileModel(), + val walkInfo: WalkCompleteInfoModel = WalkCompleteInfoModel() +) + +fun WalkFinishEntity.toUiModel(): WalkFinishModel { + return WalkFinishModel( + routeId = routeId, + petProfile = petProfile.toUiModel(), + walkInfo = walkInfo.toUiModel() + ) +} + + +data class WalkCompletePetProfileModel( + val petName: String = "", + val petImage: String = "", +) + +fun WalkPetProfileEntity.toUiModel(): WalkCompletePetProfileModel { + return WalkCompletePetProfileModel( + petName = petName, + petImage = petProfileImageUrl + ) +} + +data class WalkCompleteInfoModel( + val startedAt: String = "", +) + +fun WalkInfoEntity.toUiModel() : WalkCompleteInfoModel { + return WalkCompleteInfoModel( + startedAt = startAt + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkInfoModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkInfoModel.kt new file mode 100644 index 00000000..f1fe0060 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkInfoModel.kt @@ -0,0 +1,19 @@ +package com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model + +import com.paw.key.domain.entity.walk.WalkFinish + +data class WalkInfoModel( + val distance: Int = 0, + val duration: Int = 0, + val stepCount: Int = 0, + val endedAt: String = "" +) + +fun WalkFinish.toUiModel(): WalkInfoModel{ + return WalkInfoModel( + distance = distance, + duration = duration, + stepCount = stepCount, + endedAt = endedAt + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkMapInfoModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkMapInfoModel.kt new file mode 100644 index 00000000..2a44f1ab --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/model/WalkMapInfoModel.kt @@ -0,0 +1,17 @@ +package com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model + +import com.paw.key.domain.entity.walk.WalkCompleteGeometryEntity + +data class WalkMapInfoModel( + val type: String = "", + val coordinates: List> = emptyList() +) + +fun WalkCompleteGeometryEntity.toUiModel() : WalkMapInfoModel { + return WalkMapInfoModel( + type = type, + coordinates = coordinates + ) +} + + diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/state/WalkCompleteContract.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/state/WalkCompleteContract.kt index e661d166..9ca4c129 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/state/WalkCompleteContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkcomplete/state/WalkCompleteContract.kt @@ -1,14 +1,13 @@ package com.paw.key.presentation.ui.course.walkcourse.walkcomplete.state import androidx.compose.runtime.Immutable -import com.paw.key.presentation.ui.course.walkcourse.model.WalkInfoState +import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model.WalkFinishModel +import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model.WalkInfoModel +import com.paw.key.presentation.ui.course.walkcourse.walkcomplete.model.WalkMapInfoModel @Immutable data class WalkCompleteState( - val userProfile: String = "", - val petName : String = "", - val dateTime : String = "", - val mapImage: String = "", - - val walkInfo: WalkInfoState = WalkInfoState() -) \ No newline at end of file + val walkCompleteUserInfo: WalkFinishModel = WalkFinishModel(), + val walkCompleteFinishInfo: WalkInfoModel = WalkInfoModel(), + val walkCompleteMapInfo: WalkMapInfoModel = WalkMapInfoModel() +) diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareScreen.kt index a9baa76e..b3837a57 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareScreen.kt @@ -1,6 +1,8 @@ package com.paw.key.presentation.ui.course.walkcourse.walkprepare +import android.widget.Toast import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer @@ -9,8 +11,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -18,25 +22,46 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.core.designsystem.component.DokiBorderButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.extension.collectSideEffect import com.paw.key.presentation.ui.course.walkcourse.walkprepare.component.WalkPrepareBody import com.paw.key.presentation.ui.course.walkcourse.walkprepare.component.WalkPrepareWeatherInfo +import com.paw.key.presentation.ui.course.walkcourse.walkprepare.state.WalkPrepareSideEffect import com.paw.key.presentation.ui.course.walkcourse.walkprepare.state.WalkPrepareState @Composable fun WalkPrepareRoute( paddingValues: PaddingValues, - navigateWalkCourse: () -> Unit = {}, + navigateWalkCourse: (routeId: String) -> Unit = {}, viewModel: WalkPrepareViewModel = hiltViewModel() ) { + val context = LocalContext.current val state by viewModel.state.collectAsStateWithLifecycle() + viewModel.sideEffect.collectSideEffect { + when(it) { + is WalkPrepareSideEffect.NavigateToWalkCourse -> { + navigateWalkCourse(it.routeId) + } + is WalkPrepareSideEffect.ShowToastMessage -> { + Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show() + } + } + } + + DisposableEffect(Unit) { + onDispose { + viewModel.updateWalkPreparation() + } + } + WalkPrepareScreen( paddingValues = paddingValues, state = state, - navigateWalkCourse = navigateWalkCourse, + startWalkCourse = viewModel::startWalk, addWalkItem = viewModel::addWalkItem, deleteWalkItem = viewModel::deleteWalkItem, - clearLastAddedItemId = viewModel::clearLastAddedItemId + clearLastAddedItemId = viewModel::clearLastAddedItemId, + //onFinishWalk = viewModel::finishWalk ) } @@ -44,10 +69,11 @@ fun WalkPrepareRoute( private fun WalkPrepareScreen( paddingValues: PaddingValues, state: WalkPrepareState, - navigateWalkCourse: () -> Unit = {}, + startWalkCourse: () -> Unit = {}, addWalkItem : () -> Unit = {}, deleteWalkItem : (Int) -> Unit = {}, - clearLastAddedItemId: () -> Unit = {} + clearLastAddedItemId: () -> Unit = {}, + onFinishWalk : () -> Unit = {} ) { Column ( modifier = Modifier @@ -59,13 +85,14 @@ private fun WalkPrepareScreen( ) { TopBar( title = "산책", - isBackVisible = false + isBackVisible = false, + modifier = Modifier.clickable(onClick = onFinishWalk) ) Spacer(modifier = Modifier.height(20.dp)) - // Todo : 안에 내용은 수정하기 WalkPrepareWeatherInfo( + walkPreparationMessage = state.walkPreparationMessage, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) @@ -88,7 +115,7 @@ private fun WalkPrepareScreen( DokiBorderButton( text = "산책 기록하기", enabled = true, - onClick = navigateWalkCourse, + onClick = startWalkCourse, modifier = Modifier .padding(horizontal = 16.dp) ) diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareViewModel.kt index 6b4f7a2d..afcba9dc 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/WalkPrepareViewModel.kt @@ -2,22 +2,56 @@ package com.paw.key.presentation.ui.course.walkcourse.walkprepare import androidx.compose.foundation.text.input.TextFieldState import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.paw.key.domain.repository.walk.WalkRepository +import com.paw.key.domain.repository.walkpreparation.WalkPreparationRepository +import com.paw.key.presentation.ui.course.walkcourse.walkprepare.model.WalkPreparationMessageModel import com.paw.key.presentation.ui.course.walkcourse.walkprepare.model.WalkPrepareItemModel +import com.paw.key.presentation.ui.course.walkcourse.walkprepare.state.WalkPrepareSideEffect import com.paw.key.presentation.ui.course.walkcourse.walkprepare.state.WalkPrepareState +import com.paw.key.presentation.ui.course.walkcourse.walkprepare.state.toEntity 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.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class WalkPrepareViewModel @Inject constructor( - + private val preparationRepository: WalkPreparationRepository, + private val walkRepository: WalkRepository ) : ViewModel() { private val _state = MutableStateFlow(WalkPrepareState()) val state = _state.asStateFlow() + private val _sideEffect = MutableSharedFlow() + val sideEffect = _sideEffect.asSharedFlow() + + init { + fetchWalkPreparationMessage() + } + + fun fetchWalkPreparationMessage() { + viewModelScope.launch { + preparationRepository.getWalkPreparationMessage() + .onSuccess { result -> + _state.update { currentState -> + currentState.copy( + walkPreparationMessage = WalkPreparationMessageModel( + mainMessage = result.mainMessage, + subMessage = result.subMessage + ) + ) + } + } + } + } + fun addWalkItem() { val currentList = _state.value.walkPrepareItemList val newId = (currentList.maxOfOrNull { it.id } ?: 0) + 1 @@ -51,4 +85,43 @@ class WalkPrepareViewModel @Inject constructor( fun clearLastAddedItemId() { _state.update { it.copy(lastAddedItemId = null) } } + + fun updateWalkPreparation() { + viewModelScope.launch { + val currentState = _state.value + val preparationData = currentState.toEntity() + + preparationRepository.patchWalkPreparation( + entity = preparationData + ) + } + } + + fun startWalk() { + viewModelScope.launch { + walkRepository.startWalk(deviceInfo = "ANDROID") + .onSuccess { + Timber.e("startWalk success ${it.routeId}") + _sideEffect.emit(WalkPrepareSideEffect.NavigateToWalkCourse(it.routeId)) + } + .onFailure { + Timber.e(it) + _sideEffect.emit(WalkPrepareSideEffect.ShowToastMessage("산책 시작에 실패하였습니다.")) + } + } + } + + /*fun finishWalk() { + viewModelScope.launch { + walkRepository.finishWalk( + routeId = "routeId", + walkFinish = state.value.toEntity() + ).onSuccess { + _sideEffect.emit(WalkPrepareSideEffect.NavigateToWalkCourse(it.routeId)) + }.onFailure { + Timber.e(it) + _sideEffect.emit(WalkPrepareSideEffect.ShowToastMessage("산책 종료에 실패하였습니다.")) + } + } + }*/ } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/component/WalkPrepareWeatherInfo.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/component/WalkPrepareWeatherInfo.kt index d81ca577..060c4e8a 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/component/WalkPrepareWeatherInfo.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/component/WalkPrepareWeatherInfo.kt @@ -23,12 +23,12 @@ 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.presentation.ui.course.walkcourse.walkprepare.model.WalkPreparationMessageModel @Composable fun WalkPrepareWeatherInfo( + walkPreparationMessage: WalkPreparationMessageModel, modifier: Modifier = Modifier, - title: String = "발이 차가워요.. 잠깐 다녀와요!", - subTitle: String = "실외 금지! 실내 놀이로 대체", ) { Row( modifier = modifier @@ -62,14 +62,14 @@ fun WalkPrepareWeatherInfo( Spacer(modifier = Modifier.height(2.dp)) Text( - text = title, + text = walkPreparationMessage.mainMessage, style = PawKeyTheme.typography.subTitle, color = PawKeyTheme.colors.contents, ) Text( - text = subTitle, + text = walkPreparationMessage.subMessage, style = PawKeyTheme.typography.bodySmall, color = PawKeyTheme.colors.contents ) @@ -92,7 +92,7 @@ fun WalkPrepareWeatherInfo( private fun WalkPrepareWeatherInfoPreview() { PawKeyTheme { WalkPrepareWeatherInfo( - subTitle = "10분 내 짧은 산책 / 패딩과 신발 필수" + walkPreparationMessage = WalkPreparationMessageModel() ) } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/model/WalkPreparationMessageModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/model/WalkPreparationMessageModel.kt new file mode 100644 index 00000000..0bd47c69 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/model/WalkPreparationMessageModel.kt @@ -0,0 +1,15 @@ +package com.paw.key.presentation.ui.course.walkcourse.walkprepare.model + +import com.paw.key.domain.entity.walkpreparation.WalkPreparationMessageEntity + +data class WalkPreparationMessageModel( + val mainMessage: String = "", + val subMessage: String = "" +) + +fun WalkPreparationMessageEntity.toUiModel(): WalkPreparationMessageModel { + return WalkPreparationMessageModel( + mainMessage = mainMessage, + subMessage = subMessage + ) +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/state/WalkPrepareContract.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/state/WalkPrepareContract.kt index 3e6ed08f..64e0404b 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/state/WalkPrepareContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcourse/walkprepare/state/WalkPrepareContract.kt @@ -1,13 +1,18 @@ package com.paw.key.presentation.ui.course.walkcourse.walkprepare.state import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.runtime.Immutable +import com.paw.key.domain.entity.walkpreparation.WalkPreparationEntity +import com.paw.key.presentation.ui.course.walkcourse.walkprepare.model.WalkPreparationMessageModel import com.paw.key.presentation.ui.course.walkcourse.walkprepare.model.WalkPrepareItemModel import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +@Immutable data class WalkPrepareState( val walkPrepareItemList: PersistentList = persistentListOf(), + val walkPreparationMessage: WalkPreparationMessageModel = WalkPreparationMessageModel(), val lastAddedItemId: Int? = null, ) { val dummyWalkPrepare = listOf( @@ -17,3 +22,16 @@ data class WalkPrepareState( WalkPrepareItemModel(4, TextFieldState("간식")), ).toImmutableList() } + +fun WalkPrepareState.toEntity(): WalkPreparationEntity { + return WalkPreparationEntity( + preparationList = walkPrepareItemList + .map { item -> item.walkItem.text.toString() } + .filter { text -> text.isNotBlank() } + ) +} + +sealed interface WalkPrepareSideEffect { + data class NavigateToWalkCourse(val routeId: String) : WalkPrepareSideEffect + data class ShowToastMessage(val message: String) : WalkPrepareSideEffect +} \ No newline at end of file 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 32a9a69d..0f1fa2ba 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 @@ -34,10 +34,10 @@ import com.paw.key.R import com.paw.key.core.designsystem.component.DokiBorderButton import com.paw.key.core.designsystem.component.DokiButton import com.paw.key.core.designsystem.component.TopBar +import com.paw.key.core.designsystem.component.walk.WalkReviewInfoHolder 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.WalkReviewImageRow -import com.paw.key.core.designsystem.component.walk.WalkReviewInfoHolder import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewMultipleFilter import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewSingleFilter import com.paw.key.presentation.ui.course.walkreview.state.WalkReviewState @@ -268,7 +268,11 @@ private fun WalkReviewScreen( // 텍필 넣기 BasicTextField( value = state.walkReviewTitle, - onValueChange = onTitleValueChange, + onValueChange = { + if (it.length <= 14) { + onTitleValueChange(it) + } + }, textStyle = PawKeyTheme.typography.bodyActive.copy(color = PawKeyTheme.colors.contents), modifier = Modifier.fillMaxWidth(), decorationBox = { innerTextField -> @@ -298,7 +302,11 @@ private fun WalkReviewScreen( BasicTextField( value = state.walkReviewContent, - onValueChange = onContentValueChange, + onValueChange = { + if (it.length <= 250) { + onContentValueChange(it) + } + }, textStyle = PawKeyTheme.typography.bodyActive.copy(color = PawKeyTheme.colors.contents), modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt index 851ec501..3f92e902 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.material3.Text -import com.paw.key.core.designsystem.component.DogkyButton +import com.paw.key.core.designsystem.component.DokiButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.dbti.component.SelectCard @@ -77,7 +77,7 @@ fun TestScreen( Spacer(modifier = Modifier.weight(1f)) - DogkyButton( + DokiButton( text = "다음으로", onClick = onNextClick, enabled = selectedOptionId != null, diff --git a/app/src/main/java/com/paw/key/presentation/ui/detail/component/DetailImageHolder.kt b/app/src/main/java/com/paw/key/presentation/ui/detail/component/DetailImageHolder.kt index 0472c8d1..ea3c54e6 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/detail/component/DetailImageHolder.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/detail/component/DetailImageHolder.kt @@ -24,7 +24,7 @@ fun DetailImageHolder( modifier: Modifier = Modifier ) { BoxWithConstraints(modifier = modifier.fillMaxWidth()) { - val imageWidth = maxWidth * 0.35f + val imageWidth = maxWidth * 0.317f LazyRow( modifier = Modifier.fillMaxWidth(), @@ -36,7 +36,7 @@ fun DetailImageHolder( contentScale = ContentScale.Crop, modifier = Modifier .size(imageWidth) - .aspectRatio(3f / 3f) + .aspectRatio(1f) .clip(RoundedCornerShape(4.dp)) ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt index c132bd16..ce7eb29b 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/HomeScreen.kt @@ -1,6 +1,5 @@ package com.paw.key.presentation.ui.home -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -17,28 +16,28 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign 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.LoadingScreen import com.paw.key.core.designsystem.component.routeitem.RouteItem import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.core.model.WalkingRouteUiModel import com.paw.key.core.util.UiState +import com.paw.key.presentation.ui.home.component.HomeBanner +import com.paw.key.presentation.ui.home.component.HomeEmptyRoute import com.paw.key.presentation.ui.home.component.HomeStartWalkingRow import com.paw.key.presentation.ui.home.component.HomeTopBar import com.paw.key.presentation.ui.home.component.HomeWalkingInfoHolder import com.paw.key.presentation.ui.home.state.HomeState import com.paw.key.presentation.ui.home.viewmodel.HomeViewModel import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @Composable fun HomeRoute( @@ -48,11 +47,29 @@ fun HomeRoute( ) { val state by viewModel.state.collectAsStateWithLifecycle() - HomeScreen( - paddingValues = paddingValues, - navigateToCourse = navigateToCourse, - state = state - ) + LaunchedEffect(Unit) { + viewModel.fetchPetName() + viewModel.fetchHomeInfo() + viewModel.fetchHomeWeather() + + // Todo: 서버 부담이 있어 추후 변경하고 호출할 예정 + //viewModel.fetchHomeRoute() + } + + when (val uiState = state) { + is UiState.Loading -> { + LoadingScreen() + } + is UiState.Failure -> { /* 에러 UI */ } + is UiState.Empty -> { /* 빈 UI */ } + is UiState.Success -> { + HomeScreen( + paddingValues = paddingValues, + navigateToCourse = navigateToCourse, + state = uiState.data + ) + } + } } @Composable @@ -67,149 +84,118 @@ private fun HomeScreen( modifier = Modifier .fillMaxSize() .background(color = PawKeyTheme.colors.background) - .padding(horizontal = 16.dp) .padding(top = 16.dp) .padding(paddingValues) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, ) { HomeTopBar( - location = "강남구 역삼동", - onLocationClick = {} + homeWeatherModel = state.homeInfo, + onLocationClick = {}, + modifier = Modifier + .padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(24.dp)) HomeWalkingInfoHolder( - walkingInfo = state.walkingInfo + walkingInfo = state.walkingInfo, + modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(24.dp)) HomeStartWalkingRow( - petName = "보리", - onClick = navigateToCourse + petName = state.petName, + onClick = navigateToCourse, + modifier = Modifier.padding(horizontal = 16.dp) ) - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.height(24.dp)) + + HomeBanner() + + Spacer(modifier = Modifier.height(24.dp)) Text( text = "인기있는 산책 루트 추천", style = PawKeyTheme.typography.header3, color = PawKeyTheme.colors.contents, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), textAlign = TextAlign.Start ) Spacer(modifier = Modifier.height(16.dp)) - when (val uiState = state.walkingPopularData) { - is UiState.Success -> { - if (uiState.data.isEmpty()) { - Column ( - modifier = Modifier - .fillMaxWidth(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(R.drawable.img_home_empty), - contentDescription = null - ) - - Text( - text = "곧 추천 루트가 채워질 예정이에요\n" + - "추후에 인기루트를 확인하실 수 있어요!", - color = PawKeyTheme.colors.defaultDark, - style = PawKeyTheme.typography.subTitle, - textAlign = TextAlign.Center - ) - } - } else { - LazyRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - itemsIndexed( - items = uiState.data, - key = { _, item -> item.id } - ) { _, item -> - RouteItem( - routeTitle = item.title, - routeTime = item.time, - routeDate = item.date, - routeImage = item.imageUri, - location = item.location, - onClick = {}, - onClickHeart = {}, - modifier = Modifier.width(itemWidth) - ) - } - } + if (state.walkingPopularData.isEmpty()) { + HomeEmptyRoute( + modifier = Modifier + .padding(horizontal = 16.dp) + ) + } else { + LazyRow( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + itemsIndexed( + items = state.walkingPopularData, + key = { _, item -> item.postId } + ) { _, item -> + RouteItem( + routeTitle = item.title, + routeTime = item.duration.toString(), + routeDate = item.date, + routeImage = item.imageUrl!!, + location = item.regionName, + onClick = {}, + onClickHeart = {}, + modifier = Modifier.width(itemWidth) + ) } + } + } - Spacer(modifier = Modifier.height(23.dp)) - - Text( - text = "비슷한 이용자 루트 추천", - style = PawKeyTheme.typography.header3, - color = PawKeyTheme.colors.contents, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Start - ) - - Spacer(modifier = Modifier.height(16.dp)) - - if (state.walkingRecommendedData.isEmpty()) { - Column ( - modifier = Modifier - .fillMaxWidth(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(R.drawable.img_home_empty), - contentDescription = null - ) - - Text( - text = "곧 추천 루트가 채워질 예정이에요\n" + - "추후에 인기루트를 확인하실 수 있어요!", - color = PawKeyTheme.colors.defaultDark, - style = PawKeyTheme.typography.subTitle, - textAlign = TextAlign.Center - ) - } - } else { - LazyRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - itemsIndexed( - items = state.walkingRecommendedData, - key = { _, item -> item.id } - ) { _, item -> - RouteItem( - routeTitle = item.title, - routeTime = item.time, - routeDate = item.date, - routeImage = item.imageUri, - location = item.location, - onClick = {}, - onClickHeart = {}, - modifier = Modifier.width(itemWidth) - ) - } - } - } + Spacer(modifier = Modifier.height(32.dp)) - Spacer(modifier = Modifier.height(24.dp)) - } + Text( + text = "비슷한 이용자 루트 추천", + style = PawKeyTheme.typography.header3, + color = PawKeyTheme.colors.contents, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + textAlign = TextAlign.Start + ) - is UiState.Loading -> {} - is UiState.Empty -> Text("데이터가 없어요") - is UiState.Failure -> Text("로드 실패") + Spacer(modifier = Modifier.height(16.dp)) + + if (state.walkingRecommendedData.isEmpty()) { + HomeEmptyRoute( + modifier = Modifier + .padding(horizontal = 16.dp) + ) + } else { + LazyRow( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + itemsIndexed( + items = state.walkingRecommendedData, + key = { _, item -> item.postId } + ) { _, item -> + RouteItem( + routeTitle = item.title, + routeTime = item.duration.toString(), + routeDate = item.date, + routeImage = item.imageUrl!!, + location = item.regionName, + onClick = {}, + onClickHeart = {}, + modifier = Modifier.width(itemWidth) + ) + } + } } + + Spacer(modifier = Modifier.height(24.dp)) } } @@ -221,7 +207,6 @@ private fun HomePreview() { paddingValues = PaddingValues(), navigateToCourse = {}, state = HomeState( - walkingPopularData = UiState.Success(WalkingRouteUiModel.Fake.toImmutableList()), walkingRecommendedData = persistentListOf() ) ) diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeBanner.kt b/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeBanner.kt new file mode 100644 index 00000000..f82668b6 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeBanner.kt @@ -0,0 +1,69 @@ +package com.paw.key.presentation.ui.home.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +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 + +@Composable +fun HomeBanner( + modifier: Modifier = Modifier, + firstText : String = "내 반려견은 어떤 성향을 가지고 있을까?\n", + secondText : String = "간단한 테스트를 통해 반려견 성향을 알아보세요!" +) { + Box ( + modifier = modifier + .fillMaxWidth() + .background( + color = PawKeyTheme.colors.primary + ) + ) { + Text( + modifier = Modifier + .padding(start = 16.dp, top = 20.dp, bottom = 20.dp) + .align(Alignment.CenterStart), + text = buildAnnotatedString { + withStyle(style = PawKeyTheme.typography.bodyBold.toSpanStyle()) { + append(firstText) + } + withStyle(style = PawKeyTheme.typography.subButtonDefault.toSpanStyle()) { + append(secondText) + } + }, + color = PawKeyTheme.colors.background + ) + + Image( + painter = painterResource(R.drawable.img_walk_info), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(end = 16.dp) + .size(87.dp,76.dp) + ) + } +} + +@Preview +@Composable +private fun HomeBannerPreview() { + PawKeyTheme { + HomeBanner() + } +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeEmptyRoute.kt b/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeEmptyRoute.kt new file mode 100644 index 00000000..d9b1a81c --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeEmptyRoute.kt @@ -0,0 +1,66 @@ +package com.paw.key.presentation.ui.home.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +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 + +@Composable +fun HomeEmptyRoute( + modifier: Modifier = Modifier +) { + val configuration = LocalConfiguration.current + val screenWidthDp = configuration.screenWidthDp + + val emptyImageSize = if (screenWidthDp < 600) { + 150.dp + } else { + 250.dp + } + + Column( + modifier = modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(R.drawable.img_home_empty), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier + .size(emptyImageSize) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "곧 추천 루트가 채워질 예정이에요\n추후에 인기루트를 확인하실 수 있어요!", + color = PawKeyTheme.colors.defaultDark, + style = PawKeyTheme.typography.subTitle, + textAlign = TextAlign.Center + ) + } +} + +@Preview +@Composable +private fun HomeEmptyRoutePreview() { + PawKeyTheme { + HomeEmptyRoute() + } +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeTopBar.kt b/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeTopBar.kt index 3f74c789..2a3a6ebf 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeTopBar.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/component/HomeTopBar.kt @@ -1,25 +1,34 @@ package com.paw.key.presentation.ui.home.component +import androidx.annotation.DrawableRes import androidx.compose.foundation.Image 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.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.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.component.DogkyFilterBadge import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.presentation.ui.home.model.HomeWeatherModel @Preview(showBackground = true) @Composable private fun PreviewHomeTopBar() { PawKeyTheme { HomeTopBar( - location = "강남구 역삼동", + homeWeatherModel = HomeWeatherModel(), onLocationClick = {}, ) @@ -28,24 +37,69 @@ private fun PreviewHomeTopBar() { @Composable fun HomeTopBar( - location: String, + homeWeatherModel: HomeWeatherModel, onLocationClick: () -> Unit, modifier: Modifier = Modifier, ) { - Row ( + Column( modifier = modifier .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp) ) { Image( imageVector = ImageVector.vectorResource(R.drawable.ic_logo), contentDescription = "logo", + modifier = Modifier.align(Alignment.Start) ) - DogkyFilterBadge( - location = location, - onLocationClick = onLocationClick, - ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + WeatherHolder( + weatherIcon = R.drawable.ic_home_temperature, + content = "${homeWeatherModel.temperature}℃", + ) + + Spacer(modifier = Modifier.width(8.dp)) + + WeatherHolder( + weatherIcon = R.drawable.ic_home_drop, + content = "${homeWeatherModel.rainyMm}mm", + ) + + Spacer(modifier = Modifier.weight(1f)) + + DogkyFilterBadge( + location = homeWeatherModel.region, + onLocationClick = onLocationClick, + ) + } } } + +@Composable +fun WeatherHolder( + @DrawableRes weatherIcon: Int, + content: String, + modifier: Modifier = Modifier +) { + Row ( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + Icon( + imageVector = ImageVector.vectorResource(weatherIcon), + contentDescription = "temperature", + tint = Color.Unspecified + ) + + Text( + text = content, + style = PawKeyTheme.typography.subButtonActive, + color = PawKeyTheme.colors.contents + ) + } +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/model/HomeRouteModel.kt b/app/src/main/java/com/paw/key/presentation/ui/home/model/HomeRouteModel.kt new file mode 100644 index 00000000..affb6d7b --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/home/model/HomeRouteModel.kt @@ -0,0 +1,14 @@ +package com.paw.key.presentation.ui.home.model + +import com.paw.key.core.model.WalkingRouteUiModel +import com.paw.key.domain.entity.home.RouteEntity + +fun RouteEntity.toUiModel() = WalkingRouteUiModel( + postId = postId.toInt(), + regionName = regionName, + title = title, + date = date, + duration = duration, + isLiked = isLiked, + imageUrl = imageUrl +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/model/HomeWeatherModel.kt b/app/src/main/java/com/paw/key/presentation/ui/home/model/HomeWeatherModel.kt new file mode 100644 index 00000000..5e64f36b --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/home/model/HomeWeatherModel.kt @@ -0,0 +1,15 @@ +package com.paw.key.presentation.ui.home.model + +import com.paw.key.domain.entity.home.HomeWeatherEntity + +data class HomeWeatherModel( + val temperature : Int = -1, + val rainyMm : Int = -1, + val region : String = "" +) + +fun HomeWeatherEntity.toUiModel() = HomeWeatherModel( + temperature = temperature, + rainyMm = rainyMm, + region = region +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/model/WalkingInfo.kt b/app/src/main/java/com/paw/key/presentation/ui/home/model/WalkingInfo.kt index c183334f..e9ec25b0 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/model/WalkingInfo.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/model/WalkingInfo.kt @@ -1,6 +1,8 @@ package com.paw.key.presentation.ui.home.model import androidx.compose.runtime.Immutable +import com.paw.key.core.extension.toTimeFormat +import com.paw.key.domain.entity.home.HomeInfoEntity @Immutable data class WalkingInfo( @@ -8,3 +10,11 @@ data class WalkingInfo( val walkingTime : String = "00:00:00", val walkingCount: Int = 0 ) + +fun HomeInfoEntity.toUiModel() = WalkingInfo( + cumulativeDistance = distance, + walkingTime = totalTime.toTimeFormat(), + walkingCount = count +) + + diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt b/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt index 3edf8d7f..0b39ebc5 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/state/HomeContract.kt @@ -1,15 +1,19 @@ package com.paw.key.presentation.ui.home.state -import com.paw.key.core.util.UiState -import com.paw.key.presentation.ui.home.model.WalkingInfo +import androidx.compose.runtime.Immutable import com.paw.key.core.model.WalkingRouteUiModel +import com.paw.key.presentation.ui.home.model.HomeWeatherModel +import com.paw.key.presentation.ui.home.model.WalkingInfo import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +@Immutable data class HomeState( - val walkingPopularData : UiState> = UiState.Loading, + val walkingPopularData : ImmutableList = persistentListOf(), val walkingRecommendedData: ImmutableList = persistentListOf(), - val walkingInfo: WalkingInfo = WalkingInfo() + val walkingInfo: WalkingInfo = WalkingInfo(), + val homeInfo: HomeWeatherModel = HomeWeatherModel(), + val petName: String = "" ) sealed interface HomeSideEffect { diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt index aa96977d..40e5b5e4 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/viewmodel/HomeViewModel.kt @@ -1,8 +1,12 @@ package com.paw.key.presentation.ui.home.viewmodel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.paw.key.core.extension.updateSuccess import com.paw.key.core.util.UiState -import com.paw.key.core.model.WalkingRouteUiModel +import com.paw.key.domain.repository.home.HomeRepository +import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.presentation.ui.home.model.toUiModel import com.paw.key.presentation.ui.home.state.HomeSideEffect import com.paw.key.presentation.ui.home.state.HomeState import dagger.hilt.android.lifecycle.HiltViewModel @@ -12,22 +16,82 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( + private val repository: HomeRepository, + private val localStorageRepository: LocalStorageRepository ): ViewModel() { - private val _state = MutableStateFlow(HomeState()) + private val _state = MutableStateFlow>(UiState.Loading) val state = _state.asStateFlow() private val _sideEffect = MutableSharedFlow() val sideEffect = _sideEffect.asSharedFlow() - init { - _state.update { - it.copy( - walkingPopularData = UiState.Success(WalkingRouteUiModel.Fake.toImmutableList()) - ) + fun fetchHomeInfo() { + viewModelScope.launch { + repository.getHomeInfo() + .onSuccess { result -> + _state.update { currentState -> + when (currentState) { + is UiState.Loading -> UiState.Success( + HomeState(walkingInfo = result.toUiModel()) + ) + is UiState.Success -> currentState.copy( + data = currentState.data.copy(walkingInfo = result.toUiModel()) + ) + else -> currentState + } + } + } + .onFailure(Timber::e) + } + } + + fun fetchPetName() { + viewModelScope.launch { + val petName = localStorageRepository.getPetName() + _state.updateSuccess { + it.copy( + petName = petName + ) + } + } + } + + fun fetchHomeWeather() { + viewModelScope.launch { + repository.getHomeWeather() + .onSuccess { result -> + _state.updateSuccess { + it.copy( + homeInfo = result.toUiModel() + ) + } + } + .onFailure(Timber::e) + } + } + + fun fetchHomeRoute() { + viewModelScope.launch { + repository.getHomeRecommended() + .onSuccess { result -> + _state.updateSuccess { + it.copy( + walkingRecommendedData = result.similarUserRoutes + .map { route -> route.toUiModel() } + .toImmutableList(), + walkingPopularData = result.popularRoutes + .map { route -> route.toUiModel() } + .toImmutableList() + ) + } + } + .onFailure(Timber::e) } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt index d9173b0c..60ef76d3 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/LoginScreen.kt @@ -1,5 +1,6 @@ package com.paw.key.presentation.ui.login +import android.app.Activity import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -10,6 +11,7 @@ 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.offset import androidx.compose.foundation.layout.padding @@ -76,7 +78,7 @@ fun LoginRoute( LoginScreen( paddingValues = paddingValues, onGoogleSignIn = { - viewModel.onGoogleSignIn(context = context, onSuccess = navigateHome) + viewModel.onGoogleSignIn(context = context as Activity) }, onKakaoSignIn = { viewModel.onKakaoSignIn(context = context) @@ -139,7 +141,11 @@ fun LoginScreen( Image( painter = painterResource(R.drawable.img_login_sub), contentDescription = stringResource(R.string.ic_login_sub_image), - contentScale = ContentScale.Crop, + contentScale = ContentScale.Fit, + modifier = Modifier + .fillMaxWidth() + .height(34.dp) + .padding(horizontal = 100.dp) ) LoginSocialButton( @@ -178,7 +184,7 @@ fun LoginScreen( modifier = Modifier .size(370.dp) .align(Alignment.CenterEnd) - .offset(x = 70.dp), + .offset(x = 10.dp) ) } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt index 0ef57ced..ee813393 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/viewmodel/LoginViewModel.kt @@ -3,7 +3,8 @@ package com.paw.key.presentation.ui.login.viewmodel import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.paw.key.domain.usecase.LoginUseCase +import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.domain.usecase.auth.LoginUseCase import com.paw.key.presentation.ui.login.state.LoginSideEffect import com.paw.key.presentation.ui.login.state.LoginState import dagger.hilt.android.lifecycle.HiltViewModel @@ -17,7 +18,8 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val loginUseCase: LoginUseCase + private val loginUseCase: LoginUseCase, + private val localStorageRepository: LocalStorageRepository, ) : ViewModel() { private val _state = MutableStateFlow(LoginState()) val state: StateFlow @@ -29,13 +31,17 @@ class LoginViewModel @Inject constructor( fun onGoogleSignIn( context: Context, - onSuccess: () -> Unit, ) { viewModelScope.launch { loginUseCase.invokeGoogleLogin(context) .onSuccess { - // Todo : isNew확인 - _sideEffect.emit(LoginSideEffect.NavigateToHome) + localStorageRepository.saveUserProvider("GOOGLE") + + if (it) { + _sideEffect.emit(LoginSideEffect.NavigateToSignUp) + } else { + _sideEffect.emit(LoginSideEffect.NavigateToHome) + } } .onFailure { e -> Timber.e(e, "Google sign-in failed") @@ -49,11 +55,12 @@ class LoginViewModel @Inject constructor( viewModelScope.launch { loginUseCase.invokeKakaoLogin(context) .onSuccess { - // Todo: isNewUser가 true이면이니 signup false는 home + localStorageRepository.saveUserProvider("KAKAO") + // isNewUser가 true이면이니 signup false는 home if (it) { _sideEffect.emit(LoginSideEffect.NavigateToSignUp) } else { - _sideEffect.emit(LoginSideEffect.NavigateToSignUp) + _sideEffect.emit(LoginSideEffect.NavigateToHome) } } } @@ -70,4 +77,4 @@ class LoginViewModel @Inject constructor( fun onPasswordVisibilityChanged() { _state.update { it.copy(isPasswordVisible = !it.isPasswordVisible) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/MainActivity.kt b/app/src/main/java/com/paw/key/presentation/ui/main/MainActivity.kt index cc1a7a27..3de08d99 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/main/MainActivity.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/main/MainActivity.kt @@ -28,7 +28,7 @@ class MainActivity : ComponentActivity() { setContent { PawKeyTheme { - MainRoute() + MainScreen() } } } 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 89df215a..18662d51 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 @@ -16,12 +16,12 @@ import com.paw.key.presentation.ui.course.walkreview.navigation.navigateWalkRevi import com.paw.key.presentation.ui.home.navigation.navigateHome import com.paw.key.presentation.ui.home.navigation.navigateHomeLocationSetting import com.paw.key.presentation.ui.login.navigation.navigateLogin -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType -import com.paw.key.presentation.ui.mypage.courseinfo.navigation.navigateCourseInfo import com.paw.key.presentation.ui.mypage.main.navigation.navigateMyPage -import com.paw.key.presentation.ui.mypage.petinfo.navigation.navigatePetProfile -import com.paw.key.presentation.ui.mypage.petinfo.navigation.navigatePetProfileList -import com.paw.key.presentation.ui.mypage.userinfo.navigation.navigateUserProfile +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType +import com.paw.key.presentation.ui.mypage.route.courseinfo.navigation.navigateCourseInfo +import com.paw.key.presentation.ui.mypage.route.petinfo.navigation.navigatePetProfile +import com.paw.key.presentation.ui.mypage.route.petinfo.navigation.navigatePetProfileList +import com.paw.key.presentation.ui.mypage.route.userinfo.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.navigateSignUp @@ -115,8 +115,14 @@ class MainNavigator( } // walk course - fun navigateWalkCourse(navOptions: NavOptions? = null) { - navController.navigateWalkCourse(navOptions = navOptions) + fun navigateWalkCourse( + navOptions: NavOptions? = null, + routeId: String + ) { + navController.navigateWalkCourse( + navOptions = navOptions, + routeId = routeId + ) } fun navigateWalkReview( diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/MainScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/main/MainScreen.kt index 90ae2dce..8ae85e46 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/main/MainScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/main/MainScreen.kt @@ -20,12 +20,6 @@ import androidx.compose.ui.platform.LocalContext import com.paw.key.presentation.ui.main.component.MainBottomBar import kotlinx.collections.immutable.toImmutableList -@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) -@Composable -fun MainRoute() { - MainScreen() -} - @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @Composable fun MainScreen( 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 7370380e..6ac58367 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 @@ -18,11 +18,11 @@ import com.paw.key.presentation.ui.course.walkreview.navigation.walkReviewNavGra import com.paw.key.presentation.ui.home.navigation.homeLocationSettingNavGraph import com.paw.key.presentation.ui.home.navigation.homeNavGraph import com.paw.key.presentation.ui.login.navigation.loginNavGraph -import com.paw.key.presentation.ui.mypage.courseinfo.navigation.courseInfoNavGraph import com.paw.key.presentation.ui.mypage.main.navigation.myPageNavGraph -import com.paw.key.presentation.ui.mypage.petinfo.navigation.petProfileListNavGraph -import com.paw.key.presentation.ui.mypage.petinfo.navigation.petProfileNavGraph -import com.paw.key.presentation.ui.mypage.userinfo.navigation.userProfileNavGraph +import com.paw.key.presentation.ui.mypage.route.courseinfo.navigation.courseInfoNavGraph +import com.paw.key.presentation.ui.mypage.route.petinfo.navigation.petProfileListNavGraph +import com.paw.key.presentation.ui.mypage.route.petinfo.navigation.petProfileNavGraph +import com.paw.key.presentation.ui.mypage.route.userinfo.navigation.userProfileNavGraph import com.paw.key.presentation.ui.onboard.navigation.onboardingNavGraph import com.paw.key.presentation.ui.region.navigation.regionalNavGraph import com.paw.key.presentation.ui.signup.navigation.signUpNavGraph @@ -101,7 +101,7 @@ fun PawKeyNavHost( walkReviewNavGraph( paddingValues = paddingValues, navigateHome = navigator::navigateHome, - navigateWalkDetail = navigator::navigateWalkCourse, // Todo 상세 정보 뷰로 + navigateWalkDetail = {}, // Todo 상세 정보 뷰로 ) communityNavGraph( @@ -120,6 +120,9 @@ fun PawKeyNavHost( }, navigatePetProfileList = navigator::navigatePetProfileList, navigateUserProfile = navigator::navigateUserProfile, + navigateLogin = { + navigator.navigateLogin(clearStackNavOptions) + } ) courseInfoNavGraph( @@ -162,7 +165,10 @@ fun PawKeyNavHost( launchSingleTop = true } navigator.navigateOnboarding(navOptions = options) - } + }, + navigateToHome = { + navigator.navigateHome(clearStackNavOptions) + }, ) onboardingNavGraph( @@ -187,7 +193,7 @@ fun PawKeyNavHost( }, // Todo: Home 으로 수정 navigateHome = { - navigator.navigateSignUp(clearStackNavOptions) + navigator.navigateHome(clearStackNavOptions) }, snackBarHostState = snackbarHostState ) @@ -200,7 +206,13 @@ fun PawKeyNavHost( ) signUpNavGraph( - navigateUp = navigator::navigateUp, + navigateUp = { + val options = navOptions { + popUpTo(0) { inclusive = true } + launchSingleTop = true + } + navigator.navigateLogin(options) + }, navigateToHome = { val options = navOptions { popUpTo(0) { inclusive = true } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt index 898f83ad..9b3f84f7 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt @@ -1,5 +1,6 @@ package com.paw.key.presentation.ui.mypage.main +import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -10,20 +11,23 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext 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.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType +import com.paw.key.core.extension.collectSideEffect import com.paw.key.presentation.ui.mypage.main.component.MyList import com.paw.key.presentation.ui.mypage.main.component.MyPageCard import com.paw.key.presentation.ui.mypage.main.component.OwnerCard import com.paw.key.presentation.ui.mypage.main.component.SettingList import com.paw.key.presentation.ui.mypage.main.model.MyListState +import com.paw.key.presentation.ui.mypage.main.model.MyPageSideEffect import com.paw.key.presentation.ui.mypage.main.model.MyPageState import com.paw.key.presentation.ui.mypage.main.viewmodel.MyPageViewModel +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType @Composable fun MyPageRoute( @@ -33,14 +37,28 @@ fun MyPageRoute( navigateCourseInfo: (CourseType) -> Unit, navigatePetProfileList: () -> Unit, navigateUserProfile: () -> Unit, + navigateToLogin: () -> Unit, modifier: Modifier = Modifier, viewModel: MyPageViewModel = hiltViewModel(), ) { + val context = LocalContext.current val state by viewModel.state.collectAsStateWithLifecycle() + viewModel.sideEffect.collectSideEffect { + when(it) { + MyPageSideEffect.NavigateNext -> TODO() + MyPageSideEffect.NavigateToLogin -> navigateToLogin() + MyPageSideEffect.NavigateUp -> TODO() + else -> { + Toast.makeText(context, "오류가 발생했습니다.", Toast.LENGTH_SHORT).show() + } + } + } + MyPageScreen( state = state, paddingValues = paddingValues, + deleteUser = viewModel::removeUser, navigateUp = navigateUp, navigatePetProfile = navigatePetProfile, navigateCourseInfo = navigateCourseInfo, @@ -54,6 +72,7 @@ fun MyPageRoute( fun MyPageScreen( state: MyPageState, paddingValues: PaddingValues, + deleteUser: () -> Unit, navigateUp: () -> Unit, navigatePetProfile: () -> Unit, navigateCourseInfo: (CourseType) -> Unit, @@ -70,7 +89,8 @@ fun MyPageScreen( TopBar( title = "마이페이지", onBackClick = navigateUp, - isBackVisible = false + isBackVisible = false, + onClickTitle = deleteUser ) LazyColumn( @@ -80,23 +100,25 @@ fun MyPageScreen( .padding(horizontal = 16.dp, vertical = 18.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - item { OwnerCard( ownerName = state.ownerName, - ownerEmail = state.petName, + ownerEmail = "", navigateUserProfile = navigateUserProfile ) } item { - MyPageCard( - userName = "단지", - userAge = "6개월", - userGender = "여아", - dogBreed = "우지", - buttonTitle = "DBTI검사하러 가기", - ) + with(state.petInfo) { + MyPageCard( + userName = petName, + userAge = "6개월", + userGender = petGender, + dogBreed = petBreed, + buttonTitle = "DBTI검사하러 가기", + dogImage = petImageUrl + ) + } } item { @@ -134,9 +156,6 @@ private fun MyPageScreenPreview() { MyPageScreen( state = MyPageState( ownerName = "키큰오팔전차님", - petName = "포비", - petAge = "12세", - petGender = "여아", petTags = listOf("조금 느긋해요", "#오토바이소리", "#대형견"), walkCount = 7, totalDistance = "14km" @@ -147,6 +166,7 @@ private fun MyPageScreenPreview() { navigateCourseInfo = {}, navigatePetProfileList = {}, navigateUserProfile = {}, + deleteUser = {} ) } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/component/MypageCard.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/component/MypageCard.kt index 271ba1e5..b005e79b 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/component/MypageCard.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/component/MypageCard.kt @@ -1,6 +1,5 @@ package com.paw.key.presentation.ui.mypage.main.component -import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -18,11 +17,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import coil.request.ImageRequest +import com.paw.key.core.designsystem.component.UrlImage import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.extension.noRippleClickable @@ -32,6 +29,7 @@ fun MyPageCard( userAge: String, userGender: String, dogBreed: String, + dogImage: String?, buttonTitle: String, modifier: Modifier = Modifier, ) { @@ -50,7 +48,7 @@ fun MyPageCard( userAge = userAge, userGender = userGender, dogBreed = dogBreed, - image = Uri.parse("https://picsum.photos/200"), + image = dogImage, modifier = modifier ) @@ -74,7 +72,7 @@ private fun MyPageCardContent( userAge: String, userGender: String, dogBreed: String, - image: Uri?, + image: String?, modifier: Modifier = Modifier, ) { Row( @@ -83,16 +81,12 @@ private fun MyPageCardContent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(image) - .crossfade(true) - .build(), - contentDescription = null, + UrlImage( + url = image!!, modifier = Modifier .size(64.dp) .clip(CircleShape), - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, ) Column( @@ -107,7 +101,7 @@ private fun MyPageCardContent( ) Text( - "$userAge / $userGender/ $dogBreed", + "$userAge / $userGender / $dogBreed", style = PawKeyTheme.typography.subButtonDefault, color = PawKeyTheme.colors.defaultDark ) @@ -150,7 +144,8 @@ private fun MyPageCardPreview() { userAge = "20", userGender = "남", dogBreed = "견종 이름", - buttonTitle = "DBTI 검사하러 가기" + buttonTitle = "DBTI 검사하러 가기", + dogImage = null ) } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/model/MyPageContract.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/model/MyPageContract.kt index f814846a..91871940 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/model/MyPageContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/model/MyPageContract.kt @@ -1,22 +1,21 @@ package com.paw.key.presentation.ui.mypage.main.model -import android.net.Uri import androidx.compose.runtime.Immutable +import com.paw.key.presentation.ui.mypage.model.PetInfoModel - @Immutable - data class MyPageState( - val ownerName: String = "김도기님", - val petName: String = "포비", - val petAge: String = "12세", - val petGender: String = "여아", - val petImageUrl: Uri ?= null, - val petTags: List = listOf("조금 느긋해요", "#오토바이소리", "#대형견"), - val walkCount: Int = 0, - val totalDistance: String = "14km" - ) +@Immutable +data class MyPageState( + val ownerName: String = "님", -sealed class MyPageSideEffect { - data class ShowSnackBar(val message: String) : MyPageSideEffect() - data object NavigateUp : MyPageSideEffect() - data object NavigateNext : MyPageSideEffect() + val petInfo : PetInfoModel = PetInfoModel(), + val petTags: List = emptyList(), + val walkCount: Int = 0, + val totalDistance: String = "", +) + +sealed interface MyPageSideEffect { + data class ShowSnackBar(val message: String) : MyPageSideEffect + data object NavigateUp : MyPageSideEffect + data object NavigateNext : MyPageSideEffect + data object NavigateToLogin : MyPageSideEffect } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/navigation/MyPageNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/navigation/MyPageNavigation.kt index 4fdd340d..b5e20afe 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/navigation/MyPageNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/navigation/MyPageNavigation.kt @@ -1,15 +1,14 @@ package com.paw.key.presentation.ui.mypage.main.navigation import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.SnackbarHostState import androidx.compose.ui.Modifier import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.paw.key.core.navigation.MainTabRoute -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType import com.paw.key.presentation.ui.mypage.main.MyPageRoute +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType import kotlinx.serialization.Serializable fun NavController.navigateMyPage( @@ -25,6 +24,7 @@ fun NavGraphBuilder.myPageNavGraph( navigateCourseInfo: (CourseType) -> Unit, navigatePetProfileList: () -> Unit, navigateUserProfile: () -> Unit, + navigateLogin: () -> Unit, modifier: Modifier = Modifier ) { composable { @@ -35,6 +35,7 @@ fun NavGraphBuilder.myPageNavGraph( navigateCourseInfo = navigateCourseInfo, navigatePetProfileList = navigatePetProfileList, navigateUserProfile = navigateUserProfile, + navigateToLogin = navigateLogin, modifier = modifier ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/viewmodel/MyPageViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/viewmodel/MyPageViewModel.kt index 18c50a11..475f17d2 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/viewmodel/MyPageViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/viewmodel/MyPageViewModel.kt @@ -2,69 +2,95 @@ package com.paw.key.presentation.ui.mypage.main.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.paw.key.core.app.AppRestarter import com.paw.key.domain.repository.localstorage.LocalStorageRepository -import com.paw.key.domain.repository.petprofile.PetProfileRepository -import com.paw.key.domain.repository.userprofile.UserProfileRepository +import com.paw.key.domain.repository.user.UserRepository import com.paw.key.presentation.ui.mypage.main.model.MyPageSideEffect import com.paw.key.presentation.ui.mypage.main.model.MyPageState +import com.paw.key.presentation.ui.mypage.model.toUiModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel class MyPageViewModel @Inject constructor( - private val petProfileRepository: PetProfileRepository, - private val userProfileRepository: UserProfileRepository, - private val localRepository: LocalStorageRepository + private val userRepository: UserRepository, + private val localRepository: LocalStorageRepository, + private val appRestarter: AppRestarter ) : ViewModel() { private val _state = MutableStateFlow(MyPageState()) val state: StateFlow get() = _state.asStateFlow() //get할때마다 업데이트 private val _sideEffect = MutableSharedFlow() - val sideEffect: MutableSharedFlow = _sideEffect + val sideEffect = _sideEffect.asSharedFlow() init { - viewModelScope.launch { - val userId = localRepository.getUserId() - getUserProfiles(userId) - getPetProfiles(userId) - } + getUserProfiles() + getPetProfiles() } - fun getUserProfiles(userId: Int) { + fun getUserProfiles() { + Timber.e("getUserProfiles") viewModelScope.launch { - userProfileRepository.getUserProfiles(userId) + userRepository.getUserProfiles() .onSuccess { user -> + Timber.e("getUserProfiles: $user") _state.update { state -> - state.copy(ownerName = "${user.name}님") + state.copy( + ownerName = "${user.name}님", + ) } }.onFailure { e -> + Timber.e("getUserProfiles: $e") _sideEffect.emit(MyPageSideEffect.ShowSnackBar("유저 프로필 불러오기 실패")) } } } - fun getPetProfiles(userId: Int) { + fun getPetProfiles() { viewModelScope.launch { - petProfileRepository.getPetProfiles(userId) - .onSuccess { - _state.value = _state.value.copy( - petName = it.first().name, - petAge = it.first().age.toString(), - petGender = it.first().gender, - petImageUrl = it.first().imageUrl, - petTags = it.first().traits.map { trait -> trait.option}, - walkCount = it.first().walkCount - ) + val petId = localRepository.getPetId() + + userRepository.getPetProfiles(petId) + .onSuccess { result -> + _state.update { currentState -> + currentState.copy( + petInfo = result.toUiModel() + ) + } + Timber.e("getPetProfiles: $result") + Timber.e("getPetProfiles: ${_state.value.petInfo}") }.onFailure { + Timber.e("getPetProfiles: $it") _sideEffect.emit(MyPageSideEffect.ShowSnackBar("펫 프로필 불러오기 실패")) } } } -} \ No newline at end of file + + fun removeUser() { + viewModelScope.launch { + val userProvider = localRepository.getUserProvider() + + Timber.e("userProvider: $userProvider") + + userRepository.deleteUser(userProvider) + .onSuccess { + Timber.e("유저 삭제 성공") + localRepository.clearInfo() + appRestarter.restartApp() + } + .onFailure { + Timber.e(it) + _sideEffect.emit(MyPageSideEffect.ShowSnackBar("유저 삭제 실패")) + } + } + } +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/model/PetInfoModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/model/PetInfoModel.kt new file mode 100644 index 00000000..b29fdd0f --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/model/PetInfoModel.kt @@ -0,0 +1,29 @@ +package com.paw.key.presentation.ui.mypage.model + +import com.paw.key.domain.entity.petprofile.PetProfileEntity + +data class PetInfoModel( + val petId: Int = -1, + val petImageUrl: String = "", + val petName: String = "", + val petBirthday: String = "", + val petGender: String = "", + val petBreed: String = "", + val petAge: String = "", + val petNeutered: Boolean = false, + val petDbtiName: String = "", + val petDbtiDescription: String = "" +) + +fun PetProfileEntity.toUiModel() = PetInfoModel( + petId = petId.toInt(), + petImageUrl = imageUrl, + petName = name, + petBirthday = birth, + petGender = gender, + petBreed = breed, + petAge = age, + petNeutered = isNeutered, + petDbtiName = dbtiName.orEmpty(), + petDbtiDescription = dbtiDescription.orEmpty() +) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt deleted file mode 100644 index afa14118..00000000 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.paw.key.presentation.ui.mypage.petinfo.viewmodel - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.paw.key.domain.repository.localstorage.LocalStorageRepository -import com.paw.key.domain.repository.petprofile.PetProfileRepository -import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileSideEffect -import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileState -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class PetProfileViewModel @Inject constructor( - private val petProfileRepository: PetProfileRepository, - private val localRepository: LocalStorageRepository -) : ViewModel() { - - private val _state = MutableStateFlow(PetProfileState()) - val state: StateFlow = _state.asStateFlow() - - private val _sideEffect = MutableSharedFlow() // 필요 시 따로 Contract로 분리 가능 - val sideEffect: MutableSharedFlow = _sideEffect - - init { - viewModelScope.launch { - val userId = localRepository.getUserId() - getPetProfiles(userId) - } - } - - fun getPetProfiles(userId: Int) { - viewModelScope.launch { - petProfileRepository.getPetProfiles(userId) - .onSuccess { result -> - Log.d("PetProfileViewModel", "펫 프로필 불러오기 성공: ${result.size}마리") - _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("펫 프로필 불러오기 성공 (${result.size}마리)")) - // 필요하면 내부 상태 저장 - // _state.update { it.copy(profiles = result) } - _state.update { it -> - it.copy( - imageUrl = result.first().imageUrl, - name = result.first().name, - gender = result.first().gender, - breed = result.first().breed, - age = result.first().age.toString(), - energyLevel = result.first().traits.first().option, - socialLevel = result.first().traits.first().option, - isNeutered = result.first().isNeutered - ) - } - } - .onFailure { e -> - Log.e("PetProfileViewModel", "펫 프로필 불러오기 실패", e) - _sideEffect.emit(PetProfileSideEffect.ShowSnackBar(e.message ?: "알 수 없는 오류")) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/CourseInfoScreen.kt similarity index 92% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/CourseInfoScreen.kt index 27d1cdfa..60f321d9 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/CourseInfoScreen.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.courseinfo +package com.paw.key.presentation.ui.mypage.route.courseinfo import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -17,8 +17,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.component.routeitem.RouteItem import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseData -import com.paw.key.presentation.ui.mypage.courseinfo.viewmodel.CourseInfoViewModel +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseData +import com.paw.key.presentation.ui.mypage.route.courseinfo.viewmodel.CourseInfoViewModel @Composable diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/component/CourseRouteItem.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/component/CourseRouteItem.kt similarity index 97% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/component/CourseRouteItem.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/component/CourseRouteItem.kt index 669d6b64..fee6d721 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/component/CourseRouteItem.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/component/CourseRouteItem.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.courseinfo.component +package com.paw.key.presentation.ui.mypage.route.courseinfo.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -30,7 +30,6 @@ import coil.compose.AsyncImage import com.paw.key.R import com.paw.key.core.designsystem.component.DogkyFilterBadge import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.core.designsystem.theme.PretendardBold @Composable fun CourseRouteItem( diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/component/MyReviewCard.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/component/MyReviewCard.kt similarity index 98% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/component/MyReviewCard.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/component/MyReviewCard.kt index 9b440999..02a8040b 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/component/MyReviewCard.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/component/MyReviewCard.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.courseinfo.component +package com.paw.key.presentation.ui.mypage.route.courseinfo.component import androidx.annotation.DrawableRes import androidx.compose.foundation.background diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt similarity index 85% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt index 3e7d8080..fd075b0e 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.courseinfo.model +package com.paw.key.presentation.ui.mypage.route.courseinfo.model enum class CourseType( val courseType: String, diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/navigation/CourseInfoNavigation.kt similarity index 74% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/navigation/CourseInfoNavigation.kt index c5a73b21..bcfa510f 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/navigation/CourseInfoNavigation.kt @@ -1,11 +1,11 @@ -package com.paw.key.presentation.ui.mypage.courseinfo.navigation +package com.paw.key.presentation.ui.mypage.route.courseinfo.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import com.paw.key.presentation.ui.mypage.courseinfo.CourseInfoRoute -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType +import com.paw.key.presentation.ui.mypage.route.courseinfo.CourseInfoRoute +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt similarity index 78% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt index 6d732138..f0267c00 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt @@ -1,10 +1,10 @@ -package com.paw.key.presentation.ui.mypage.courseinfo.viewmodel +package com.paw.key.presentation.ui.mypage.route.courseinfo.viewmodel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.navigation.toRoute -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType -import com.paw.key.presentation.ui.mypage.courseinfo.navigation.CourseInfo +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType +import com.paw.key.presentation.ui.mypage.route.courseinfo.navigation.CourseInfo import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileListScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileListScreen.kt similarity index 90% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileListScreen.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileListScreen.kt index 5d52eef5..9588bdd8 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileListScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileListScreen.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.petinfo +package com.paw.key.presentation.ui.mypage.route.petinfo import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -14,8 +14,8 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.presentation.ui.mypage.petinfo.component.PetInfoCard -import com.paw.key.presentation.ui.mypage.petinfo.viewmodel.PetProfileViewModel +import com.paw.key.presentation.ui.mypage.route.petinfo.component.PetInfoCard +import com.paw.key.presentation.ui.mypage.route.petinfo.viewmodel.PetProfileViewModel @Composable diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt similarity index 98% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt index afdbf9eb..e02843a4 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.petinfo +package com.paw.key.presentation.ui.mypage.route.petinfo import android.Manifest import android.net.Uri @@ -46,7 +46,7 @@ import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.extension.noRippleClickable -import com.paw.key.presentation.ui.mypage.petinfo.viewmodel.PetProfileViewModel +import com.paw.key.presentation.ui.mypage.route.petinfo.viewmodel.PetProfileViewModel 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 diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/component/PetInfoCard.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/component/PetInfoCard.kt similarity index 98% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/component/PetInfoCard.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/component/PetInfoCard.kt index d91bca28..659ec323 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/component/PetInfoCard.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/component/PetInfoCard.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.petinfo.component +package com.paw.key.presentation.ui.mypage.route.petinfo.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt similarity index 74% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt index 89d23be0..539b1130 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt @@ -1,7 +1,8 @@ -package com.paw.key.presentation.ui.mypage.petinfo.model +package com.paw.key.presentation.ui.mypage.route.petinfo.model import android.net.Uri import androidx.compose.runtime.Immutable +import com.paw.key.presentation.ui.mypage.model.PetInfoModel @Immutable data class PetProfileState( @@ -13,7 +14,9 @@ data class PetProfileState( val age: String = "4세", val isNeutered: Boolean = true, val energyLevel: String = "활동적이에요", - val socialLevel: String = "불편해해요" + val socialLevel: String = "불편해해요", + + val petInfo: PetInfoModel = PetInfoModel() ) sealed class PetProfileSideEffect { data class ShowSnackBar(val message: String) : PetProfileSideEffect() diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/navigation/PetProfileListNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/navigation/PetProfileListNavigation.kt similarity index 84% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/navigation/PetProfileListNavigation.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/navigation/PetProfileListNavigation.kt index 2c3e062e..634e43df 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/navigation/PetProfileListNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/navigation/PetProfileListNavigation.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.petinfo.navigation +package com.paw.key.presentation.ui.mypage.route.petinfo.navigation import androidx.compose.ui.Modifier import androidx.navigation.NavController @@ -6,7 +6,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.paw.key.core.navigation.Route -import com.paw.key.presentation.ui.mypage.petinfo.PetProfileListRoute +import com.paw.key.presentation.ui.mypage.route.petinfo.PetProfileListRoute import kotlinx.serialization.Serializable fun NavController.navigatePetProfileList( diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/navigation/PetProfileNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/navigation/PetProfileNavigation.kt similarity index 78% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/navigation/PetProfileNavigation.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/navigation/PetProfileNavigation.kt index 7ee4e18b..330b6e56 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/navigation/PetProfileNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/navigation/PetProfileNavigation.kt @@ -1,12 +1,11 @@ -package com.paw.key.presentation.ui.mypage.petinfo.navigation +package com.paw.key.presentation.ui.mypage.route.petinfo.navigation -import androidx.compose.ui.Modifier import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.paw.key.core.navigation.Route -import com.paw.key.presentation.ui.mypage.petinfo.PetProfileRoute +import com.paw.key.presentation.ui.mypage.route.petinfo.PetProfileRoute import kotlinx.serialization.Serializable fun NavController.navigatePetProfile( diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt new file mode 100644 index 00000000..2583596d --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt @@ -0,0 +1,52 @@ +package com.paw.key.presentation.ui.mypage.route.petinfo.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.domain.repository.user.UserRepository +import com.paw.key.presentation.ui.mypage.model.toUiModel +import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileSideEffect +import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class PetProfileViewModel @Inject constructor( + private val userRepository: UserRepository, + private val localRepository: LocalStorageRepository +) : ViewModel() { + + private val _state = MutableStateFlow(PetProfileState()) + val state: StateFlow = _state.asStateFlow() + + private val _sideEffect = MutableSharedFlow() // 필요 시 따로 Contract로 분리 가능 + val sideEffect = _sideEffect.asSharedFlow() + + init { + getPetProfiles() + } + + fun getPetProfiles() { + viewModelScope.launch { + val petId = localRepository.getPetId() + + userRepository.getPetProfiles(petId) + .onSuccess { result -> + _state.update { currentState -> + currentState.copy( + petInfo = result.toUiModel() + ) + } + }.onFailure { + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("펫 프로필 불러오기 실패")) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/UserProfileScreen.kt similarity index 91% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/UserProfileScreen.kt index e97fd315..6ae531cd 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/UserProfileScreen.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.userinfo +package com.paw.key.presentation.ui.mypage.route.userinfo import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -17,10 +17,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.presentation.ui.mypage.userinfo.component.UserEditTextField -import com.paw.key.presentation.ui.mypage.userinfo.component.UserGenderButton -import com.paw.key.presentation.ui.mypage.userinfo.component.UserProfileItem -import com.paw.key.presentation.ui.mypage.userinfo.viewmodel.UserProfileViewModel +import com.paw.key.presentation.ui.mypage.route.userinfo.component.UserEditTextField +import com.paw.key.presentation.ui.mypage.route.userinfo.component.UserGenderButton +import com.paw.key.presentation.ui.mypage.route.userinfo.component.UserProfileItem +import com.paw.key.presentation.ui.mypage.route.userinfo.viewmodel.UserProfileViewModel @Composable fun UserProfileRoute( diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserEditTextField.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserEditTextField.kt similarity index 98% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserEditTextField.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserEditTextField.kt index 87782ac3..72936057 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserEditTextField.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserEditTextField.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.userinfo.component +package com.paw.key.presentation.ui.mypage.route.userinfo.component import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserGenderButton.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserGenderButton.kt similarity index 96% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserGenderButton.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserGenderButton.kt index cd7e75a8..11cf57d3 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserGenderButton.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserGenderButton.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.userinfo.component +package com.paw.key.presentation.ui.mypage.route.userinfo.component import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserProfileItem.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserProfileItem.kt similarity index 94% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserProfileItem.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserProfileItem.kt index 7064db51..4a45b290 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/component/UserProfileItem.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/component/UserProfileItem.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.userinfo.component +package com.paw.key.presentation.ui.mypage.route.userinfo.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/model/UserProfileContract.kt similarity index 73% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/model/UserProfileContract.kt index 310f9234..c5dc7941 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/model/UserProfileContract.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.userinfo.model +package com.paw.key.presentation.ui.mypage.route.userinfo.model import androidx.compose.runtime.Immutable @@ -6,12 +6,12 @@ import androidx.compose.runtime.Immutable data class UserProfileState( val name: String = "김도기", val gender: String = "여성", - val age: Int = 24, - val activeRegion: String = "강남구 역삼동" + val email: String = "", + val birth: String = "" ) sealed class UserProfileSideEffect{ data class ShowSnackBar(val message: String) : UserProfileSideEffect() data object NavigateUp : UserProfileSideEffect() data object NavigateNext : UserProfileSideEffect() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/navigation/UserProfileNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/navigation/UserProfileNavigation.kt similarity index 84% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/navigation/UserProfileNavigation.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/navigation/UserProfileNavigation.kt index 863e51dd..985d63d4 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/navigation/UserProfileNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/navigation/UserProfileNavigation.kt @@ -1,4 +1,4 @@ -package com.paw.key.presentation.ui.mypage.userinfo.navigation +package com.paw.key.presentation.ui.mypage.route.userinfo.navigation import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.SnackbarHostState @@ -7,7 +7,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.paw.key.core.navigation.Route -import com.paw.key.presentation.ui.mypage.userinfo.UserProfileRoute +import com.paw.key.presentation.ui.mypage.route.userinfo.UserProfileRoute import kotlinx.serialization.Serializable fun NavController.navigateUserProfile( diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt similarity index 53% rename from app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt rename to app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt index 01b5d7a3..ddbf76d2 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt @@ -1,12 +1,11 @@ -package com.paw.key.presentation.ui.mypage.userinfo.viewmodel +package com.paw.key.presentation.ui.mypage.route.userinfo.viewmodel import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.paw.key.domain.repository.localstorage.LocalStorageRepository -import com.paw.key.domain.repository.userprofile.UserProfileRepository -import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileSideEffect -import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileState +import com.paw.key.domain.repository.user.UserRepository +import com.paw.key.presentation.ui.mypage.route.userinfo.model.UserProfileSideEffect +import com.paw.key.presentation.ui.mypage.route.userinfo.model.UserProfileState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -18,8 +17,7 @@ import javax.inject.Inject @HiltViewModel class UserProfileViewModel @Inject constructor( - private val userProfileRepository: UserProfileRepository, - private val localRepository: LocalStorageRepository + private val userRepository: UserRepository, ) : ViewModel() { private val _state = MutableStateFlow(UserProfileState()) @@ -29,34 +27,23 @@ class UserProfileViewModel @Inject constructor( val sideEffect: MutableSharedFlow = _sideEffect init { - viewModelScope.launch { - val userId = localRepository.getUserId() - getUserProfiles(userId) - } + getUserProfiles() } - fun getUserProfiles(userId: Int) { + fun getUserProfiles() { viewModelScope.launch { - userProfileRepository.getUserProfiles(userId) + userRepository.getUserProfiles() .onSuccess { result -> - Log.d("UserProfileViewModel", "유저 프로필 불러오기 성공: $result") _sideEffect.emit(UserProfileSideEffect.ShowSnackBar("유저 프로필 불러오기 성공")) _state.update { state -> state.copy( name = result.name, gender = result.gender, - age = result.age, - activeRegion = result.activeRegion + birth = result.birth.orEmpty(), + email = result.email ) } - - try { - //PreferenceDataStore.saveActiveRegion(result.activeRegion) - Log.d("UserProfileViewModel", "activeRegion 저장 완료: ${result.activeRegion}") - } catch (e: Exception) { - Log.e("UserProfileViewModel", "activeRegion 저장 실패: ${e.message}") - } } .onFailure { e -> Log.e("UserProfileViewModel", "유저 프로필 불러오기 실패", e) @@ -64,4 +51,4 @@ class UserProfileViewModel @Inject constructor( } } } -} \ No newline at end of file +} 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 79325e30..ac2fca4e 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 @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,6 +23,7 @@ import androidx.compose.ui.unit.dp import com.paw.key.R import com.paw.key.core.designsystem.component.DokiButton import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.extension.noRippleClickable import com.paw.key.presentation.ui.onboard.component.OnboardPager import com.paw.key.presentation.ui.onboard.component.OnboardingPosting @@ -78,19 +80,20 @@ fun OnboardingScreen( Spacer(modifier = Modifier.height(40.dp)) Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_onboard_main_logo), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_logo), contentDescription = stringResource(id = R.string.ic_onboarding_top_icon), tint = Color.Unspecified ) - Spacer(modifier = Modifier.height(13.dp)) + Spacer(modifier = Modifier.height(40.dp)) OnboardPager( jobList = listOf( OnboardingPosting( title = stringResource(id = R.string.ic_onboadring_pager_title1), subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext1), - backImg = R.drawable.img_onboarding_1 + backImg = R.drawable.doki_welcome, + isLarge = true ), OnboardingPosting( title = stringResource(id = R.string.ic_onboadring_pager_title2), @@ -100,18 +103,31 @@ fun OnboardingScreen( OnboardingPosting( title = stringResource(id = R.string.ic_onboadring_pager_title3), subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext3), - backImg = R.drawable.img_onboarding_2 + backImg = R.drawable.img_onboarding_3 ), OnboardingPosting( title = stringResource(id = R.string.ic_onboadring_pager_title4), subtitle = stringResource(id = R.string.ic_onboarding_pager_subtext4), - backImg = R.drawable.img_onboarding_2 + backImg = R.drawable.img_onboarding_4 ), - ) + ), + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.height(26.dp)) + + Text ( + text = "건너뛰기", + style = PawKeyTheme.typography.subButtonActive, + color = PawKeyTheme.colors.defaultDark, + modifier = Modifier + .noRippleClickable(onClick = navigateSignUp) ) + Spacer(modifier = Modifier.height(8.dp)) + DokiButton( - text = stringResource(id = R.string.ic_onboarding_button), + text = "시작하기", enabled = true, onClick = navigateNext, modifier = Modifier diff --git a/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt b/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt index 1ba52997..e9352e5e 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/onboard/component/OnboardPager.kt @@ -5,11 +5,10 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape @@ -18,8 +17,9 @@ 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.platform.LocalConfiguration +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -36,7 +36,8 @@ private fun PreviewOnboardPager() { OnboardingPosting( title = "우리 강아지를 위한 산책", subtitle = "DOGKY와 즐거운 산책을 시작해봐요!", - backImg = R.drawable.img_onboarding_1 + backImg = R.drawable.doki_welcome, + isLarge = false ), ) ) @@ -51,33 +52,37 @@ fun OnboardPager( val pageCount = jobList.size val pagerState = rememberPagerState(pageCount = { pageCount }) - val currentPage = pagerState.currentPage + val currentPage = pagerState.settledPage val currentItem = jobList.getOrNull(currentPage) Box( modifier = modifier .fillMaxWidth() - .height(LocalConfiguration.current.screenHeightDp.dp * 0.7f) .background(color = PawKeyTheme.colors.white1) ) { HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize() ) { page -> - OnboardingListItem(backImg = jobList[page].backImg) + OnboardingListItem( + backImg = jobList[page].backImg, + isLarge = jobList[page].isLarge, + ) } Column( modifier = Modifier .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Crossfade(targetState = currentItem?.title) { title -> title?.let { Text( text = it, - style = PawKeyTheme.typography.head24B.copy(lineHeight = 36.sp), - color = PawKeyTheme.colors.black, + style = PawKeyTheme.typography.header2.copy(lineHeight = 36.sp), + color = PawKeyTheme.colors.contents, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center ) } } @@ -86,8 +91,10 @@ fun OnboardPager( subtitle?.takeIf { it.isNotEmpty() }?.let { Text( text = it, - style = PawKeyTheme.typography.body16M, - color = PawKeyTheme.colors.gray400, + style = PawKeyTheme.typography.subButtonDefault, + color = PawKeyTheme.colors.defaultDark, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center ) } } @@ -98,27 +105,37 @@ fun OnboardPager( selectedPage = currentPage, selectedColor = PawKeyTheme.colors.primary, defaultColor = PawKeyTheme.colors.gray100, + space = 4.dp, modifier = Modifier .align(Alignment.BottomCenter) - .padding(bottom = 20.dp) + .padding(top = 25.dp) ) } } @Composable -fun OnboardingListItem(backImg: Int) { +fun OnboardingListItem( + backImg: Int, + modifier: Modifier = Modifier, + isLarge: Boolean = false, +) { Box( - modifier = Modifier - .fillMaxSize() + modifier = modifier .clip(RoundedCornerShape(bottomStart = 24.dp, bottomEnd = 24.dp)) + .fillMaxSize() .background(PawKeyTheme.colors.white1) ) { Image( painter = painterResource(id = backImg), contentDescription = null, modifier = Modifier - .align(alignment = Alignment.Center) - .size(360.dp), + .align(alignment = Alignment.BottomCenter) + .padding(bottom = 20.dp) + .then( + if (isLarge) Modifier.fillMaxSize() + else Modifier.aspectRatio(375f / 332f) + ), + contentScale = if (isLarge) ContentScale.Crop else ContentScale.Fit ) } } @@ -127,4 +144,5 @@ data class OnboardingPosting( val title: String, val subtitle: String, val backImg: Int, + val isLarge: Boolean = false ) \ 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 ac8df4ac..6cb087b4 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 @@ -9,7 +9,7 @@ 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 +import com.paw.key.domain.repository.home.HomeRepository import com.paw.key.domain.repository.localstorage.LocalStorageRepository import com.paw.key.presentation.ui.region.navigation.Regional import com.paw.key.presentation.ui.region.state.DrawType @@ -30,7 +30,7 @@ import javax.inject.Inject class RegionViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val regionRepository: RegionRepository, - private val homeRepository: HomeRegionRepository, + private val homeRepository: HomeRepository, private val localStorageRepository: LocalStorageRepository ) : ViewModel() { private val _state = MutableStateFlow(RegionState()) @@ -48,7 +48,6 @@ class RegionViewModel @Inject constructor( if (regionIdState.regionId != -1) { viewModelScope.launch { getRegionGeometry( - userId = localStorageRepository.getUserId(), regionId = regionIdState.regionId, ) } @@ -56,15 +55,14 @@ class RegionViewModel @Inject constructor( viewModelScope.launch { Timber.e("RegionViewModel test용 regionId: ${regionIdState.regionId}") getRegionGeometry( - userId = localStorageRepository.getUserId(), - regionId = 39, + regionId = 2, ) } } } - fun getRegionGeometry(userId: Int, regionId: Int?) = viewModelScope.launch { - regionRepository.getRegionGeometry(userId, regionId!!) + fun getRegionGeometry(regionId: Int?) = viewModelScope.launch { + regionRepository.getRegionGeometry(regionId!!) .onSuccess { data -> val coordinates = data.geometry.coordinates val flattenedLatLng = flattenCoordinatesToLatLng(coordinates) @@ -85,7 +83,7 @@ class RegionViewModel @Inject constructor( uiState = UiState.Success(flattenedLatLng), entireCoordinates = allPoints, drawType = DrawType.SINGLE, - preRegionName = data.preRegionName, + preRegionName = data.regionName, regionName = data.regionName ) } @@ -96,7 +94,7 @@ class RegionViewModel @Inject constructor( uiState = UiState.Success(flattenedLatLng), entireCoordinates = allPoints, drawType = DrawType.MULTIPLE, - preRegionName = data.preRegionName, + preRegionName = data.regionName, regionName = data.regionName ) } 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 index d7ebbcea..92ad86df 100644 --- 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 @@ -40,6 +40,7 @@ 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.ImageTypeSelectDialog 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 @@ -63,9 +64,12 @@ fun SignUpPetInfoScreen( onPetNeuteredChanged : (Boolean) -> Unit, onPetBreedChanged : (PetInfoItemModel) -> Unit, onSelectedImage: (Uri?) -> Unit, + createCameraUri: () -> Unit, modifier: Modifier = Modifier ) { var isSheetOpen by remember { mutableStateOf(false) } + var isImageTypeDialogOpen by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() @@ -93,7 +97,18 @@ fun SignUpPetInfoScreen( if (isGranted) { legacyGalleryLauncher.launch("image/*") } else { - deniedPermission + deniedPermission() + } + } + ) + + val cameraPermissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission(), + onResult = { isGranted -> + if (isGranted) { + createCameraUri() + } else { + deniedPermission() } } ) @@ -116,13 +131,7 @@ fun SignUpPetInfoScreen( uri = petInfo.petImage, 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) - } + isImageTypeDialogOpen = true } ) @@ -258,4 +267,25 @@ fun SignUpPetInfoScreen( } } } -} \ No newline at end of file + + if (isImageTypeDialogOpen) { + ImageTypeSelectDialog( + onCameraClick = { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA) + }, + onGalleryClick = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + photoPickerLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } else { + permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) + } + }, + onDefaultClick = { + onSelectedImage(null) + }, + onDismissRequest = { isImageTypeDialogOpen = false } + ) + } +} 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 9b1e4d6e..0cc427a7 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 @@ -2,6 +2,8 @@ package com.paw.key.presentation.ui.signup import android.net.Uri import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,8 +16,12 @@ import androidx.compose.foundation.verticalScroll 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.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -47,6 +53,16 @@ fun SignUpRoute( ) { val state by viewModel.state.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current + var cameraImageUri by remember { mutableStateOf(null) } + + val cameraLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.TakePicture(), + onResult = { success -> + if (success) { + viewModel.updatePetImage(cameraImageUri) + } + } + ) BackHandler(enabled = true) { viewModel.onBackPressed() @@ -68,6 +84,12 @@ fun SignUpRoute( is SignUpSideEffect.NavigateHome -> { navigateToHome() } + + is SignUpSideEffect.LaunchCamera -> { + val uri = it.uriString.toUri() + cameraImageUri = uri + cameraLauncher.launch(uri) + } } } } @@ -100,6 +122,7 @@ fun SignUpRoute( deniedPermission = viewModel::deniedPermission, onSelectedImage = viewModel::updatePetImage, requestPetInfo = viewModel::getPetInfo, + createCameraUri = viewModel::createCameraUri, locationInfo = state.locationInfo, getRegions = viewModel::getRegions, @@ -133,6 +156,7 @@ fun SignUpScreen( onPetBreedChanged : (PetInfoItemModel) -> Unit, onSelectedImage: (Uri?) -> Unit, requestPetInfo : () -> Unit, + createCameraUri: () -> Unit, locationInfo : SignUpLocationInfo, getRegions: () -> Unit, @@ -218,6 +242,7 @@ fun SignUpScreen( nickName = userInfo.nickName, birthDate = userInfo.birthDate, gender = userInfo.gender, + isDuplicate = userInfo.isDuplicate, onNickNameChanged = onNickNameChanged, onBirthDateChanged = onBirthDateChanged, onGenderChanged = onGenderChanged, @@ -240,6 +265,7 @@ fun SignUpScreen( onSelectedImage(it) }, requestPetInfo = requestPetInfo, + createCameraUri = createCameraUri, modifier = Modifier ) } @@ -274,4 +300,4 @@ fun SignUpScreen( } } } -} \ 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 index ca726a70..97b25d5f 100644 --- 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 @@ -14,7 +14,9 @@ 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.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.paw.key.core.designsystem.theme.PawKeyTheme 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 @@ -26,6 +28,7 @@ fun SignUpUserInfoScreen( nickName: String, birthDate: String, gender: Gender, + isDuplicate: Boolean, onNickNameChanged: (String) -> Unit, onBirthDateChanged: (String) -> Unit, onGenderChanged: (Gender) -> Unit, @@ -40,6 +43,7 @@ fun SignUpUserInfoScreen( ) { FormField( label = "닉네임", + isDuplicate = isDuplicate, content = { SignUpTextField( value = nickName, @@ -102,4 +106,20 @@ fun SignUpUserInfoScreen( } ) } -} \ No newline at end of file +} + +@Preview +@Composable +private fun SignUpUserInfoScreenPreview() { + PawKeyTheme { + SignUpUserInfoScreen( + nickName = "", + birthDate = "", + gender = Gender.MALE, + isDuplicate = true, + onNickNameChanged = {}, + onBirthDateChanged = {}, + onGenderChanged = {} + ) + } +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/FormField.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/FormField.kt index 568fbd45..b487d593 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/signup/component/FormField.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/FormField.kt @@ -1,9 +1,11 @@ package com.paw.key.presentation.ui.signup.component +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -29,15 +31,32 @@ private fun PreviewFormField() { @Composable fun FormField( label: String, + modifier: Modifier = Modifier, + isDuplicate: Boolean = false, content: @Composable () -> Unit, ) { - Text( - text = label, - color = PawKeyTheme.colors.black, - style = PawKeyTheme.typography.body16Sb - ) + Row ( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + color = PawKeyTheme.colors.black, + style = PawKeyTheme.typography.body16Sb + ) + + Spacer(modifier = Modifier.weight(1f)) + + if (isDuplicate) { + Text( + text = "*이미 존재하는 닉네임입니다", + style = PawKeyTheme.typography.buttonSmall, + color = PawKeyTheme.colors.dokiRed + ) + } + } Spacer(modifier = Modifier.height(10.dp)) content() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/paw/key/presentation/ui/signup/component/ImageTypeSelectDialog.kt b/app/src/main/java/com/paw/key/presentation/ui/signup/component/ImageTypeSelectDialog.kt new file mode 100644 index 00000000..b88536d0 --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/signup/component/ImageTypeSelectDialog.kt @@ -0,0 +1,252 @@ +package com.paw.key.presentation.ui.signup.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.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.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.paw.key.core.designsystem.component.DokiButton +import com.paw.key.core.designsystem.theme.PawKeyTheme + +@Composable +fun ImageTypeSelectDialog( + onCameraClick: () -> Unit, + onGalleryClick: () -> Unit, + onDefaultClick: () -> Unit, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + properties: DialogProperties = DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false + ), +) { + val drawLineColor = PawKeyTheme.colors.defaultButton + + Dialog( + onDismissRequest = onDismissRequest, + properties = properties, + ) { + Box( + modifier = modifier + .fillMaxSize() + .background(color = PawKeyTheme.colors.black.copy(alpha = 0.75f)) + .padding(horizontal = 16.dp), + contentAlignment = Alignment.Center + ) { + Column ( + modifier = Modifier + .fillMaxSize() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = PawKeyTheme.colors.background, + shape = RoundedCornerShape(16.dp) + ) + ) { + // 카메라 + TextButton( + onClick = { + onCameraClick() + onDismissRequest() + }, + modifier = Modifier + .fillMaxWidth() + .drawBehind { + val strokeWidth = 1.dp.toPx() + drawLine( + color = drawLineColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = strokeWidth + ) + } + ) { + Text( + text = "카메라", + style = PawKeyTheme.typography.mainButtonDefault, + color = PawKeyTheme.colors.primary, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + + // 갤러리 + TextButton( + onClick = { + onGalleryClick() + onDismissRequest() + }, + modifier = Modifier + .fillMaxWidth() + .drawBehind { + val strokeWidth = 1.dp.toPx() + drawLine( + color = drawLineColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = strokeWidth + ) + } + ) { + Text( + text = "갤러리", + style = PawKeyTheme.typography.mainButtonDefault, + color = PawKeyTheme.colors.primary, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + + // 기본 이미지 + TextButton( + onClick = { + onDefaultClick() + onDismissRequest() + }, + modifier = Modifier + .fillMaxWidth() + ) { + Text( + text = "기본 이미지", + style = PawKeyTheme.typography.mainButtonDefault, + color = PawKeyTheme.colors.primary, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + } + + Spacer(modifier = Modifier.height(10.dp)) + + DokiButton( + text = "취소하기", + enabled = true, + onClick = onDismissRequest, + modifier = Modifier + .fillMaxWidth(), + ) + } + } + } +} + +@Preview +@Composable +private fun ImageTypeSelectPreview() { + PawKeyTheme { + val drawLineColor = PawKeyTheme.colors.defaultButton + + Box( + modifier = Modifier + .fillMaxSize() + .background(color = PawKeyTheme.colors.black.copy(alpha = 0.75f)) + .padding(horizontal = 16.dp), + contentAlignment = Alignment.Center + ) { + Column ( + modifier = Modifier + .fillMaxSize() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = PawKeyTheme.colors.background, + shape = RoundedCornerShape(16.dp) + ) + ) { + // 카메라 + TextButton( + onClick = { + + }, + modifier = Modifier + .fillMaxWidth() + .drawBehind { + val strokeWidth = 1.dp.toPx() + drawLine( + color = drawLineColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = strokeWidth + ) + } + ) { + Text( + text = "카메라", + style = PawKeyTheme.typography.mainButtonDefault, + color = PawKeyTheme.colors.primary, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + + // 갤러리 + TextButton( + onClick = { + + }, + modifier = Modifier + .fillMaxWidth() + .drawBehind { + val strokeWidth = 1.dp.toPx() + drawLine( + color = drawLineColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = strokeWidth + ) + } + ) { + Text( + text = "갤러리", + style = PawKeyTheme.typography.mainButtonDefault, + color = PawKeyTheme.colors.primary, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + + // 기본 이미지 + TextButton( + onClick = { + + }, + modifier = Modifier + .fillMaxWidth() + ) { + Text( + text = "기본 이미지", + style = PawKeyTheme.typography.mainButtonDefault, + color = PawKeyTheme.colors.primary, + modifier = Modifier.padding(vertical = 6.dp) + ) + } + } + + Spacer(modifier = Modifier.height(10.dp)) + + DokiButton( + text = "취소", + enabled = true, + onClick = {}, + modifier = Modifier + .fillMaxWidth(), + ) + } + } + } + +} 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 index 2ad25ab2..8a4e641c 100644 --- 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 @@ -10,7 +10,6 @@ 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 @@ -21,12 +20,9 @@ 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.disableNestedScroll import com.paw.key.core.extension.noRippleClickable @@ -35,7 +31,6 @@ import com.paw.key.presentation.ui.signup.model.DongModel import com.paw.key.presentation.ui.signup.model.GuModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @Composable fun RegionSearchContent( @@ -45,27 +40,6 @@ fun RegionSearchContent( onRegionSelected: (GuModel, DongModel) -> Unit, modifier: Modifier = Modifier, ) { - var searchText by remember { mutableStateOf("") } - - var currentGu by remember(selectedGu) { - mutableStateOf(if (selectedGu.id != 0) selectedGu else regionList.firstOrNull()?.gu ?: GuModel(0, "")) - } - - val filteredRegionList = remember(searchText, regionList) { - if (searchText.isBlank()) { - regionList - } else { - regionList.filter { - it.gu.name.contains(searchText, ignoreCase = true) - }.toImmutableList() - } - } - - val currentDongList = remember(currentGu, filteredRegionList) { - // 전체 리스트에서 찾아야 동 정보가 유실되지 않음 - regionList.find { it.gu.id == currentGu.id }?.dongs ?: persistentListOf() - } - Column( modifier = modifier .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)) @@ -90,7 +64,7 @@ fun RegionSearchContent( textAlign = TextAlign.Center ) - SignUpTextField( + /*SignUpTextField( value = searchText, onValueChange = { searchText = it @@ -103,10 +77,10 @@ fun RegionSearchContent( tint = PawKeyTheme.colors.contents ) } - ) + )*/ RegionSearchList( - regionList = filteredRegionList, + regionList = regionList, selectedGu = selectedGu, selectedDong = selectedDong, onRegionSelected = onRegionSelected, 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 88ee71f3..691b7a8f 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 @@ -35,6 +35,7 @@ fun SignUpTextField( onValueChange: (String) -> Unit, placeholder: String, modifier: Modifier = Modifier, + isDuplicate: Boolean = false, enabled: Boolean = true, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, @@ -47,6 +48,7 @@ fun SignUpTextField( val borderColor = when { !enabled -> PawKeyTheme.colors.defaultMiddle isFocused.value -> PawKeyTheme.colors.primary + isDuplicate -> PawKeyTheme.colors.dokiRed else -> PawKeyTheme.colors.defaultMiddle } @@ -118,4 +120,4 @@ private fun SignUpTextFieldPreview() { placeholder = "이름을 입력해주세요." ) } -} \ 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 index f103f0b5..89ac03e1 100644 --- 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 @@ -8,4 +8,5 @@ data class SignUpUserInfo( val nickName : String = "", val birthDate : String = "", val gender : Gender = Gender.UNKNOWN, -) \ No newline at end of file + val isDuplicate : Boolean = false +) 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 c08de92b..4eb88a05 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 @@ -23,11 +23,12 @@ data class SignUpState( val isLoading: Boolean = false, ) -sealed class SignUpSideEffect { - data class ShowSnackBar(val message: String) : SignUpSideEffect() - data object NavigateUp : SignUpSideEffect() - data object NavigateNext : SignUpSideEffect() - data object NavigateHome : SignUpSideEffect() +sealed interface SignUpSideEffect { + data class ShowSnackBar(val message: String) : SignUpSideEffect + data object NavigateUp : SignUpSideEffect + data object NavigateNext : SignUpSideEffect + data object NavigateHome : SignUpSideEffect + data class LaunchCamera(val uriString: String) : SignUpSideEffect } enum class SignUpStateType { 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 98b552e5..770fbc22 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 @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.paw.key.core.extension.toBirthDateFormat import com.paw.key.core.util.UiState +import com.paw.key.core.util.file.ImageUriManager import com.paw.key.core.util.flattenCoordinatesToLatLng import com.paw.key.core.util.handleError import com.paw.key.domain.entity.user.PetInfoEntity @@ -12,7 +13,7 @@ import com.paw.key.domain.entity.user.UserInfoEntity import com.paw.key.domain.repository.RegionRepository import com.paw.key.domain.repository.localstorage.LocalStorageRepository import com.paw.key.domain.repository.user.UserRepository -import com.paw.key.domain.usecase.PostCreateUserUseCase +import com.paw.key.domain.usecase.user.PostCreateUserUseCase import com.paw.key.presentation.ui.region.state.DrawType import com.paw.key.presentation.ui.signup.model.DongModel import com.paw.key.presentation.ui.signup.model.GuModel @@ -25,10 +26,16 @@ import com.paw.key.presentation.ui.signup.state.SignUpStateType import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber @@ -36,12 +43,14 @@ import java.time.LocalDate import java.time.format.DateTimeFormatter import javax.inject.Inject +@OptIn(FlowPreview::class) @HiltViewModel class SignUpViewModel @Inject constructor( private val regionRepository: RegionRepository, private val userRepository: UserRepository, - private val localRepository: LocalStorageRepository, + private val localStorageRepository: LocalStorageRepository, private val postCreateUserUseCase: PostCreateUserUseCase, + private val imageUriManager: ImageUriManager ) : ViewModel() { private val _state = MutableStateFlow(SignUpState()) val state: StateFlow = _state.asStateFlow() @@ -49,6 +58,19 @@ class SignUpViewModel @Inject constructor( private val _sideEffect = MutableSharedFlow() val sideEffect: MutableSharedFlow = _sideEffect + init { + viewModelScope.launch { + state + .map { it.userInfo.nickName } + .debounce(500L) + .filter { it.isNotBlank() } + .distinctUntilChanged() + .collectLatest { nickname -> + checkNicknameDuplicate(nickname) + } + } + } + fun deniedPermission() { viewModelScope.launch { _sideEffect.emit(SignUpSideEffect.ShowSnackBar("갤러리 접근 권한을 허용해주세요")) @@ -128,14 +150,12 @@ class SignUpViewModel @Inject constructor( if (_state.value.isRegionComplete && _state.value.locationInfo.selectedGu.name.isNotBlank() && _state.value.locationInfo.selectedDong.name.isNotBlank()) { postCreateUser() } else { - // Todo: 좌표값이 없어서 우선 여기서 종료 - /*updateState { + updateState { it.copy( signUpState = SignUpStateType.REGION_MANAGEMENT, ) } - _sideEffect.emit(SignUpSideEffect.NavigateNext)*/ - postCreateUser() + _sideEffect.emit(SignUpSideEffect.NavigateNext) } } @@ -195,64 +215,11 @@ class SignUpViewModel @Inject constructor( ), petImageUri = _state.value.petInfo.petImage?.toString() ).onSuccess { + localStorageRepository.savePetName(_state.value.petInfo.petName) _state.update { it.copy(isLoading = false) } _sideEffect.emit(SignUpSideEffect.NavigateHome) }.onFailure(Timber::e) - - /*suspendRunCatching { - val currentState = _state.value - val petImageUri = currentState.petInfo.petImage - - val finalImageId: Int = if (petImageUri != null) { - val presignedResult = imageRepository.presignedImage( - presignedEntity = ImagePresignedEntity( - domain = ImageDomainType.PET_PROFILE, - contentType = "image/webp" - ) - ).getOrThrow() - - val registerImage = imageRepository.registerImage( - uriString = "${presignedResult.imageUrl}#${_state.value.petInfo.petImage}", - domainType = ImageDomainType.PET_PROFILE, - ).onFailure(Timber::e) - - if (!registerImage.isSuccess) { - throw Exception("이미지 업로드에 실패했습니다.") - } - - registerImage.getOrThrow().imageId - } else { - -1 - } - - userRepository.createUser( - userInfoEntity = UserInfoEntity( - name = _state.value.userInfo.nickName, - birth = _state.value.userInfo.birthDate.toBirthDateFormat(), - gender = _state.value.userInfo.gender.value, - dongId = _state.value.locationInfo.selectedDong.id, - pet = PetInfoEntity( - name = _state.value.petInfo.petName, - birth = _state.value.petInfo.petBirthDate.toBirthDateFormat(), - gender = _state.value.petInfo.petGender.value, - isNeutered = _state.value.petInfo.petNeutered, - breedId = _state.value.petInfo.petBreed.id, - imageId = finalImageId - ) - ) - ).onSuccess { - UserDataStore.saveUserId(context, it.userId) - UserDataStore.savePetId(context, it.petId) - } - }.onSuccess { - Timber.e("postCreateUser success") - _sideEffect.emit(SignUpSideEffect.NavigateHome) - }.onFailure { - Timber.e(it) - _sideEffect.emit(SignUpSideEffect.ShowSnackBar(it.message ?: "알 수 없는 오류가 발생했습니다.")) - } - }*/ } } @@ -267,6 +234,22 @@ class SignUpViewModel @Inject constructor( } } + private suspend fun checkNicknameDuplicate(nickname: String) { + updateState { it.copy(userInfo = it.userInfo.copy(isDuplicate = false)) } + + userRepository.checkNickname(nickname) + .onSuccess { isDuplicate -> + updateState { + it.copy( + userInfo = it.userInfo.copy(isDuplicate = isDuplicate) + ) + } + } + .onFailure { + updateState { it.copy(userInfo = it.userInfo.copy(isDuplicate = false)) } + } + } + fun updateBirthDate(birthDate: String) { val digitsOnly = birthDate.filter { it.isDigit() } @@ -413,16 +396,16 @@ class SignUpViewModel @Inject constructor( viewModelScope.launch { val dongId = _state.value.locationInfo.selectedDong.id getRegionGeometry( - userId = localRepository.getUserId(), regionId = dongId, ) onNextClick() } } - fun getRegionGeometry(userId: Int, regionId: Int?) = viewModelScope.launch { - regionRepository.getRegionGeometry(userId, regionId!!) + fun getRegionGeometry(regionId: Int?) = viewModelScope.launch { + regionRepository.getRegionGeometry(regionId!!) .onSuccess { data -> + Timber.e("getRegionGeometry $data") val coordinates = data.geometry.coordinates val flattenedLatLng = flattenCoordinatesToLatLng(coordinates) @@ -466,6 +449,7 @@ class SignUpViewModel @Inject constructor( } } .onFailure { throwable -> + Timber.e("getRegionGeometry $throwable") val errorMessage = handleError(throwable) _state.update { currentState -> currentState.copy( @@ -483,7 +467,7 @@ class SignUpViewModel @Inject constructor( // UserInfo 확인 state.userInfo.nickName.isNotBlank() && state.userInfo.nickName.length <= 8 && state.userInfo.birthDate.length == 8 && state.userInfo.birthDate.isValidDate() && - state.userInfo.gender != Gender.UNKNOWN + state.userInfo.gender != Gender.UNKNOWN && !state.userInfo.isDuplicate } SignUpStateType.PET_INFO -> { @@ -503,6 +487,17 @@ class SignUpViewModel @Inject constructor( } } } + + fun createCameraUri() { + viewModelScope.launch { + val uriString = imageUriManager.createTempImageUri() + if (uriString != null) { + _sideEffect.emit(SignUpSideEffect.LaunchCamera(uriString)) + } else { + _sideEffect.emit(SignUpSideEffect.ShowSnackBar("카메라를 실행할 수 없습니다")) + } + } + } } private fun String.isValidDate(): Boolean { @@ -514,4 +509,4 @@ private fun String.isValidDate(): Boolean { } 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 9f0d9101..0891611c 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 @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon 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 @@ -25,7 +24,8 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.paw.key.R import com.paw.key.core.designsystem.theme.PawKeyTheme -import com.paw.key.presentation.ui.splash.state.SplashContract +import com.paw.key.core.extension.collectSingleEvent +import com.paw.key.presentation.ui.splash.state.SplashSideEffect import com.paw.key.presentation.ui.splash.viewmodel.SplashViewModel @Preview(showBackground = true) @@ -43,10 +43,10 @@ private fun PreviewSplashScreen() { fun SplashRoute( paddingValues: PaddingValues, navigateLogin: () -> Unit, + navigateToHome: () -> Unit, modifier: Modifier = Modifier, viewModel: SplashViewModel = hiltViewModel(), ) { - val effectFlow = viewModel.sideeffect val statusBarColor = PawKeyTheme.colors.green500 val context = LocalContext.current @@ -64,11 +64,11 @@ fun SplashRoute( } } - LaunchedEffect(Unit) { - effectFlow.collect { effect -> - when (effect) { - is SplashContract.SplashSideEffect.NavigateToLogin -> navigateLogin() - } + viewModel.sideEffect.collectSingleEvent { + when (it) { + SplashSideEffect.NavigateToLogin -> navigateLogin() + + SplashSideEffect.NavigateToHome -> navigateToHome() } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/splash/navigation/SplashNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/splash/navigation/SplashNavigation.kt index 9c0d2b48..3d83e3ca 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/splash/navigation/SplashNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/splash/navigation/SplashNavigation.kt @@ -1,7 +1,6 @@ package com.paw.key.presentation.ui.splash.navigation import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.SnackbarHostState import androidx.compose.ui.Modifier import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder @@ -20,12 +19,14 @@ fun NavController.navigateSplash( fun NavGraphBuilder.splashNavGraph( paddingValues: PaddingValues, navigateLogin: () -> Unit, + navigateToHome: () -> Unit, modifier: Modifier = Modifier, ) { composable { SplashRoute( paddingValues = paddingValues, navigateLogin = navigateLogin, + navigateToHome = navigateToHome, modifier = modifier ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/splash/state/SplashContract.kt b/app/src/main/java/com/paw/key/presentation/ui/splash/state/SplashContract.kt index b0a6d65d..4184b974 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/splash/state/SplashContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/splash/state/SplashContract.kt @@ -2,14 +2,13 @@ package com.paw.key.presentation.ui.splash.state import androidx.compose.runtime.Immutable -class SplashContract { +@Immutable +data class SplashState( + val isLoading: Boolean = true, +) - @Immutable - data class SplashState( - val isLoading: Boolean = true, - ) +sealed interface SplashSideEffect { + data object NavigateToLogin : SplashSideEffect - sealed class SplashSideEffect { - data object NavigateToLogin : SplashSideEffect() - } -} + data object NavigateToHome: SplashSideEffect +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/splash/viewmodel/SplashViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/splash/viewmodel/SplashViewModel.kt index 8216097c..522a5b37 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/splash/viewmodel/SplashViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/splash/viewmodel/SplashViewModel.kt @@ -2,34 +2,60 @@ package com.paw.key.presentation.ui.splash.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.paw.key.presentation.ui.splash.state.SplashContract +import com.paw.key.data.network.TokenRefreshService +import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.presentation.ui.splash.state.SplashSideEffect +import com.paw.key.presentation.ui.splash.state.SplashState import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel -class SplashViewModel @Inject constructor() : ViewModel() { +class SplashViewModel @Inject constructor( + private val localStorageRepository: LocalStorageRepository, + private val reIssueManager: TokenRefreshService, +) : ViewModel() { - private val _state = MutableStateFlow(SplashContract.SplashState()) - val state: StateFlow + private val _state = MutableStateFlow(SplashState()) + val state: StateFlow get() = _state.asStateFlow() - private val _sideeffect = MutableSharedFlow() - val sideeffect: SharedFlow - get() = _sideeffect.asSharedFlow() + private val _sideEffect = Channel() + val sideEffect = _sideEffect.receiveAsFlow() init { + checkToken() + } + + private fun checkToken() { + Timber.e("checkToken") viewModelScope.launch { - delay(1800) - _sideeffect.emit(SplashContract.SplashSideEffect.NavigateToLogin) + val refreshToken = localStorageRepository.getRefreshToken() + + if (refreshToken.isEmpty()) { + _sideEffect.send(SplashSideEffect.NavigateToLogin) + return@launch + } + + val deviceId = localStorageRepository.getDeviceId() + reIssueManager.refresh(refreshToken, deviceId) + .onSuccess { (accessToken, newRefreshToken) -> + Timber.e("checkTokenSuc $accessToken") + localStorageRepository.saveTokens(accessToken.value, newRefreshToken.value) + _sideEffect.send(SplashSideEffect.NavigateToHome) + } + .onFailure { + Timber.e("checkTokenFail $it") + localStorageRepository.clearInfo() + _sideEffect.send(SplashSideEffect.NavigateToLogin) + } } } } diff --git a/app/src/main/res/drawable/ic_home_drop.xml b/app/src/main/res/drawable/ic_home_drop.xml new file mode 100644 index 00000000..efe97772 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_drop.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_home_temperature.xml b/app/src/main/res/drawable/ic_home_temperature.xml new file mode 100644 index 00000000..68ddce80 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_temperature.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_login_title_logo.xml b/app/src/main/res/drawable/ic_login_title_logo.xml index 854bbd42..c83a3c0d 100644 --- a/app/src/main/res/drawable/ic_login_title_logo.xml +++ b/app/src/main/res/drawable/ic_login_title_logo.xml @@ -1,21 +1,18 @@ + android:width="69dp" + android:height="23dp" + android:viewportWidth="69" + android:viewportHeight="23"> + android:fillColor="#FF00D281" + android:pathData="M63.1 2.76c0-0.48 0-0.84 0.02-1.07 0.04-0.25 0.13-0.53 0.28-0.84 0.27-0.57 1-0.85 2.2-0.85 1.32 0 2.1 0.39 2.35 1.16 0.1 0.36 0.14 0.9 0.14 1.63v17.45c0 0.5-0.02 0.87-0.05 1.1-0.02 0.23-0.1 0.5-0.26 0.81-0.26 0.57-1 0.85-2.2 0.85-1.32 0-2.1-0.4-2.32-1.2-0.11-0.33-0.17-0.86-0.17-1.6V2.77Z"/> + android:fillColor="#FF00D281" + android:pathData="M53.2 11.31c3.06 2.94 5.23 5.15 6.51 6.63 0.66 0.74 1 1.37 1 1.89 0 0.5-0.4 1.1-1.2 1.8-0.8 0.68-1.47 1.02-1.99 1.02-0.5 0-1.1-0.42-1.83-1.26l-6.6-7.53v5.85c0 0.48-0.02 0.83-0.06 1.05-0.02 0.22-0.1 0.48-0.27 0.78-0.28 0.54-1.06 0.81-2.34 0.81-1.4 0-2.22-0.38-2.46-1.14-0.12-0.32-0.18-0.83-0.18-1.53V3c0-0.46 0.01-0.8 0.03-1.02 0.04-0.24 0.14-0.51 0.3-0.81 0.28-0.54 1.06-0.81 2.34-0.81 1.4 0 2.23 0.37 2.5 1.11 0.1 0.34 0.14 0.86 0.14 1.56v5.73c2.72-3.06 4.92-5.57 6.6-7.53C56.4 0.41 57 0 57.52 0s1.18 0.35 1.98 1.05c0.8 0.68 1.2 1.28 1.2 1.8 0 0.5-0.3 1.1-0.9 1.8-1.16 1.34-2.96 3.18-5.4 5.52l-1.2 1.14Z"/> + android:fillColor="#FF00D281" + android:pathData="M31.14 0.09c2.96 0 5.53 1.07 7.71 3.21 2.18 2.14 3.27 4.8 3.27 7.98 0 3.16-1.04 5.86-3.12 8.1-2.08 2.22-4.64 3.33-7.68 3.33s-5.63-1.1-7.77-3.3c-2.12-2.2-3.18-4.82-3.18-7.86 0-1.66 0.3-3.21 0.9-4.65 0.6-1.46 1.4-2.68 2.4-3.66s2.15-1.75 3.45-2.31c1.3-0.56 2.64-0.84 4.02-0.84ZM25.68 11.4c0 1.78 0.57 3.23 1.71 4.35 1.16 1.1 2.45 1.65 3.87 1.65s2.7-0.54 3.84-1.62c1.14-1.08 1.71-2.53 1.71-4.35 0-1.82-0.58-3.28-1.74-4.38-1.14-1.1-2.42-1.65-3.84-1.65s-2.7 0.56-3.84 1.68c-1.14 1.1-1.7 2.54-1.7 4.32Z"/> - - + android:fillColor="#FF00D281" + android:pathData="M2.67 0.33l5.55 0.03c2.9 0 5.45 1.05 7.65 3.15 2.2 2.08 3.3 4.65 3.3 7.71 0 3.04-1.08 5.65-3.24 7.83-2.14 2.18-4.74 3.27-7.8 3.27H2.64c-1.22 0-1.98-0.25-2.28-0.75C0.12 21.15 0 20.51 0 19.65V2.97c0-0.48 0.01-0.83 0.03-1.05 0.04-0.22 0.14-0.48 0.3-0.78 0.28-0.54 1.06-0.81 2.34-0.81Zm5.55 16.68c1.44 0 2.74-0.53 3.9-1.59 1.16-1.08 1.74-2.43 1.74-4.05 0-1.62-0.57-2.97-1.71-4.05-1.12-1.1-2.44-1.65-3.96-1.65H5.31v11.34h2.91Z"/> + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_logo.xml b/app/src/main/res/drawable/ic_logo.xml index 5f9f863d..198c896f 100644 --- a/app/src/main/res/drawable/ic_logo.xml +++ b/app/src/main/res/drawable/ic_logo.xml @@ -1,22 +1,18 @@ - + android:width="69dp" + android:height="23dp" + android:viewportWidth="69" + android:viewportHeight="23"> + android:pathData="M63.1 2.76c0-0.48 0-0.84 0.02-1.07 0.04-0.25 0.13-0.53 0.28-0.84 0.27-0.57 1-0.85 2.2-0.85 1.32 0 2.1 0.39 2.35 1.16 0.1 0.36 0.14 0.9 0.14 1.63v17.45c0 0.5-0.02 0.87-0.05 1.1-0.02 0.23-0.1 0.5-0.26 0.81-0.26 0.57-1 0.85-2.2 0.85-1.32 0-2.1-0.4-2.32-1.2-0.11-0.33-0.17-0.86-0.17-1.6V2.77Z"/> + android:pathData="M53.2 11.31c3.06 2.94 5.23 5.15 6.51 6.63 0.66 0.74 1 1.37 1 1.89 0 0.5-0.4 1.1-1.2 1.8-0.8 0.68-1.47 1.02-1.99 1.02-0.5 0-1.1-0.42-1.83-1.26l-6.6-7.53v5.85c0 0.48-0.02 0.83-0.06 1.05-0.02 0.22-0.1 0.48-0.27 0.78-0.28 0.54-1.06 0.81-2.34 0.81-1.4 0-2.22-0.38-2.46-1.14-0.12-0.32-0.18-0.83-0.18-1.53V3c0-0.46 0.01-0.8 0.03-1.02 0.04-0.24 0.14-0.51 0.3-0.81 0.28-0.54 1.06-0.81 2.34-0.81 1.4 0 2.23 0.37 2.5 1.11 0.1 0.34 0.14 0.86 0.14 1.56v5.73c2.72-3.06 4.92-5.57 6.6-7.53C56.4 0.41 57 0 57.52 0s1.18 0.35 1.98 1.05c0.8 0.68 1.2 1.28 1.2 1.8 0 0.5-0.3 1.1-0.9 1.8-1.16 1.34-2.96 3.18-5.4 5.52l-1.2 1.14Z"/> + android:pathData="M31.14 0.09c2.96 0 5.53 1.07 7.71 3.21 2.18 2.14 3.27 4.8 3.27 7.98 0 3.16-1.04 5.86-3.12 8.1-2.08 2.22-4.64 3.33-7.68 3.33s-5.63-1.1-7.77-3.3c-2.12-2.2-3.18-4.82-3.18-7.86 0-1.66 0.3-3.21 0.9-4.65 0.6-1.46 1.4-2.68 2.4-3.66s2.15-1.75 3.45-2.31c1.3-0.56 2.64-0.84 4.02-0.84ZM25.68 11.4c0 1.78 0.57 3.23 1.71 4.35 1.16 1.1 2.45 1.65 3.87 1.65s2.7-0.54 3.84-1.62c1.14-1.08 1.71-2.53 1.71-4.35 0-1.82-0.58-3.28-1.74-4.38-1.14-1.1-2.42-1.65-3.84-1.65s-2.7 0.56-3.84 1.68c-1.14 1.1-1.7 2.54-1.7 4.32Z"/> - - + android:pathData="M2.67 0.33l5.55 0.03c2.9 0 5.45 1.05 7.65 3.15 2.2 2.08 3.3 4.65 3.3 7.71 0 3.04-1.08 5.65-3.24 7.83-2.14 2.18-4.74 3.27-7.8 3.27H2.64c-1.22 0-1.98-0.25-2.28-0.75C0.12 21.15 0 20.51 0 19.65V2.97c0-0.48 0.01-0.83 0.03-1.05 0.04-0.22 0.14-0.48 0.3-0.78 0.28-0.54 1.06-0.81 2.34-0.81Zm5.55 16.68c1.44 0 2.74-0.53 3.9-1.59 1.16-1.08 1.74-2.43 1.74-4.05 0-1.62-0.57-2.97-1.71-4.05-1.12-1.1-2.44-1.65-3.96-1.65H5.31v11.34h2.91Z"/> + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_onboard_main_logo.xml b/app/src/main/res/drawable/ic_onboard_main_logo.xml deleted file mode 100644 index e8b28ee1..00000000 --- a/app/src/main/res/drawable/ic_onboard_main_logo.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/img_login_main.png b/app/src/main/res/drawable/img_login_main.png index 50f70953..6749ce79 100644 Binary files a/app/src/main/res/drawable/img_login_main.png and b/app/src/main/res/drawable/img_login_main.png differ diff --git a/app/src/main/res/drawable/img_login_sub.png b/app/src/main/res/drawable/img_login_sub.png index e6f914d1..d271864e 100644 Binary files a/app/src/main/res/drawable/img_login_sub.png and b/app/src/main/res/drawable/img_login_sub.png differ diff --git a/app/src/main/res/drawable/img_onboarding_2.png b/app/src/main/res/drawable/img_onboarding_2.png index f787bbba..c3e40629 100644 Binary files a/app/src/main/res/drawable/img_onboarding_2.png and b/app/src/main/res/drawable/img_onboarding_2.png differ diff --git a/app/src/main/res/drawable/img_onboarding_3.png b/app/src/main/res/drawable/img_onboarding_3.png new file mode 100644 index 00000000..8ea0b070 Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_3.png differ diff --git a/app/src/main/res/drawable/img_onboarding_4.png b/app/src/main/res/drawable/img_onboarding_4.png new file mode 100644 index 00000000..200b0912 Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_4.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 45a126b7..feef024c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,7 +29,7 @@ 기록한 산책길을 이웃과 나누세요 공유된 산책길을 따라 걸어보세요 - DOGKY와 즐거운 산책을 시작해봐요! + DOKI와 즐거운 산책을 시작해봐요! 산책의 거리, 분위기, 활동 등을 카테고리에 따라\n특별한 일상으로 저장할 수 있어요 내가 걸었던 루트를 공유하고\n다른 보호자들과 함께 즐길 수 있어요 이웃들이 남긴 루프를 걸으며\n해당 루트에 대한 유용한 정보를 얻을 수 있어요 diff --git a/keystore/debug.keystore b/keystore/debug.keystore deleted file mode 100644 index 1f99f3ca..00000000 Binary files a/keystore/debug.keystore and /dev/null differ