diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c34ee587..c051a1c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.AppCompat" + android:screenOrientation="portrait" tools:targetApi="31"> = accountDataStore.authId override val uId: Flow = accountDataStore.uId + override val userName: Flow = accountDataStore.userName + override val nickName: Flow = accountDataStore.nickName // TODO API Call override suspend fun signUp( diff --git a/data/src/main/java/com/whyranoid/data/repository/RunningRepositoryImpl.kt b/data/src/main/java/com/whyranoid/data/repository/RunningRepositoryImpl.kt index 168d30e8..ddd5ecf0 100644 --- a/data/src/main/java/com/whyranoid/data/repository/RunningRepositoryImpl.kt +++ b/data/src/main/java/com/whyranoid/data/repository/RunningRepositoryImpl.kt @@ -19,11 +19,13 @@ class RunningRepositoryImpl(context: Context) : RunningRepository { private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) private val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000L).build() - private lateinit var locationCallback: LocationCallback + private var locationCallback: LocationCallback? = null override val userLocationState: MutableStateFlow = MutableStateFlow(UserLocation.NotTracking) + private var isTrackingUserLocation = true + override suspend fun startRunning() { } @@ -40,13 +42,16 @@ class RunningRepositoryImpl(context: Context) : RunningRepository { } override fun listenLocation() { + isTrackingUserLocation = true if (userLocationState.value is UserLocation.Tracking) return if ((runningDataManager.runningState.value is RunningState.NotRunning).not()) return locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { locationResult.lastLocation?.let { location -> - userLocationState.value = - UserLocation.Tracking(location.latitude, location.longitude) + if (isTrackingUserLocation) { + userLocationState.value = + UserLocation.Tracking(location.latitude, location.longitude) + } } ?: run { removeListener() } @@ -55,7 +60,7 @@ class RunningRepositoryImpl(context: Context) : RunningRepository { try { fusedLocationClient.requestLocationUpdates( locationRequest, - locationCallback, + requireNotNull(locationCallback), Looper.getMainLooper(), ) } catch (e: SecurityException) { @@ -66,7 +71,10 @@ class RunningRepositoryImpl(context: Context) : RunningRepository { } override fun removeListener() { - fusedLocationClient.removeLocationUpdates(locationCallback) + isTrackingUserLocation = false + userLocationState.value = UserLocation.NotTracking + fusedLocationClient.removeLocationUpdates(requireNotNull(locationCallback)) + locationCallback = null } override fun removeUserLocation() { diff --git a/domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt b/domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt index 807b876e..2117622b 100644 --- a/domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt +++ b/domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt @@ -7,6 +7,9 @@ interface AccountRepository { val authId: Flow val uId: Flow + val userName: Flow + val nickName: Flow + suspend fun signUp( authId: String, userName: String, diff --git a/domain/src/main/java/com/whyranoid/domain/util/Extensions.kt b/domain/src/main/java/com/whyranoid/domain/util/Extensions.kt new file mode 100644 index 00000000..f3e388cd --- /dev/null +++ b/domain/src/main/java/com/whyranoid/domain/util/Extensions.kt @@ -0,0 +1,3 @@ +package com.whyranoid.domain.util + +val String.Companion.EMPTY: String get() = "" diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt index fef723ef..8e87fe1c 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/AppScreen.kt @@ -40,11 +40,14 @@ import com.whyranoid.presentation.screens.Screen.Companion.bottomNavigationItems import com.whyranoid.presentation.screens.challenge.ChallengeDetailScreen import com.whyranoid.presentation.screens.challenge.ChallengeExitScreen import com.whyranoid.presentation.screens.challenge.ChallengeMainScreen -import com.whyranoid.presentation.screens.mypage.EditProfileScreen import com.whyranoid.presentation.screens.mypage.MyPageScreen import com.whyranoid.presentation.screens.mypage.addpost.AddPostScreen +import com.whyranoid.presentation.screens.mypage.editprofile.EditProfileScreen import com.whyranoid.presentation.screens.running.RunningScreen +import com.whyranoid.presentation.screens.signin.SignInScreen +import com.whyranoid.presentation.screens.splash.SplashScreen import com.whyranoid.presentation.theme.WalkieColor +import com.whyranoid.presentation.viewmodel.SplashState import com.whyranoid.presentation.viewmodel.SplashViewModel import org.koin.androidx.compose.koinViewModel @@ -87,11 +90,11 @@ fun AppScreen(startWorker: () -> Unit) { val splashState = splashViewModel.splashState.collectAsStateWithLifecycle() AppScreenContent(startWorker, navController) // TODO Splash 적용 -// when (splashState.value) { -// SplashState.InitialState -> SplashScreen() -// SplashState.SignInState -> SignInScreen { splashViewModel.finishSignIn() } -// SplashState.SignedInState -> AppScreenContent(startWorker, navController) -// } + when (splashState.value) { + SplashState.InitialState -> SplashScreen() + SplashState.SignInState -> SignInScreen { splashViewModel.finishSignIn() } + SplashState.SignedInState -> AppScreenContent(startWorker, navController) + } } @Composable diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/PostingScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/PostingScreen.kt index 75082105..07d0a1e2 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/PostingScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/addpost/PostingScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions @@ -70,27 +71,20 @@ import java.util.* @SuppressLint("SimpleDateFormat") @Composable fun PostingScreen(runningHistory: RunningHistory) { - var textVisibleState by remember { mutableStateOf(true) } + var textVisibleState by remember { mutableStateOf(TextVisibleState.WHITE) } var photoEditState by remember { mutableStateOf(false) } Column( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), + modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.Top, ) { Box( - Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp) - .padding(top = 20.dp), + Modifier.fillMaxWidth().padding(horizontal = 20.dp).padding(top = 20.dp), ) { Text( style = WalkieTypography.Title, text = "새 게시물", - modifier = Modifier - .align(Alignment.Center) - .padding(bottom = 24.dp), + modifier = Modifier.align(Alignment.Center).padding(bottom = 24.dp), ) } @@ -103,12 +97,8 @@ fun PostingScreen(runningHistory: RunningHistory) { Row( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(bottom = 12.dp) - .weight(1f) - .height(40.dp) - .clip(RoundedCornerShape(12.dp)) - .background(WalkieColor.GrayDefault), + modifier = Modifier.padding(bottom = 12.dp).weight(1f).height(40.dp) + .clip(RoundedCornerShape(12.dp)).background(WalkieColor.GrayDefault), ) { Text(text = it, style = WalkieTypography.SubTitle) } @@ -122,33 +112,28 @@ fun PostingScreen(runningHistory: RunningHistory) { Row( horizontalArrangement = Arrangement.SpaceAround, verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(horizontal = 20.dp) - .align(Alignment.End) - .wrapContentSize(), + modifier = Modifier.padding(horizontal = 20.dp).align(Alignment.End).wrapContentSize(), ) { if (photoEditState) { Text("앨범", style = WalkieTypography.SubTitle) Spacer(Modifier.weight(1f)) } Icon( - modifier = Modifier - .clickable { - textVisibleState = textVisibleState.not() + modifier = Modifier.clickable { + textVisibleState = when (textVisibleState) { + TextVisibleState.WHITE -> TextVisibleState.BLACK + TextVisibleState.BLACK -> TextVisibleState.HIDE + TextVisibleState.HIDE -> TextVisibleState.WHITE } - .size(48.dp) - .padding(12.dp), + }.size(48.dp).clip(CircleShape).padding(12.dp), painter = painterResource(id = R.drawable.ic_timer), contentDescription = "textVisible", tint = WalkieColor.GrayDefault, ) Icon( - modifier = Modifier - .clickable { - photoEditState = photoEditState.not() - } - .size(48.dp) - .padding(12.dp), + modifier = Modifier.clickable { + photoEditState = photoEditState.not() + }.size(48.dp).clip(CircleShape).padding(12.dp), painter = painterResource(id = R.drawable.ic_gallery), contentDescription = "gallery", tint = WalkieColor.GrayDefault, @@ -166,13 +151,9 @@ fun PostingScreen(runningHistory: RunningHistory) { BasicTextField( keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), - modifier = Modifier - .padding(horizontal = 20.dp) - .fillMaxWidth() - .height(92.dp) + modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth().height(92.dp) .background(Color.White) - .border(1.dp, WalkieColor.GrayDefault, RoundedCornerShape(12.dp)) - .padding(8.dp), + .border(1.dp, WalkieColor.GrayDefault, RoundedCornerShape(12.dp)).padding(8.dp), value = text, onValueChange = { text = it @@ -184,16 +165,12 @@ fun PostingScreen(runningHistory: RunningHistory) { } Box( - modifier = Modifier - .fillMaxSize() - .padding(20.dp), + modifier = Modifier.fillMaxSize().padding(20.dp), contentAlignment = Alignment.BottomCenter, ) { Button( shape = RoundedCornerShape(12.dp), - modifier = Modifier - .fillMaxWidth() - .height(48.dp), + modifier = Modifier.fillMaxWidth().height(48.dp), onClick = { if (photoEditState) { photoEditState = false @@ -213,7 +190,7 @@ fun PostingScreen(runningHistory: RunningHistory) { @Composable fun Map( runningHistory: RunningHistory, - textVisibleState: Boolean, + textVisibleState: TextVisibleState, ) { val runningHistoryUiModel = runningHistory.toRunningHistoryUiModel(LocalContext.current) @@ -261,9 +238,7 @@ fun Map( Box { NaverMap( - modifier = Modifier - .padding(horizontal = 20.dp) - .aspectRatio(1f), + modifier = Modifier.padding(horizontal = 20.dp).aspectRatio(1f), cameraPositionState = cameraPositionState, uiSettings = mapUiSettings, properties = mapProperties, @@ -289,20 +264,19 @@ fun Map( } } - Text( - text = SimpleDateFormat("yyyy.MM.dd HH:mm").format(Date(runningHistory.finishedAt)), - modifier = Modifier.padding(top = 12.dp).align(Alignment.TopCenter), - style = WalkieTypography.Body2, - ) - // 지도 하단 정보 - if (textVisibleState) { + if (textVisibleState != TextVisibleState.HIDE) { + val textColor = if (textVisibleState == TextVisibleState.WHITE) Color.White else Color.Black + + Text( + text = SimpleDateFormat("yyyy.MM.dd HH:mm").format(Date(runningHistory.finishedAt)), + modifier = Modifier.padding(top = 12.dp).align(Alignment.TopCenter), + style = WalkieTypography.Body2.copy(color = textColor), + ) + Row( - modifier = Modifier - .padding(horizontal = 20.dp) - .fillMaxWidth() - .align(Alignment.BottomCenter) - .padding(bottom = 12.dp) + modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth() + .align(Alignment.BottomCenter).padding(bottom = 12.dp) .padding(horizontal = 20.dp), horizontalArrangement = Arrangement.SpaceBetween, ) { @@ -313,10 +287,14 @@ fun Map( ).forEach { Text( it, - style = WalkieTypography.Title, + style = WalkieTypography.Title.copy(color = textColor), ) } } } } } + +enum class TextVisibleState { + WHITE, BLACK, HIDE +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/EditProfileScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/editprofile/EditProfileScreen.kt similarity index 85% rename from presentation/src/main/java/com/whyranoid/presentation/screens/mypage/EditProfileScreen.kt rename to presentation/src/main/java/com/whyranoid/presentation/screens/mypage/editprofile/EditProfileScreen.kt index 0bea76b3..62f3ee85 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/EditProfileScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/editprofile/EditProfileScreen.kt @@ -1,4 +1,4 @@ -package com.whyranoid.presentation.screens.mypage +package com.whyranoid.presentation.screens.mypage.editprofile import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -22,8 +22,10 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -32,19 +34,29 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController +import com.whyranoid.domain.util.EMPTY import com.whyranoid.presentation.reusable.CheckableCustomTextField +import com.whyranoid.presentation.theme.WalkieColor import com.whyranoid.presentation.theme.WalkieTypography +import org.koin.androidx.compose.koinViewModel @Composable -fun EditProfileScreen(name: String = "", nick: String = "", navController: NavController) { - EditProfileContent(name = name, nick = nick) { +fun EditProfileScreen(navController: NavController) { + val viewModel = koinViewModel() + val name = viewModel.name.collectAsStateWithLifecycle(initialValue = String.EMPTY) + val nick = viewModel.nick.collectAsStateWithLifecycle(initialValue = String.EMPTY) + + EditProfileContent(name = name.value.orEmpty(), nick = nick.value.orEmpty()) { navController.popBackStack() } } +// TODO API 연결 @Composable fun EditProfileContent(name: String, nick: String, onCloseClicked: () -> Unit) { Column( @@ -118,17 +130,14 @@ fun EditProfileContent(name: String, nick: String, onCloseClicked: () -> Unit) { Text( modifier = Modifier.align(Alignment.Start), - text = "이름 변경", + text = "이름", fontWeight = WalkieTypography.Title.fontWeight, ) Spacer(modifier = Modifier.height(16.dp)) - var nameText by rememberSaveable { mutableStateOf(name) } - - CheckableCustomTextField( - text = nameText, - onTextChanged = { text -> nameText = text }, + Box( + contentAlignment = Alignment.CenterStart, modifier = Modifier .fillMaxWidth() .height(34.dp) @@ -136,15 +145,13 @@ fun EditProfileContent(name: String, nick: String, onCloseClicked: () -> Unit) { Color(0xFFEEEEEE), RoundedCornerShape(10.dp), ), - trailingIcon = { - Icon( - imageVector = Icons.Filled.Edit, - contentDescription = "Edit Icon", - tint = Color(0xFF989898), - ) - }, - placeholderText = "이름", - ) + ) { + Text( + modifier = Modifier.padding(start = 8.dp), + text = name, + style = TextStyle.Default.copy(color = WalkieColor.GrayDefault), + ) + } Spacer(modifier = Modifier.height(20.dp)) @@ -156,9 +163,12 @@ fun EditProfileContent(name: String, nick: String, onCloseClicked: () -> Unit) { Spacer(modifier = Modifier.height(16.dp)) - // TODO 임시로 만든 변수, viewModel, uiState로 관리하도록 변경 필요(139, 171 line) var nickCheckState: Boolean? by rememberSaveable { mutableStateOf(null) } - var nickName by rememberSaveable { mutableStateOf(nick) } + var nickName by remember { mutableStateOf(nick) } + + LaunchedEffect(key1 = nick) { + nickName = nick + } CheckableCustomTextField( text = nickName, @@ -177,7 +187,6 @@ fun EditProfileContent(name: String, nick: String, onCloseClicked: () -> Unit) { tint = Color(0xFF989898), ) }, - placeholderText = "닉네임", checkButton = { text -> Text( text = "확인", diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/editprofile/EditProfileViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/editprofile/EditProfileViewModel.kt new file mode 100644 index 00000000..df97383e --- /dev/null +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/mypage/editprofile/EditProfileViewModel.kt @@ -0,0 +1,11 @@ +package com.whyranoid.presentation.screens.mypage.editprofile + +import androidx.lifecycle.ViewModel +import com.whyranoid.domain.repository.AccountRepository + +class EditProfileViewModel( + private val accountRepository: AccountRepository, +) : ViewModel() { + val name = accountRepository.userName + val nick = accountRepository.nickName +} diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt index 1d55eb13..d64df597 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/running/RunningScreen.kt @@ -295,6 +295,8 @@ fun RunningMapScreen( LocationOverlay( position = LatLng(location), icon = OverlayImage.fromResource(R.drawable.ic_running_screen_selected), + circleOutlineWidth = 2.dp, + circleOutlineColor = Color.White, ) } } @@ -305,6 +307,8 @@ fun RunningMapScreen( LocationOverlay( position = LatLng(location), icon = OverlayImage.fromResource(R.drawable.ic_running_screen_selected), + circleOutlineWidth = 2.dp, + circleOutlineColor = Color.White, ) } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt index 34217cdb..1ab6d47f 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInUserNameScreen.kt @@ -136,7 +136,7 @@ fun SignInUserNameScreen(onSuccess: () -> Unit) { viewModel.setUserNameState( userNameState.copy( year = year, - month = month, + month = month + 1, day = day, ), ) diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/RunningViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/RunningViewModel.kt index d6717943..41657d5c 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/RunningViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/RunningViewModel.kt @@ -53,6 +53,8 @@ class RunningViewModel( private val runningHistoryRepository: RunningHistoryRepository, ) : ViewModel(), ContainerHost { + private var savedHistory: List>? = null + private val runningDataManager = RunningDataManager.getInstance() var startWorker: (() -> Unit)? = null @@ -73,9 +75,6 @@ class RunningViewModel( } viewModelScope.launch { runningDataManager.runningState.collect { runningState -> - if (runningState.runningData.lastLocation != null) { - runningRepository.removeUserLocation() - } intent { reduce { val runningInfo = @@ -194,6 +193,8 @@ class RunningViewModel( } fun saveHistory(bitmap: Bitmap, finishData: RunningFinishData) { + if (savedHistory == finishData.runningPositionList) return + savedHistory = finishData.runningPositionList intent { runningHistoryRepository.saveRunningHistory( RunningHistory(