diff --git a/app/src/main/java/com/paw/key/core/designsystem/component/CourseCard.kt b/app/src/main/java/com/paw/key/core/designsystem/component/CourseCard.kt index 92ab9859..52935c3f 100644 --- a/app/src/main/java/com/paw/key/core/designsystem/component/CourseCard.kt +++ b/app/src/main/java/com/paw/key/core/designsystem/component/CourseCard.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import com.paw.key.R 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 @@ -48,7 +49,9 @@ fun CourseCard( petName:String, date: String, onCLickItem : () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + isShared : Boolean = false, // true면 떠진거 false면 닫은거 + isRecord : Boolean = false // 기록한 아이템 - true면 하트, false면 공유 아이콘 ) { Column( modifier = modifier @@ -65,21 +68,35 @@ fun CourseCard( modifier = Modifier .fillMaxWidth() .aspectRatio(343f / 172f) - .clip(RoundedCornerShape(20.dp)) + .clip(RoundedCornerShape(10.dp)) ) { - // 지도 이미지 + // 지도 이미지 Todo : 테스트용 Image( painter = painterResource(id = R.drawable.dummy_map), contentDescription = null, - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .padding(start = 8.dp, end = 8.dp, top = 8.dp) + .clip(RoundedCornerShape(8.dp)), contentScale = ContentScale.Crop ) + /*AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data("https://pawkey-server.com/image.jpg") // ← 서버에서 받은 이미지 URL 넣깅 + .crossfade(true) + .build(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + )*/ + // 하단 그라데이션 오버레이 Box( modifier = Modifier .fillMaxWidth() .height(LocalConfiguration.current.screenHeightDp.dp * 0.6f) // 높이 조절 가능 + .padding(start = 8.dp, end = 8.dp, top = 8.dp) .align(Alignment.BottomCenter) .background( brush = Brush.verticalGradient( @@ -87,8 +104,10 @@ fun CourseCard( PawKeyTheme.colors.black.copy(0.05f), PawKeyTheme.colors.black.copy(0.55f) ) - ) + ), + shape = RoundedCornerShape(8.dp) ) + .clip(RoundedCornerShape(8.dp)) ) // 프로필 + 제목 @@ -131,40 +150,67 @@ fun CourseCard( ) } } - Spacer(modifier = Modifier.weight(1f)) // 아이콘을 Row 끝으로 밀기 위한 Spacer + Spacer(modifier = Modifier.weight(1f)) - val isLiked = remember { mutableStateOf(false) } + when { + isShared -> { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_eye_linear_valid), + contentDescription = "공유됨", + tint = PawKeyTheme.colors.gray200, + ) + } + isRecord -> { + val isLiked = remember { mutableStateOf(false) } - Icon( - imageVector = if (isLiked.value) { - ImageVector.vectorResource(id = R.drawable.ic_heart_filled) - } else { - ImageVector.vectorResource(id = R.drawable.ic_heart_default) - }, - contentDescription = "좋아요", - tint = Color.Unspecified, - modifier = Modifier.clickable { - isLiked.value = !isLiked.value + Icon( + imageVector = if (isLiked.value) { + ImageVector.vectorResource(id = R.drawable.ic_heart_filled) + } else { + ImageVector.vectorResource(id = R.drawable.ic_heart_default) + }, + contentDescription = "좋아요", + tint = Color.Unspecified, + modifier = Modifier.clickable { + isLiked.value = !isLiked.value + } + ) } - ) + else -> { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_eye_linear_invalid), + contentDescription = "공유 안됨", + tint = PawKeyTheme.colors.gray200, + ) + } + } } } Spacer(modifier = Modifier.height(12.dp)) - ChipRow(tags = listOf( + ChipRow( + tags = listOf( "이륜차 거의 없음", "배변 쓰레기통", "쉼터", "CCTV 있음", "물그릇 비치","이륜차 거의 없음", "배변 쓰레기통", - "쉼터", - )) + "쉼터",), + modifier = Modifier + .padding(start = 16.dp, end = 16.dp) + ) Spacer(modifier = Modifier.height(12.dp)) } + + HorizontalDivider( + color = PawKeyTheme.colors.gray50, + thickness = 1.dp, + modifier = Modifier.padding(start = 16.dp, end = 16.dp) + ) } @Preview(showBackground = true) @Composable diff --git a/app/src/main/java/com/paw/key/core/designsystem/component/TopBar.kt b/app/src/main/java/com/paw/key/core/designsystem/component/TopBar.kt index 3f882719..5be756a7 100644 --- a/app/src/main/java/com/paw/key/core/designsystem/component/TopBar.kt +++ b/app/src/main/java/com/paw/key/core/designsystem/component/TopBar.kt @@ -1,6 +1,7 @@ package com.paw.key.core.designsystem.component import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -14,6 +15,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.paw.key.R import com.paw.key.core.designsystem.theme.PawKeyTheme @@ -21,31 +23,44 @@ import com.paw.key.core.designsystem.theme.PawKeyTheme @Composable fun TopBar( title: String, - onBackClick: () -> Unit = {} + onBackClick: () -> Unit, + modifier: Modifier = Modifier, + isBackVisible: Boolean = true, ) { - Row( - modifier = Modifier + Box( + modifier = modifier .fillMaxWidth() - .padding(vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically + .padding(vertical = 12.dp, horizontal = 16.dp) ) { - - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_left_black), - contentDescription = "뒤로가기", - modifier = Modifier.clickable { onBackClick() } - ) - - Box( - modifier = Modifier.weight(1f), - contentAlignment = Alignment.Center - ) { - Text( - text = title, - style = PawKeyTheme.typography.body16Sb + if (isBackVisible) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_left_black), + contentDescription = "뒤로가기", + modifier = Modifier + .align(Alignment.CenterStart) + .clickable { onBackClick() } ) } - Spacer(modifier = Modifier.width(24.dp)) + Text( + text = title, + style = PawKeyTheme.typography.head18Sb, + modifier = Modifier + .align(Alignment.Center) + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun TopBarPreview() { + PawKeyTheme { + TopBar( + title = "산책 완료", + onBackClick = {}, + isBackVisible = true, + modifier = Modifier + .fillMaxWidth() + ) } } \ 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 05e973a6..42d82910 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 @@ -6,7 +6,6 @@ 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 diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/entire/EntireCourseScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/entire/EntireCourseScreen.kt index a1e43a0f..2be31cee 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/entire/EntireCourseScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/entire/EntireCourseScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.snapshotFlow 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.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -159,7 +160,8 @@ fun EntireCourseScreen( onTabSelected(it) }, tabs = tabs, - modifier = Modifier, + modifier = Modifier + .padding(top = 8.dp), ) when (currentPage) { 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 5347944c..d3104f85 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 @@ -86,12 +86,10 @@ fun TabListScreen( } LazyColumn( - verticalArrangement = Arrangement.spacedBy(20.dp), modifier = modifier .fillMaxSize() .background(PawKeyTheme.colors.white2) .padding(bottom = 36.dp) - ) { item { @@ -99,6 +97,7 @@ fun TabListScreen( title = "제목을 입력해주세요", petName = "안녕꼬리", date = "21/1/1", + isRecord = true, onCLickItem = {} ) } @@ -107,6 +106,7 @@ fun TabListScreen( title = "제목을 입력해주세요", petName = "안녕꼬리", date = "21/1/1", + isRecord = true, onCLickItem = {} ) } @@ -115,6 +115,7 @@ fun TabListScreen( title = "제목을 입력해주세요", petName = "안녕꼬리", date = "21/1/1", + isRecord = true, onCLickItem = {} ) } @@ -123,6 +124,7 @@ fun TabListScreen( title = "제목을 입력해주세요", petName = "안녕꼬리", date = "21/1/1", + isRecord = true, onCLickItem = {} ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/viewmodel/TapListViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/viewmodel/TapListViewModel.kt index 9e069d0b..efe2532e 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/viewmodel/TapListViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/List/viewmodel/TapListViewModel.kt @@ -12,8 +12,7 @@ import javax.inject.Inject @HiltViewModel class TapListViewModel @Inject constructor( -) - : ViewModel() { +) : ViewModel() { private val _state = MutableStateFlow(TapListContract.TapListState()) val state: StateFlow diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/TapMapScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/TapMapScreen.kt index 1f46cbd3..4f6d1161 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/TapMapScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/entire/tab/map/TapMapScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.FloatingActionButton @@ -239,9 +240,10 @@ fun TapMapScreen( FloatingActionButton( onClick = onClickTracking, shape = CircleShape, - containerColor = PawKeyTheme.colors.gray50, + containerColor = PawKeyTheme.colors.white1, modifier = Modifier - .align(Alignment.BottomEnd) + .align(Alignment.CenterEnd) + .size(44.dp) ) { Icon( imageVector = ImageVector.vectorResource(R.drawable.ic_course_map_tap_location_on), diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt index fe18c8e3..df46a541 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walk/WalkCourseScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -328,13 +329,15 @@ fun WalkCourseRoute( // 캡처 부분 LaunchedEffect(state.shouldCaptureMap, state.poiPoints) { + delay(500) + if (state.shouldCaptureMap) { val glSurfaceView = mapView.surfaceView as? GLSurfaceView if (glSurfaceView != null) { withContext(Dispatchers.IO) { captureMapToBitmap(glSurfaceView) { capturedBitmap -> capturedBitmap?.let { - viewModel.onMapCaptured(it) // 캡처된 비트맵을 ViewModel로 전달 + viewModel.onMapCaptured(it) Log.d("WalkCourseRoute", "맵 캡처 성공! (triggered by shouldCaptureMap)") } ?: run { Log.e("WalkCourseRoute", "맵 캡처 실패: 비트맵이 null입니다.") @@ -463,12 +466,11 @@ fun WalkCourseScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - // Todo : 텍스트 스타일 24b로 변경 예쩡 if (!isSharedWalk) { Text( text = "산책이 중단되었어요!", textAlign = TextAlign.Center, - style = PawKeyTheme.typography.head22B, + style = PawKeyTheme.typography.head24B, color = PawKeyTheme.colors.white1, modifier = Modifier.fillMaxWidth() ) @@ -479,7 +481,9 @@ fun WalkCourseScreen( textAlign = TextAlign.Center, style = PawKeyTheme.typography.body16M, color = PawKeyTheme.colors.white2, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth() ) } else { Text( @@ -509,26 +513,28 @@ fun WalkCourseScreen( .navigationBarsPadding(), horizontalAlignment = Alignment.CenterHorizontally ) { - Row ( - modifier = Modifier - .fillMaxWidth() - ) { - Spacer(modifier = Modifier.weight(1f)) - - FloatingActionButton( - shape = CircleShape, - onClick = onClickTracking, - containerColor = Color.White + if (isTracking) { + Row ( + modifier = Modifier + .fillMaxWidth() ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_course_map_tap_location_on), - contentDescription = "내 위치",//stringResource(id = R.string.lo) - tint = Color.Black - ) + Spacer(modifier = Modifier.weight(1f)) + + FloatingActionButton( + shape = CircleShape, + onClick = onClickTracking, + containerColor = PawKeyTheme.colors.white1, + modifier = Modifier + .size(44.dp) + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_course_map_tap_location_on), + contentDescription = "내 위치",//stringResource(id = R.string.lo) + tint = Color.Unspecified + ) + } } - } - if (isTracking) { PawkeyButton( text = "중지하기", enabled = true, @@ -553,13 +559,14 @@ fun WalkCourseScreen( }, modifier = Modifier .padding(top = 16.dp) + .padding(bottom = 44.dp) ) } else { Row ( modifier = Modifier .fillMaxWidth() .padding(16.dp) - ){ + ) { Text( text = "계속 산책하기", modifier = Modifier @@ -576,7 +583,7 @@ fun WalkCourseScreen( color = PawKeyTheme.colors.green500, shape = RoundedCornerShape(8.dp) ) - .padding(horizontal = 24.dp, vertical = 16.dp), + .padding(horizontal = 28.dp, vertical = 16.dp), color = PawKeyTheme.colors.green500, style = PawKeyTheme.typography.body16Sb ) @@ -595,7 +602,7 @@ fun WalkCourseScreen( navigateNext() onStopTracking() } - .padding(horizontal = 24.dp, vertical = 16.dp), + .padding(horizontal = 28.dp, vertical = 16.dp), color = PawKeyTheme.colors.white1, style = PawKeyTheme.typography.body16Sb ) @@ -611,13 +618,18 @@ fun captureMapToBitmap(surfaceView: GLSurfaceView, onCaptured: (Bitmap?) -> Unit surfaceView.queueEvent { val egl = EGLContext.getEGL() as EGL10 val gl = egl.eglGetCurrentContext().gl as GL10 - val bitmap = createBitmapFromGLSurface(0, 0, surfaceView.width, surfaceView.height, gl) + // 원하는 최종 크기를 먼저 계산 + val screenWidth = surfaceView.context.resources.displayMetrics.widthPixels + val contentWidth = (screenWidth - 32) + val targetHeight = (156 * surfaceView.context.resources.displayMetrics.density).toInt() + + val bitmap = createBitmapFromGLSurface(0, 0, surfaceView.width, surfaceView.height, gl, contentWidth, targetHeight) onCaptured(bitmap) } } -fun createBitmapFromGLSurface(x: Int, y: Int, w: Int, h: Int, gl: GL10): Bitmap? { +fun createBitmapFromGLSurface(x: Int, y: Int, w: Int, h: Int, gl: GL10, targetWidth: Int, targetHeight: Int): Bitmap? { val bitmapBuffer = IntArray(w * h) val bitmapSource = IntArray(w * h) val intBuffer = IntBuffer.wrap(bitmapBuffer) @@ -654,8 +666,10 @@ fun createBitmapFromGLSurface(x: Int, y: Int, w: Int, h: Int, gl: GL10): Bitmap? var cropWidth: Int var cropHeight: Int + // 비율 조정 val currentAspectRatio = w.toFloat() / h.toFloat() + // 가로와 세로의 비율 조정 - 가로가 크다면 세로를 증가, 세로가 크다면 가로로 증가 if (currentAspectRatio > targetAspectRatio) { cropHeight = h cropWidth = (h * targetAspectRatio).toInt() diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walk/component/courseMapView.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walk/component/courseMapView.kt index 9f57e2cc..a4756ea4 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walk/component/courseMapView.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walk/component/courseMapView.kt @@ -79,6 +79,19 @@ fun courseMapView( mutableStateOf(null) } + /*val yeoksamCoordinates = listOf( + LatLng.from(37.50097, 127.03734), // 역삼동 중심 :contentReference[oaicite:1]{index=1} + LatLng.from(37.50079, 127.03689), // 역삼역 (L2) 정문 인근 :contentReference[oaicite:2]{index=2} + LatLng.from(37.50001, 127.03549), // 역삼역 지하철역 (GPS 웹 기준) :contentReference[oaicite:3]{index=3} + LatLng.from(37.49950, 127.03322), // 역삼1동 중심 지역 :contentReference[oaicite:4]{index=4} + LatLng.from(37.49900, 127.03856), // 역삼동 중심 북동쪽 :contentReference[oaicite:5]{index=5} + LatLng.from(37.49999, 127.03719), // 테헤란로 중심가 (중간 위치) ← 위도/경도 참고 위 :contentReference[oaicite:6]{index=6} + LatLng.from(37.49850, 127.03800), // 강남대로 인근 + LatLng.from(37.49800, 127.03450), // 논현로 인근 + LatLng.from(37.50150, 127.03700), // 삼성역 방면 경계 지역 + LatLng.from(37.50050, 127.03900), // 국기원/코엑스 방향 경계 + )*/ + val drawRouteOnMap: (KakaoMap, List) -> Unit = { kakaoMap, pointsToDraw -> if (pointsToDraw.isNotEmpty()) { currentDrawnRouteLine?.remove() @@ -129,6 +142,12 @@ fun courseMapView( kakaoMapState?.let { map -> drawRouteOnMap(map, poiPoints) } + + kakaoMapState?.moveCamera( + CameraUpdateFactory.fitMapPoints( + poiPoints.toTypedArray(), 150, 15 + ) + ) } DisposableEffect(lifeCycle) { @@ -190,11 +209,6 @@ fun courseMapView( override fun onPause(owner: LifecycleOwner) { mapView.pause() - kakaoMapState?.moveCamera( - CameraUpdateFactory.fitMapPoints( - poiPoints.toTypedArray(), 700 - ) - ) } } @@ -208,19 +222,16 @@ fun courseMapView( } } - LaunchedEffect(currentUserLocation, centerLabel, kakaoMapState) { + LaunchedEffect(currentUserLocation, isTrackingEnabled, centerLabel, kakaoMapState) { if (currentUserLocation != null && centerLabel != null && kakaoMapState != null) { centerLabel?.moveTo(currentUserLocation) - Log.d("courseMapview", "Center label moved to: $currentUserLocation") - } - } - LaunchedEffect(isTrackingEnabled) { - kakaoMapState?.moveCamera( - CameraUpdateFactory.newCenterPosition( - currentUserLocation, 18 + kakaoMapState?.moveCamera( + CameraUpdateFactory.newCenterPosition( + currentUserLocation, 18 + ) ) - ) + } } LaunchedEffect(isPauseTracking) { @@ -236,20 +247,3 @@ fun courseMapView( return mapView } - - -fun calculateMidpoint(point1: LatLng, point2: LatLng): LatLng { - val lonAvg = (point1.longitude + point2.longitude) / 2.0 - - val lat1Rad = toRadians(point1.latitude) - val lon1Rad = toRadians(point1.longitude) - val lat2Rad = toRadians(point2.latitude) - val lon2Rad = toRadians(point2.longitude) - - val Bx = cos(lat2Rad) * cos(lon2Rad - lon1Rad) - val By = cos(lat2Rad) * sin(lon2Rad - lon1Rad) - val latMid = atan2(sin(lat1Rad) + sin(lat2Rad), sqrt((cos(lat1Rad) + Bx) * (cos(lat1Rad) + Bx) + By * By)) - val lonMid = lon1Rad + atan2(By, cos(lat1Rad) + Bx) - - return LatLng.from(toDegrees(latMid), toDegrees(lonMid)) -} diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walk/viewmodel/WalkCourseViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walk/viewmodel/WalkCourseViewModel.kt index bb5d1c1e..05f5cb2c 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walk/viewmodel/WalkCourseViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walk/viewmodel/WalkCourseViewModel.kt @@ -191,14 +191,6 @@ class WalkCourseViewModel @Inject constructor( val currentWalkState = _state.value try { - PreferenceDataStore.saveWalkSummary( - context = context, - points = currentWalkState.poiPoints.toList(), - totalDistance = currentWalkState.totalDistance, - totalTime = _totalTime.value, - totalSteps = currentWalkState.steps.toInt() - ) - walkSharedResultRepository.saveResult( bitmap = currentWalkState.bitmap, totalTime = _totalTime.value, @@ -213,12 +205,12 @@ class WalkCourseViewModel @Inject constructor( Log.e("WalkCourseViewModel", "Error saving all walk summary data: ${e.message}", e) _sideEffect.emit(WalkCourseSideEffect.ShowSnackBar("산책 기록 저장 실패: ${e.localizedMessage}")) } finally { - PreferenceDataStore.saveWalkSummary( - context = context, - points = currentWalkState.poiPoints.toList(), - totalDistance = currentWalkState.totalDistance, + walkSharedResultRepository.saveResult( + bitmap = currentWalkState.bitmap, totalTime = _totalTime.value, - totalSteps = currentWalkState.steps.toInt() + distance = currentWalkState.totalDistance, + steps = currentWalkState.steps.toInt(), + points = currentWalkState.poiPoints.toList() ) } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/WalkCompletionScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/WalkCompletionScreen.kt index 30a84f06..975e63af 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/WalkCompletionScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/WalkCompletionScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -29,6 +30,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.R import com.paw.key.core.designsystem.component.PawkeyButton +import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.util.PreferenceDataStore import com.paw.key.presentation.ui.course.walk.component.WalkRecordRow @@ -89,29 +91,37 @@ fun WalkCompletionScreen( .background(PawKeyTheme.colors.white2), horizontalAlignment = Alignment.CenterHorizontally, ){ + TopBar( + title = "산책 완료", + onBackClick = navigateUp, + modifier = Modifier + .background(PawKeyTheme.colors.white1), + isBackVisible = false + ) + Text( - text = "포비와 함께한 산책한 루트에요.", + text = "산책 결과를 확인해보세요.", color = PawKeyTheme.colors.black, - style = PawKeyTheme.typography.head20B2, - modifier = modifier - .padding(top = 20.dp, bottom = 10.dp) + style = PawKeyTheme.typography.head18Sb, + modifier = Modifier + .padding(top = 36.dp, bottom = 16.dp) .padding(horizontal = 16.dp) .fillMaxWidth() ) Column ( - modifier = Modifier + modifier = modifier .padding(start = 16.dp, end = 16.dp) .background( color = PawKeyTheme.colors.white1, shape = RoundedCornerShape(12.dp) ) - ){ + ) { + // Todo : 사진 받아올 곳 WalkCompleteHeader( bitmap = null, - modifier = modifier - .fillMaxWidth() - .padding(top = 10.dp) + modifier = Modifier + .padding(top = 16.dp, start = 16.dp, end = 16.dp) ) bitmap?.asImageBitmap()?.let { @@ -119,13 +129,14 @@ fun WalkCompletionScreen( bitmap = it, contentDescription = "My Image", modifier = Modifier - .padding(horizontal = 8.dp) - .padding(top = 6.dp) + .padding(start = 8.dp, end = 8.dp) + .padding(top = 12.dp) + .clip(RoundedCornerShape(8.dp)) ) } HorizontalDivider( - thickness = 3.dp, + thickness = 1.dp, color = PawKeyTheme.colors.gray50, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/component/WalkCompletionHeader.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/component/WalkCompletionHeader.kt index 8c2654a6..ef64ae5a 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/component/WalkCompletionHeader.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkcomplete/component/WalkCompletionHeader.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape @@ -24,21 +25,24 @@ fun WalkCompleteHeader( ) { Row ( modifier = modifier - ){ + .fillMaxWidth() + ) { AsyncImage( model = bitmap, contentDescription = "profile", modifier = Modifier .size(48.dp) - .padding(end = 10.dp) .background( - color = Color.LightGray, + color = PawKeyTheme.colors.gray50, shape = CircleShape ) .clip(CircleShape) ) - Column { + Column ( + modifier = Modifier + .padding(start = 10.dp) + ) { Text( text = "포비", color = PawKeyTheme.colors.black, @@ -56,7 +60,7 @@ fun WalkCompleteHeader( } } -@Preview +@Preview(showBackground = true) @Composable private fun WalkCompleteHeaderPreview() { PawKeyTheme { 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 611e7368..f2fd6295 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 @@ -7,6 +7,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -15,8 +16,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -24,15 +28,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle +import coil.compose.AsyncImage import com.paw.key.R import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.component.SubChip +import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewFeedbackForm import com.paw.key.presentation.ui.course.walkreview.component.WalkReviewFeedbackHeader @@ -47,13 +54,14 @@ import com.paw.key.presentation.ui.course.walkreview.viewmodel.WalkReviewViewMod fun WalkReviewRoute( navigateUp: () -> Unit, navigateNext: () -> Unit, + navigateShared : () -> Unit, snackBarHostState: SnackbarHostState, modifier: Modifier = Modifier, viewModel: WalkReviewViewModel = hiltViewModel(), isSharedWalk : Boolean = false ) { val state by viewModel.state.collectAsStateWithLifecycle() - val isFormValid by viewModel.isFormValid.collectAsStateWithLifecycle() + val isValid = viewModel.state.collectAsStateWithLifecycle().value.isValidForm val lifecycleOwner = LocalLifecycleOwner.current @@ -121,7 +129,7 @@ fun WalkReviewRoute( 4 -> viewModel.onSelectFrequencyFeedback(feedItem) } }, - isFormValid = isFormValid, + isFormValid = isValid, isSharedWalk = isSharedWalk, imageList = state.images, petName = state.petName, @@ -146,6 +154,7 @@ fun WalkReviewRoute( onImageDelete = { viewModel.onImageDelete(it) }, + navigateShared = navigateShared, modifier = modifier, ) } @@ -160,6 +169,7 @@ fun WalkReviewScreen( onContentTextChanged : (String) -> Unit, onClickImage : () -> Unit, onImageDelete : (Uri?) -> Unit, + navigateShared : () -> Unit, imageList: List, isFormValid : Boolean, isSharedWalk : Boolean, @@ -169,235 +179,291 @@ fun WalkReviewScreen( feedbackState : WalkReviewContract.WalkReviewFeedbackState, modifier: Modifier = Modifier, ) { - LazyColumn ( + Column ( modifier = modifier .fillMaxSize() - .background(PawKeyTheme.colors.white1) - .padding(bottom = 16.dp, top = 16.dp) - ){ - if (!isSharedWalk) { - item { - WalkReviewImageRow( - imageList = imageList, - onClickCard = { index, _ -> - if (index != 0) { - onClickImage() - } - }, - onImageDelete = { - onImageDelete(it) - }, - modifier = Modifier - .padding(start = 16.dp, end = 16.dp) - .background(PawKeyTheme.colors.white1), - ) - } - } - - item { - Column( - modifier = Modifier - .padding(top = 12.dp, bottom = 12.dp, start = 16.dp, end = 16.dp) - .background(PawKeyTheme.colors.white1) - ) { - WalkReviewInfoHolder( - icon = R.drawable.ic_walk_review_location, - content = "강남구 역삼동" - ) - - WalkReviewInfoHolder( - icon = R.drawable.ic_walk_review_time, - content = "2025.06.26(금) | 23:20-23:30" - ) - } - } - - item { - Row ( - modifier = Modifier - .padding(bottom = 12.dp, start = 16.dp, end = 16.dp) - .fillMaxWidth() - .background(PawKeyTheme.colors.white1) - ) { - val chips = listOf("2.2km", "30분", "3208걸음") - - chips.forEach { - SubChip( - text = it, + ) { + TopBar( + title = "산책 기록하기", + onBackClick = navigateUp, + modifier = Modifier + .background(PawKeyTheme.colors.white1), + isBackVisible = true + ) + + HorizontalDivider( + thickness = 1.dp, + color = PawKeyTheme.colors.gray50, + modifier = Modifier + .fillMaxWidth() + ) + + LazyColumn ( + modifier = modifier + .background(PawKeyTheme.colors.white1) + .padding(bottom = 16.dp) + ){ + if (!isSharedWalk) { + item { + WalkReviewImageRow( + imageList = imageList, + onClickCard = { index, _ -> + if (index != 0) { + onClickImage() + } + }, + onImageDelete = { + onImageDelete(it) + }, modifier = Modifier - .padding(end = 6.dp) + .background(PawKeyTheme.colors.white1), ) } - } - } - - item { - HorizontalDivider( - thickness = 10.dp, - color = PawKeyTheme.colors.gray50, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 12.dp) - ) - } + } else { + item { + Text( + text = "제목", + style = PawKeyTheme.typography.head20Sb, + color = PawKeyTheme.colors.green500, + modifier = Modifier + .padding(bottom = 10.dp) + ) - item { - WalkReviewFeedbackHeader( - petName = petName, - modifier = Modifier - .padding(top = 12.dp, start = 16.dp, end = 16.dp) - .background(PawKeyTheme.colors.white1) - ) - } + Row ( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 10.dp) + ) { + AsyncImage( + model = "", + contentDescription = "profile", + modifier = Modifier + .size(48.dp) + .background( + color = PawKeyTheme.colors.gray50, + shape = CircleShape + ) + .clip(CircleShape) + .padding(end = 10.dp) + ) + + Text( + text = "강아지 이름 작성", + style = PawKeyTheme.typography.body16Sb, + color = PawKeyTheme.colors.gray600 + ) + } + } + } - item { - val feedbackTitle = listOf( - "\uD83D\uDEB8 산책 중 안전 요소는 어땠나요?", - "\uD83E\uDDFA 산책 중 어떤 편의 시설이 있었나요?", - "\uD83C\uDF3F 산책 주변의 길 상태는 어땠나요?", - "\uD83D\uDE0C 산책로의 분위기는 어땠나요 ?", - "\uD83D\uDC36 산책 중 다른 강아지들과 얼마나 마주쳤나요?" - ) + item { + Column( + modifier = Modifier + .padding(bottom = 12.dp, start = 16.dp, end = 16.dp) + .background(PawKeyTheme.colors.white1) + ) { + WalkReviewInfoHolder( + icon = R.drawable.ic_walk_review_location, + content = "강남구 역삼동" + ) - // Todo : 서버에서 주는 값으로 변경 예정 - val eachFeedbackList = listOf( - listOf( - "킥보드나 자전거가 거의 없어요", - "차량이 거의 다니지 않아요", - "야간 조명이 잘 되어 있어요", - "보도와 차도가 구분되어 있어요", - "보도가 넓어서 산책하기 편했어요" - ), - listOf( - "배변 봉투 쓰레기통이 있어요", - "애견 산책로가 있어요", - "쉴 곳이 있어요", - "편의점이 있어요", - "반려견 동반 가능한 카페가 있어요" - ), - listOf( - "풀이 많아요", - "주로 흙길이에요", - "주로 아스팔트, 벽돌이에요", - "뛰어놀 수 있는 공간이 있어요" - ), - listOf( - "조용하고 한적했어요", - "사람이 적당히 있어요", - "사람이 많았어요" - ), - listOf( - "많이 마주쳤어요", - "가끔 마주쳤어요", - "거의 없었어요" - ) - ) + WalkReviewInfoHolder( + icon = R.drawable.ic_walk_review_time, + content = "2025.06.26(금) | 23:20-23:30" + ) + } + } - feedbackTitle.forEachIndexed { index, title -> - val currentSelectedFeedback = when (index) { - 0 -> feedbackState.selectedSafetyFeedback - 1 -> feedbackState.selectedFacilityFeedback - 2 -> feedbackState.selectedRoadFeedback - 3 -> feedbackState.selectedNoiseFeedback - 4 -> feedbackState.selectedFrequencyFeedback - else -> null + item { + Row ( + modifier = Modifier + .padding(bottom = 12.dp, start = 16.dp, end = 16.dp) + .fillMaxWidth() + .background(PawKeyTheme.colors.white1) + ) { + val chips = listOf("2.2km", "30분", "3208걸음") + + chips.forEach { + SubChip( + text = it, + modifier = Modifier + .padding(end = 6.dp) + ) + } } + } - WalkReviewFeedbackForm( - icon = R.drawable.ic_walk_review_location, - title = title, - selectedFeedbackItem = currentSelectedFeedback, - feedbackList = eachFeedbackList[index], - onClickFeedback = { selectedFeedback -> - onClickFeedback(index, selectedFeedback) - }, + item { + HorizontalDivider( + thickness = 10.dp, + color = PawKeyTheme.colors.gray50, modifier = Modifier - .padding(top = 12.dp, bottom = 12.dp, start = 16.dp, end = 16.dp) + .fillMaxWidth() + .padding(bottom = 12.dp) ) } - } - item { - HorizontalDivider( - thickness = 10.dp, - color = PawKeyTheme.colors.gray50, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 12.dp) - ) - } - - if (!isSharedWalk) { item { - Text( - text = "산책에 대한 감상을 들려주시겠어요?", - style = PawKeyTheme.typography.body16M, - color = PawKeyTheme.colors.black, + WalkReviewFeedbackHeader( + petName = petName, modifier = Modifier .padding(top = 12.dp, start = 16.dp, end = 16.dp) + .background(PawKeyTheme.colors.white1) ) + } - WalkReviewTextField( - textValue = titleText, - placeHolder = "후기 제목을 입력해주세요.", - onTextChanged = { - onTitleTextChanged(it) - }, - modifier = Modifier - .padding(top = 10.dp, start = 16.dp, end = 16.dp) + item { + val feedbackTitle = listOf( + "\uD83D\uDEB8 산책 중 안전 요소는 어땠나요?", + "\uD83E\uDDFA 산책 중 어떤 편의 시설이 있었나요?", + "\uD83C\uDF3F 산책 주변의 길 상태는 어땠나요?", + "\uD83D\uDE0C 산책로의 분위기는 어땠나요 ?", + "\uD83D\uDC36 산책 중 다른 강아지들과 얼마나 마주쳤나요?" ) - WalkReviewTextField( - textValue = contentText, - placeHolder = "산책 후기를 간단하게 적어주세요!", - onTextChanged = { - onContentTextChanged(it) - }, + // Todo : 서버에서 주는 값으로 변경 예정 + val eachFeedbackList = listOf( + listOf( + "킥보드나 자전거가 거의 없어요", + "차량이 거의 다니지 않아요", + "야간 조명이 잘 되어 있어요", + "보도와 차도가 구분되어 있어요", + "보도가 넓어서 산책하기 편했어요" + ), + listOf( + "배변 봉투 쓰레기통이 있어요", + "애견 산책로가 있어요", + "쉴 곳이 있어요", + "편의점이 있어요", + "반려견 동반 가능한 카페가 있어요" + ), + listOf( + "풀이 많아요", + "주로 흙길이에요", + "주로 아스팔트, 벽돌이에요", + "뛰어놀 수 있는 공간이 있어요" + ), + listOf( + "조용하고 한적했어요", + "사람이 적당히 있어요", + "사람이 많았어요" + ), + listOf( + "많이 마주쳤어요", + "가끔 마주쳤어요", + "거의 없었어요" + ) + ) + + feedbackTitle.forEachIndexed { index, title -> + val currentSelectedFeedback = when (index) { + 0 -> feedbackState.selectedSafetyFeedback + 1 -> feedbackState.selectedFacilityFeedback + 2 -> feedbackState.selectedRoadFeedback + 3 -> feedbackState.selectedNoiseFeedback + 4 -> feedbackState.selectedFrequencyFeedback + else -> null + } + + WalkReviewFeedbackForm( + icon = R.drawable.ic_walk_review_location, + title = title, + selectedFeedbackItem = currentSelectedFeedback, + feedbackList = eachFeedbackList[index], + onClickFeedback = { selectedFeedback -> + onClickFeedback(index, selectedFeedback) + }, + modifier = Modifier + .padding(top = 12.dp, bottom = 12.dp, start = 16.dp, end = 16.dp) + ) + } + } + + item { + HorizontalDivider( + thickness = 10.dp, + color = PawKeyTheme.colors.gray50, modifier = Modifier - .heightIn(min = 200.dp, max = 400.dp) - .padding(top = 10.dp, bottom = 24.dp, start = 16.dp, end = 16.dp) + .fillMaxWidth() + .padding(top = 12.dp, bottom = 12.dp) ) } - } - item { - HorizontalDivider( - thickness = 10.dp, - color = PawKeyTheme.colors.gray50, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 24.dp) - ) - } + if (!isSharedWalk) { + item { + Text( + text = "산책에 대한 감상을 들려주시겠어요?", + style = PawKeyTheme.typography.body16M, + color = PawKeyTheme.colors.black, + modifier = Modifier + .padding(top = 12.dp, start = 16.dp, end = 16.dp) + ) - item { - val buttonTextRes = if (isSharedWalk) { - R.string.course_review_shared_button - } else { - R.string.course_review_shared_all_button + WalkReviewTextField( + textValue = titleText, + placeHolder = "후기 제목을 입력해주세요.", + onTextChanged = { + onTitleTextChanged(it) + }, + modifier = Modifier + .padding(top = 10.dp, start = 16.dp, end = 16.dp) + .imePadding() + ) + + WalkReviewTextField( + textValue = contentText, + placeHolder = "산책 후기를 간단하게 적어주세요!", + onTextChanged = { + onContentTextChanged(it) + }, + modifier = Modifier + .heightIn(min = 200.dp, max = 400.dp) + .padding(top = 10.dp, bottom = 24.dp, start = 16.dp, end = 16.dp) + .imePadding() + ) + } } - PawkeyButton( - text = stringResource(buttonTextRes), - onClick = navigateNext, - enabled = isFormValid, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - ) + item { + HorizontalDivider( + thickness = 10.dp, + color = PawKeyTheme.colors.gray50, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp) + ) + } - if (!isSharedWalk) { - Spacer(modifier = Modifier.height(10.dp)) + item { + val buttonTextRes = if (isSharedWalk) { + R.string.course_review_shared_button + } else { + R.string.course_review_shared_all_button + } PawkeyButton( - text = stringResource(R.string.course_review_saved_button), - onClick = navigateUp, + text = stringResource(buttonTextRes), + onClick = navigateShared, enabled = isFormValid, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp), - isBackGround = true + .padding(horizontal = 16.dp) ) + + if (!isSharedWalk) { + Spacer(modifier = Modifier.height(10.dp)) + + PawkeyButton( + text = stringResource(R.string.course_review_saved_button), + onClick = navigateShared, + enabled = isFormValid, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + isBackGround = true, + isBorder = true, + ) + } } } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewFeedbackForm.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewFeedbackForm.kt index b10e923a..6eaab813 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewFeedbackForm.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewFeedbackForm.kt @@ -47,7 +47,8 @@ fun WalkReviewFeedbackForm( } FlowRow( - modifier = modifier, + modifier = Modifier + .padding(start = 16.dp, end = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewImageRow.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewImageRow.kt index acea5cb6..0a5f8150 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewImageRow.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/component/WalkReviewImageRow.kt @@ -2,6 +2,7 @@ package com.paw.key.presentation.ui.course.walkreview.component import android.net.Uri import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow @@ -26,6 +27,7 @@ fun WalkReviewImageRow ( .fillMaxWidth() .padding(top = 24.dp, bottom = 24.dp), horizontalArrangement = Arrangement.spacedBy(10.dp), + contentPadding = PaddingValues(horizontal = 16.dp) ) { items(totalCardCount) { index -> val currentImageUri = imageList.getOrNull(index) diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/navigation/WalkReviewNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/navigation/WalkReviewNavigation.kt index c8ec7e10..c96e0c49 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/navigation/WalkReviewNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/navigation/WalkReviewNavigation.kt @@ -21,12 +21,14 @@ fun NavController.navigateWalkReview( fun NavGraphBuilder.walkReviewNavGraph( navigateUp: () -> Unit, navigateNext: () -> Unit, + navigateShared : () -> Unit, snackBarHostState: SnackbarHostState, ) { composable { WalkReviewRoute( navigateUp = navigateUp, navigateNext = navigateNext, + navigateShared = navigateShared, snackBarHostState = snackBarHostState, ) } diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/state/WalkReviewContract.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/state/WalkReviewContract.kt index 7888ac8d..999d2589 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/state/WalkReviewContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/state/WalkReviewContract.kt @@ -20,7 +20,15 @@ class WalkReviewContract { val petName : String = "포비", val feedbackState: WalkReviewFeedbackState = WalkReviewFeedbackState() - ) + ){ + val isValidForm get() = title.isNotBlank() && + content.isNotBlank() && + feedbackState.selectedSafetyFeedback != null && + feedbackState.selectedFacilityFeedback != null && + feedbackState.selectedRoadFeedback != null && + feedbackState.selectedNoiseFeedback != null && + feedbackState.selectedFrequencyFeedback != null + } sealed class WalkReviewSideEffect { data class ShowSnackBar(val message: String) : WalkReviewSideEffect() diff --git a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/viewmodel/WalkReviewViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/viewmodel/WalkReviewViewModel.kt index 4ff054b3..92c7f647 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/viewmodel/WalkReviewViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/course/walkreview/viewmodel/WalkReviewViewModel.kt @@ -29,19 +29,19 @@ class WalkReviewViewModel @Inject constructor( val sideEffect : MutableSharedFlow get() = _sideEffect - val isFormValid: StateFlow = state.map { state -> - state.title.isNotBlank() && state.content.isNotBlank() && listOf( - state.feedbackState.selectedSafetyFeedback, - state.feedbackState.selectedFacilityFeedback, - state.feedbackState.selectedRoadFeedback, - state.feedbackState.selectedNoiseFeedback, - state.feedbackState.selectedFrequencyFeedback - ).any { it != null } + /*val isFormValid: StateFlow = state.map { state -> + state.title.isNotBlank() && + state.content.isNotBlank() && + state.feedbackState.selectedSafetyFeedback != null && + state.feedbackState.selectedFacilityFeedback != null && + state.feedbackState.selectedRoadFeedback != null && + state.feedbackState.selectedNoiseFeedback != null && + state.feedbackState.selectedFrequencyFeedback != null }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), false - ) + )*/ private fun handleFeedbackSelection( currentSelected: WalkReviewFeedbackData?, 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 9d622b0f..ae927ef0 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 @@ -9,6 +9,5 @@ class HomeContract { val isLocationMenuVisible: Boolean = false, val isVisible: Boolean = false, val selectedLocation: String = "", - - ) + ) } 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 83eab044..063bc10c 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 @@ -2,12 +2,14 @@ package com.paw.key.presentation.ui.login import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.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.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon @@ -26,7 +28,9 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.R import com.paw.key.core.designsystem.component.PawkeyButton +import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.util.isKeyboardOpen import com.paw.key.core.util.noRippleClickable import com.paw.key.presentation.ui.login.component.LoginTextField import com.paw.key.presentation.ui.login.viewmodel.LoginViewModel @@ -41,7 +45,7 @@ fun LoginRoute( viewModel: LoginViewModel = hiltViewModel() ) { val state by viewModel.state.collectAsStateWithLifecycle() - val isLoginFormValid by viewModel.isLoginFormValid.collectAsStateWithLifecycle() + val isLoginFormValid = viewModel.state.collectAsStateWithLifecycle().value.isLoginValid LoginScreen( paddingValues = paddingValues, @@ -59,6 +63,7 @@ fun LoginRoute( ) } + @Composable fun LoginScreen( paddingValues: PaddingValues, @@ -66,94 +71,107 @@ fun LoginScreen( navigateNext: () -> Unit, onEmailChanged: (String) -> Unit, onPasswordChanged: (String) -> Unit, - onClickIcon : () -> Unit, + onClickIcon: () -> Unit, snackBarHostState: SnackbarHostState, - email : String, - password : String, - isPasswordVisible : Boolean, - isLoginFormValid : Boolean, + email: String, + password: String, + isPasswordVisible: Boolean, + isLoginFormValid: Boolean, modifier: Modifier = Modifier, ) { - Column( + Box( modifier = modifier .fillMaxSize() .background(PawKeyTheme.colors.white1) - .padding(horizontal = 16.dp, vertical = 60.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center ) { - Spacer(modifier = Modifier.weight(1f)) - - Text( - text = "로그인", - modifier = modifier - .align(Alignment.Start) - .padding(10.dp), - color = PawKeyTheme.colors.black, - style = PawKeyTheme.typography.body14Sb + TopBar( + title = "기존 계정으로 로그인", + onBackClick = navigateUp, + modifier = Modifier.padding( + top = paddingValues.calculateTopPadding() + ).padding(top = 10.dp) ) - LoginTextField( - textValue = email, - placeHolder = "사용하실 아이디를 입력해주세요", - isPassword = true, - onTextChanged = { - onEmailChanged(it) - } - ) + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .align(Alignment.Center) + .imePadding(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "로그인", + modifier = Modifier + .align(Alignment.Start) + .padding(10.dp), + color = PawKeyTheme.colors.black, + style = PawKeyTheme.typography.body14Sb + ) - Spacer(modifier = Modifier.height(40.dp)) + LoginTextField( + textValue = email, + placeHolder = "사용하실 아이디를 입력해주세요", + isPassword = true, + onTextChanged = onEmailChanged + ) - Text( - text = "비밀번호", - modifier = modifier - .align(Alignment.Start) - .padding(10.dp), - color = PawKeyTheme.colors.black, - style = PawKeyTheme.typography.body14Sb - ) + Spacer(modifier = Modifier.height(40.dp)) - LoginTextField( - textValue = password, - placeHolder = "사용하실 비밀번호를 입력해주세요", - isPassword = isPasswordVisible, - onTextChanged = { - onPasswordChanged(it) - }, - suffix = { - Icon( - imageVector = ImageVector.vectorResource( - if (!isPasswordVisible) R.drawable.ic_eye_linear_gray_valid else R.drawable.ic_eye_linear_invalid - ), - contentDescription = null, - modifier = Modifier.noRippleClickable(onClickIcon), - tint = Color.Unspecified - ) - }, - ) + Text( + text = "비밀번호", + modifier = Modifier + .align(Alignment.Start) + .padding(10.dp), + color = PawKeyTheme.colors.black, + style = PawKeyTheme.typography.body14Sb + ) + + LoginTextField( + textValue = password, + placeHolder = "사용하실 비밀번호를 입력해주세요", + isPassword = isPasswordVisible, + onTextChanged = onPasswordChanged, + suffix = { + Icon( + imageVector = ImageVector.vectorResource( + if (!isPasswordVisible) R.drawable.ic_eye_linear_gray_valid + else R.drawable.ic_eye_linear_invalid + ), + contentDescription = null, + modifier = Modifier.noRippleClickable(onClickIcon), + tint = PawKeyTheme.colors.gray200 + ) + }, + ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(60.dp)) + } - PawkeyButton( - text = "신규 계정으로 회원가입", - onClick = navigateUp, - enabled = true, - isBackGround = true, - isBorder = false, + Column( modifier = Modifier - .fillMaxWidth() - ) + .align(Alignment.BottomCenter) + .padding(horizontal = 16.dp, vertical = 24.dp) + ) { + PawkeyButton( + text = "신규 계정으로 회원가입", + onClick = navigateUp, + enabled = true, + isBackGround = true, + isBorder = false, + modifier = Modifier.fillMaxWidth() + ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - PawkeyButton( - text = "로그인", - onClick = navigateNext, - enabled = isLoginFormValid, - modifier = Modifier - .fillMaxWidth() - .navigationBarsPadding() - ) + PawkeyButton( + text = "로그인", + onClick = navigateNext, + enabled = isLoginFormValid, + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + ) + } } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt b/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt index 80634991..c5781e95 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/login/state/LoginContract.kt @@ -9,7 +9,9 @@ class LoginContract { val password: String = "", val isPasswordVisible: Boolean = false - ) + ) { + val isLoginValid get() = email.isNotBlank() && password.isNotBlank() + } sealed class LoginSideEffect { data class ShowSnackBar(val message: String) : LoginSideEffect() 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 e71a1120..5085203a 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 @@ -22,14 +22,6 @@ class LoginViewModel @Inject constructor( val sideEffect : StateFlow get() = _sideEffect.asStateFlow() - val isLoginFormValid: StateFlow = state.map { state -> - state.email.isNotBlank() && state.password.isNotBlank() - }.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(3000), - false - ) - fun onEmailChanged(email: String) { _state.value = _state.value.copy( email = email 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 b495b801..d78b4ef6 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 @@ -91,6 +91,7 @@ fun PawKeyNavHost( walkReviewNavGraph( navigateUp = navigator::navigateUp, navigateNext = navigator::navigateDummyNext, + navigateShared = navigator::navigateArchivedDetail, snackBarHostState = snackbarHostState ) 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 0ed95387..2ad96464 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 @@ -1,9 +1,5 @@ package com.paw.key.presentation.ui.mypage.navigation -import com.paw.key.presentation.ui.mypage.SavedDetailRoute -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder @@ -11,7 +7,6 @@ import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.paw.key.core.navigation.Route import com.paw.key.presentation.ui.mypage.ArchivedDetailRoute -import com.paw.key.presentation.ui.mypage.SavedCourseRoute import kotlinx.serialization.Serializable fun NavController.navigateArchivedDetail(