diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dc0efb69..765315af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,9 @@ + + + Unit, modifier: Modifier = Modifier ) { Column( @@ -53,6 +56,9 @@ fun CourseCard( .fillMaxWidth() .size(width = 328.dp , height = 240.dp) .background(Color.White, shape = RoundedCornerShape(20.dp)) + .noRippleClickable { + onCLickItem() + } ) { // 지도 썸네일 Box( @@ -168,6 +174,7 @@ fun CourseCardPreview() { title = "홍대 주변 좋은 산책 코스", petName = "반려견 이름", date = "2025/05/17", + onCLickItem = {} ) } } \ No newline at end of file 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 index 4f605264..49d329e9 100644 --- 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 @@ -53,28 +53,6 @@ fun CourseDetail( modifier = modifier .fillMaxWidth() ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_left_black), - contentDescription = "뒤로가기" - ) - Box( - modifier = Modifier.weight(1f), - contentAlignment = Alignment.Center - ) { - Text( - text = "저장한 산책 루트", - style = PawKeyTheme.typography.body16Sb - ) - } - Spacer(modifier = Modifier.width(24.dp)) - } - AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data("https://pawkey-server.com/image.jpg") 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 4cec4372..abbd3e91 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,8 +1,10 @@ package com.paw.key.data.di import com.paw.key.data.repositoryimpl.DummyRepositoryImpl +import com.paw.key.data.repositoryimpl.RegionRepositoryImpl import com.paw.key.data.repositoryimpl.WalkSharedResultRepositoryImpl import com.paw.key.domain.repository.DummyRepository +import com.paw.key.domain.repository.RegionRepository import com.paw.key.domain.repository.WalkSharedResultRepository import dagger.Binds import dagger.Module @@ -25,4 +27,10 @@ interface RepositoryModule { walkSharedResultRepositoryImpl: WalkSharedResultRepositoryImpl ): WalkSharedResultRepository + /*Home*/ + @Binds + @Singleton + fun bindsRegionRepository( + regionRepositoryImpl: RegionRepositoryImpl + ): RegionRepository } \ No newline at end of file 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 e97134b3..7fd0552c 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 @@ -1,6 +1,7 @@ package com.paw.key.data.di import com.paw.key.data.service.DummyService +import com.paw.key.data.service.RegionService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,4 +18,10 @@ object ServiceModule { fun providesDummyService(retrofit: Retrofit ): DummyService = retrofit.create(DummyService::class.java) + @Provides + @Singleton + fun providesRegionService(retrofit: Retrofit ): RegionService = + retrofit.create(RegionService::class.java) + + } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/BaseResponse.kt b/app/src/main/java/com/paw/key/data/dto/response/BaseResponse.kt index b5fdc03b..54f43a44 100644 --- a/app/src/main/java/com/paw/key/data/dto/response/BaseResponse.kt +++ b/app/src/main/java/com/paw/key/data/dto/response/BaseResponse.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class BaseResponse( @SerialName("code") - val code: Int, + val code: String, @SerialName("message") val message: String, @SerialName("data") 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 new file mode 100644 index 00000000..82777fa8 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/region/RegionResponseDto.kt @@ -0,0 +1,20 @@ +package com.paw.key.data.dto.response.region + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RegionResponseDto( + @SerialName("regionName") + val regionName: String, + @SerialName("geometryDto") + val geometryDto: GeometryDto +) + +@Serializable +data class GeometryDto( + @SerialName("type") + val type: String, + @SerialName("coordinates") + val coordinates: List>>> +) 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 new file mode 100644 index 00000000..a1e8c971 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/mapper/RegionMapper.kt @@ -0,0 +1,30 @@ +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.model.entity.region.GeometryEntity +import com.paw.key.domain.model.entity.region.RegionDataEntity +import javax.inject.Inject + +class RegionMapper @Inject constructor() { + fun mapDtoToEntity(dto: RegionResponseDto): RegionDataEntity { + return RegionDataEntity( + regionName = dto.regionName, + 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/remote/datasource/RegionDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/RegionDataSource.kt new file mode 100644 index 00000000..fa39eae0 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/RegionDataSource.kt @@ -0,0 +1,10 @@ +package com.paw.key.data.remote.datasource + +import com.paw.key.data.service.RegionService +import javax.inject.Inject + +class RegionDataSource @Inject constructor ( + private val regionService: RegionService +) { + suspend fun getRegionGeometry(userId: Int, regionId: Int) = regionService.getRegionGeometry(userId, regionId) +} \ 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 new file mode 100644 index 00000000..05e973a6 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/RegionRepositoryImpl.kt @@ -0,0 +1,19 @@ +package com.paw.key.data.repositoryimpl + +import com.paw.key.data.mapper.RegionMapper +import com.paw.key.data.remote.datasource.RegionDataSource +import com.paw.key.domain.model.entity.region.RegionDataEntity +import com.paw.key.domain.repository.RegionRepository +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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/service/RegionService.kt b/app/src/main/java/com/paw/key/data/service/RegionService.kt new file mode 100644 index 00000000..e262de63 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/RegionService.kt @@ -0,0 +1,15 @@ +package com.paw.key.data.service + +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.region.RegionResponseDto +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Path + +interface RegionService { + @GET("regions/{regionId}/geometry") + suspend fun getRegionGeometry( + @Header("X-USER-ID") userId: Int, + @Path("regionId") regionId: Int, + ): BaseResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/model/entity/region/RegionEntity.kt b/app/src/main/java/com/paw/key/domain/model/entity/region/RegionEntity.kt index 4d51299a..932640e9 100644 --- a/app/src/main/java/com/paw/key/domain/model/entity/region/RegionEntity.kt +++ b/app/src/main/java/com/paw/key/domain/model/entity/region/RegionEntity.kt @@ -1,19 +1,11 @@ package com.paw.key.domain.model.entity.region -import com.kakao.vectormap.LatLng - -data class RegionResponse( - val code: String, - val message: String, - val data: RegionData -) - -data class RegionData( +data class RegionDataEntity( val regionName: String, - val geometryDto: GeometryDto + val geometry: GeometryEntity ) -data class GeometryDto( +data class GeometryEntity( val type: String, val coordinates: List>>> // MultiPolygon은 여러 폴리곤의 리스트를 가짐 ) 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 new file mode 100644 index 00000000..05a06930 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/RegionRepository.kt @@ -0,0 +1,7 @@ +package com.paw.key.domain.repository + +import com.paw.key.domain.model.entity.region.RegionDataEntity + +interface RegionRepository { + suspend fun getRegionGeometry(userId: Int, regionId: Int): Result +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/TabListScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/TabListScreen.kt index 5b0198e5..5347944c 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/TabListScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/TabListScreen.kt @@ -98,28 +98,32 @@ fun TabListScreen( CourseCard( title = "제목을 입력해주세요", petName = "안녕꼬리", - date = "21/1/1" + date = "21/1/1", + onCLickItem = {} ) } item { CourseCard( title = "제목을 입력해주세요", petName = "안녕꼬리", - date = "21/1/1" + date = "21/1/1", + onCLickItem = {} ) } item { CourseCard( title = "제목을 입력해주세요", petName = "안녕꼬리", - date = "21/1/1" + date = "21/1/1", + onCLickItem = {} ) } item { CourseCard( title = "제목을 입력해주세요", petName = "안녕꼬리", - date = "21/1/1" + date = "21/1/1", + onCLickItem = {} ) } } 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 72b07049..611e7368 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 @@ -1,5 +1,6 @@ package com.paw.key.presentation.ui.course.walkreview +import android.Manifest import android.net.Uri import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult @@ -56,6 +57,12 @@ fun WalkReviewRoute( val lifecycleOwner = LocalLifecycleOwner.current + val imagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_IMAGES + } else { + Manifest.permission.READ_EXTERNAL_STORAGE + } + val pickMultipleMediaLauncher = rememberLauncherForActivityResult( ActivityResultContracts.PickMultipleVisualMedia(5) ) { uris -> @@ -64,6 +71,24 @@ fun WalkReviewRoute( } } + val galleryLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.GetMultipleContents() + ) { uris: List -> + if (uris.isNotEmpty()) { + val limitedUris = uris.take(5) + viewModel.onImagesSelected(limitedUris) + } + } + + val permissionLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + galleryLauncher.launch("image/*") + } + } + + LaunchedEffect(viewModel.sideEffect, lifecycleOwner) { viewModel.sideEffect.flowWithLifecycle(lifecycleOwner.lifecycle) .collect { sideEffect -> @@ -110,9 +135,13 @@ fun WalkReviewRoute( viewModel.onContentTextChanged(it) }, onClickImage = { - pickMultipleMediaLauncher.launch( - PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo) - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pickMultipleMediaLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo) + ) + } else { + permissionLauncher.launch(imagePermission) + } }, onImageDelete = { viewModel.onImageDelete(it) diff --git a/app/src/main/java/com/paw/key/presentation/ui/home/HomeLocationSettingScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/home/HomeLocationSettingScreen.kt index e0182d09..11c6ff0c 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/home/HomeLocationSettingScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/home/HomeLocationSettingScreen.kt @@ -147,7 +147,7 @@ fun HomeLocationSettingScreen( enabled = isFormValid, onClick = { if (isFormValid) { - + navigateNext() } } ) 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 0ac6119d..460f3f9a 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 @@ -159,6 +159,7 @@ fun HomeScreen( title = "제목을 입력해주세요", petName = "반려견 이름", date = "년도/월/일", + onCLickItem = {} ) } item{} @@ -179,19 +180,15 @@ fun HomeScreen( ) { viewModel.toggleLocationMenu() } - ) - - Box( - contentAlignment = Alignment.TopEnd, - modifier = Modifier - .padding(top = 97.dp, start = 240.dp), ) { SettingButton( modifier = Modifier + .padding(top = 43.dp, end = 16.dp) .noRippleClickable { viewModel.toggleLocationMenu() navigateHomeLocationSetting() - }, + } + .align(Alignment.TopEnd), ) } } 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 81562acb..5d6431b1 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 @@ -138,7 +138,7 @@ fun LoginScreen( onClick = navigateUp, enabled = true, isBackGround = true, - isBorder = true, + isBorder = false, modifier = Modifier .fillMaxWidth() ) @@ -154,6 +154,6 @@ fun LoginScreen( .navigationBarsPadding() ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(60.dp)) } } \ No newline at end of file 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 0f9df9d0..37d1f3c9 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 @@ -1,6 +1,9 @@ package com.paw.key.presentation.ui.main +import android.app.Activity import android.os.Build +import android.widget.Toast +import androidx.activity.compose.BackHandler import androidx.annotation.RequiresApi import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box @@ -15,10 +18,14 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.presentation.animation.FootprintAnimationScreen @@ -50,8 +57,27 @@ fun MainScreen( removeFootprint: (MainContract.Footprint) -> Unit, navigator: MainNavigator = rememberMainNavigator(), ) { + val context = LocalContext.current val snackBarHostState = remember { SnackbarHostState() } + val toast = remember { + Toast.makeText(context, "한 번 더 누르면 종료합니다.", Toast.LENGTH_SHORT) + } + + var backPressedTime by remember { mutableLongStateOf(0L) } + + if (navigator.currentTab != null) { + BackHandler { + val currentTime = System.currentTimeMillis() + if (currentTime - backPressedTime <= 2000L) { + (context as? Activity)?.finishAffinity() + } else { + backPressedTime = currentTime + toast.show() + } + } + } + MainScreenContent( navigator = navigator, snackBarHostState = snackBarHostState, 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 d34fc402..b495b801 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 @@ -62,7 +62,7 @@ fun PawKeyNavHost( homeLocationSettingNavGraph( paddingValues = paddingValues, navigateUp = navigator::navigateUp, - navigateNext = navigator::navigateCourse, + navigateNext = navigator::navigateRegional, navigateHomeLocationSetting = navigator::navigateHomeLocationSetting, modifier = modifier, ) @@ -123,8 +123,10 @@ fun PawKeyNavHost( navigateNext = navigator::navigateArchivedDetail, modifier = modifier ) + savedDetailNavGraph( navigateUp = navigator::navigateUp, + navigateToWalk = navigator::navigateWalkCourse, snackBarHostState = snackbarHostState ) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/ArchivedCourseListScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/ArchivedCourseListScreen.kt index 30037f9e..45f99d08 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/ArchivedCourseListScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/ArchivedCourseListScreen.kt @@ -68,6 +68,7 @@ fun ArchivedCourseListScreen( title = course.title, petName = course.petName, date = course.date, + onCLickItem = navigateNext ) } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseDetailScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseDetailScreen.kt index 9a9fafb1..c366b26e 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseDetailScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseDetailScreen.kt @@ -3,16 +3,20 @@ package com.paw.key.presentation.ui.mypage 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.lazy.LazyColumn +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -26,10 +30,12 @@ import com.paw.key.core.designsystem.theme.White1 @Composable fun SavedDetailRoute( navigateUp: () -> Unit, + navigateToWalk: () -> Unit, modifier: Modifier = Modifier, ) { SavedCourseDetailScreen( navigateUp = navigateUp, + navigateToWalk = navigateToWalk, modifier = modifier ) } @@ -37,41 +43,70 @@ fun SavedDetailRoute( @Composable fun SavedCourseDetailScreen( navigateUp: () -> Unit, + navigateToWalk: () -> Unit, modifier: Modifier = Modifier -){ +) { var isImageExpanded by remember { mutableStateOf(false) } - Box(modifier = Modifier.fillMaxSize()) { - TopBar(title = "내가 저장한 산책 루트", - onBackClick = { navigateUp() } + Column(modifier = Modifier.fillMaxSize()) { + TopBar( + title = "내가 저장한 산책 루트", + onBackClick = navigateUp ) - LazyColumn( - modifier = modifier - .fillMaxWidth() - .padding(16.dp) - .background(color = White1) + Box( + modifier = Modifier.weight(1f) ) { - item { - CourseDetail( - title = "한강 산책로", - petName = "후추", - date = "2025/06/02", - location = "뚝섬유원지", - distance = "4.5km", - option = listOf("풍경이 좋아요", "조용해요", "길이 깨끗해요"), - time = "1시간 30분 소요", - onImageClick = { isImageExpanded = true } // ← 콜백 전달 - ) - PawkeyButton( - text = "해당 루트로 산책하기", - enabled = true, - onClick = {}, - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp) - ) + LazyColumn( + modifier = modifier + .fillMaxWidth() + .padding(16.dp) + .background(color = White1) + ) { + item { + CourseDetail( + title = "한강 산책로", + petName = "후추", + date = "2025/06/02", + location = "뚝섬유원지", + distance = "4.5km", + option = listOf("풍경이 좋아요", "조용해요", "길이 깨끗해요"), + time = "1시간 30분 소요", + onImageClick = { + isImageExpanded = true + } + ) + + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + + HorizontalDivider( + thickness = 8.dp, + color = PawKeyTheme.colors.gray50, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) + ) + + Spacer(modifier = Modifier.height(120.dp)) + } } + + + + PawkeyButton( + text = "해당 루트로 산책하기", + enabled = true, + onClick = { + navigateToWalk() + }, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(top = 24.dp, start = 16.dp, end = 16.dp, bottom = 60.dp) + ) } if (isImageExpanded) { @@ -83,10 +118,14 @@ fun SavedCourseDetailScreen( } } + @Preview @Composable fun SavedCourseDetailPreview(){ PawKeyTheme { - SavedCourseDetailScreen(navigateUp = {}) + SavedCourseDetailScreen( + navigateUp = {}, + navigateToWalk = {} + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseListScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseListScreen.kt index 7f1b6906..6a10275c 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseListScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/SavedCourseListScreen.kt @@ -80,9 +80,7 @@ fun SavedCourseListScreen( title = item.title, petName = item.petName, date = item.date, - modifier = Modifier.clickable { - navigateNext() - } + onCLickItem = navigateNext ) } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt index 2d701bf0..0ed95387 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/ArchivedDetailNavigation.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable fun NavController.navigateArchivedDetail( navOptions: NavOptions? ) { - navigate(SavedCourse, navOptions) + navigate(ArchivedDetail, navOptions) } fun NavGraphBuilder.archivedDetailNavGraph( diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/SavedDetailNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/SavedDetailNavigation.kt index 834e70a2..09263024 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/SavedDetailNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/navigation/SavedDetailNavigation.kt @@ -16,17 +16,19 @@ import kotlinx.serialization.Serializable fun NavController.navigateSavedDetail( navOptions: NavOptions? ) { - navigate(SavedCourse, navOptions) + navigate(SavedDetail, navOptions) } fun NavGraphBuilder.savedDetailNavGraph( navigateUp: () -> Unit, + navigateToWalk : () -> Unit, snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, ) { composable { SavedDetailRoute( navigateUp = navigateUp, + navigateToWalk = navigateToWalk, modifier = modifier ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/onboard/OnboardingScreen.kt index 019002da..1805fe09 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 @@ -86,14 +86,15 @@ fun OnboardingScreen( verticalArrangement = Arrangement.spacedBy(10.dp) ) { PawkeyButton( - text = "로그인", + text = "신규 계정으로 회원가입", enabled = true, - onClick = { navigateSignUp() }, + onClick = { }, ) PawkeyButton( - text = "회원가입", - enabled = false, + text = "기존 계정으로 로그인", + enabled = true, + isBackGround = true, onClick = { navigateSignUp() }, ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt index 9ca76e06..46bdeff7 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/region/RegionalManagementScreen.kt @@ -34,6 +34,7 @@ import androidx.lifecycle.flowWithLifecycle import com.kakao.vectormap.LatLng import com.kakao.vectormap.MapView import com.paw.key.core.designsystem.component.CustomSnackBar +import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.util.UiState import com.paw.key.presentation.ui.region.component.regionalMapView @@ -56,7 +57,7 @@ fun RegionalManagementRoute( LaunchedEffect(Unit) { // Todo : 나중에 서버에서 좌표받아올때 변경 - val polyPoints: List> = listOf( + /*val polyPoints: List> = listOf( // 첫 번째 폴리곤 (JSON의 첫 번째 MultiPolygon 내부 배열) listOf( LatLng.from(37.51711061445719, 127.02190765991858), @@ -194,9 +195,12 @@ fun RegionalManagementRoute( LatLng.from(37.52700302845431, 127.00996246776681), LatLng.from(37.52701147918971, 127.00997077242218) // 닫는 좌표 ) - ) + )*/ - viewModel.getRegionPoints(polyPoints) + viewModel.getRegionGeometry( + X_USER_ID = 2, + regionId = 35, + ) } LaunchedEffect(viewModel.sideEffect, lifecycleOwner) { @@ -218,7 +222,7 @@ fun RegionalManagementRoute( val mapView = regionalMapView( lifeCycle = lifecycleOwner.lifecycle, context = context, - currentUserLocation = LatLng.from(37.497942, 127.027619), + currentUserLocation = state.centerLocation, polyPoints = (state.uiState as UiState.Success>>).data ) @@ -326,24 +330,15 @@ fun RegionalManagementScreen( Spacer(modifier = Modifier.height(12.dp)) - Button( + PawkeyButton( + text = "지역 변경하기", onClick = { onClickButton() }, modifier = Modifier - .fillMaxWidth() - .height(52.dp), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.buttonColors( - containerColor = if (selectedRegion != null) PawKeyTheme.colors.green500 else PawKeyTheme.colors.gray500 - ) - ) { - Text( - text = "지역 변경하기", - style = PawKeyTheme.typography.body16Sb, - color = PawKeyTheme.colors.white1 - ) - } + .fillMaxWidth(), + enabled = true, + ) } } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/region/state/RegionContract.kt b/app/src/main/java/com/paw/key/presentation/ui/region/state/RegionContract.kt index 3fe56031..f2a090b5 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/region/state/RegionContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/region/state/RegionContract.kt @@ -8,8 +8,8 @@ class RegionContract { @Immutable data class RegionState( val uiState: UiState>> = UiState.Loading, - val selectedRegion : String? = null, - val centerLocation : LatLng? = null, + val selectedRegion: String? = null, + val centerLocation: LatLng? = null, ) sealed class RegionSideEffect { 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 fb27cc50..f88725ac 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 @@ -1,24 +1,25 @@ package com.paw.key.presentation.ui.region.viewmodel +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.kakao.vectormap.LatLng import com.paw.key.core.util.UiState -import com.paw.key.domain.model.entity.region.GeometryDto +import com.paw.key.core.util.handleError +import com.paw.key.domain.repository.RegionRepository import com.paw.key.presentation.ui.region.state.RegionContract import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow 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.update import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class RegionViewModel @Inject constructor( - + private val regionRepository: RegionRepository ) : ViewModel() { private val _state = MutableStateFlow(RegionContract.RegionState()) val state : StateFlow @@ -28,12 +29,56 @@ class RegionViewModel @Inject constructor( val sideEffect : MutableSharedFlow get() = _sideEffect - fun getRegionPoints(points : List>) { - viewModelScope.launch { - _state.value = _state.value.copy( - uiState = UiState.Success(points) - ) - } + fun getRegionGeometry(X_USER_ID: Int, regionId: Int) = viewModelScope.launch { + regionRepository.getRegionGeometry(X_USER_ID, regionId) + .onSuccess { data -> + Log.d("RegionViewModel", "API 응답 성공: $data") + Log.d("RegionViewModel", "geometry type: ${data.geometry.type}") + Log.d("RegionViewModel", "coordinates size: ${data.geometry.coordinates.size}") + + val coordinates = data.geometry.coordinates + val flattenedLatLng = flattenCoordinatesToLatLng(coordinates) + + Log.d("RegionViewModel", "flattenedLatLng size: ${flattenedLatLng}") + + _state.update { + it.copy( + uiState = UiState.Success(flattenedLatLng) + ) + } + + val firstPoint = coordinates + .firstOrNull() // 첫 번째 Polygon + ?.firstOrNull() // 첫 번째 Ring (외부 경계) + ?.firstOrNull() // 첫 번째 Point + + Log.d("RegionViewModel", "First point: $firstPoint") + + if (firstPoint != null) { + val latLng = LatLng.from(firstPoint.first, firstPoint.second) + _state.update { + it.copy( + centerLocation = latLng, + selectedRegion = data.regionName + ) + } + } else { + _state.update { + it.copy( + uiState = UiState.Failure("좌표 데이터가 올바르지 않습니다") + ) + } + } + } + .onFailure { throwable -> + Log.e("RegionViewModel", "API 호출 실패", throwable) + val errorMessage = handleError(throwable) + _state.update { + it.copy( + uiState = UiState.Failure(errorMessage) + ) + } + } } fun onChangeRegion() { @@ -43,18 +88,14 @@ class RegionViewModel @Inject constructor( ) } } +} - fun GeometryDto.toLatLngList(): List { - val latLngList = mutableListOf() - this.coordinates.forEach { polygon -> - polygon.forEach { linearRing -> - linearRing.forEach { coordinate -> - latLngList.add( - LatLng.from(coordinate.second, coordinate.first) - ) - } - } - } - return latLngList +private fun flattenCoordinatesToLatLng( + coordinates: List>>> +): List> { + return coordinates.map { polygon -> // 각 Polygon + polygon.firstOrNull()?.map { point -> + LatLng.from(point.first, point.second) + }.orEmpty() } } \ No newline at end of file