diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 60450c72..15367bed 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { minSdk = 26 targetSdk = 33 versionCode = 1 - versionName = "1.0.15" + versionName = "1.0.16" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt index 7111fb43..c976bc9d 100644 --- a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt +++ b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt @@ -144,7 +144,7 @@ val viewModelModule = viewModel { AddPostViewModel(get()) } viewModel { SearchFriendViewModel(get(), get(), get()) } viewModel { DialogViewModel(get(), get(), get(), get(), get(), get()) } - viewModel { CommunityScreenViewModel(get(), get(), get()) } + viewModel { CommunityScreenViewModel(get(), get(), get(), get(), get()) } viewModel { FollowingViewModel(get(), get(), get(), get(), get(), get()) } viewModel { SettingViewModel(get(), get()) } viewModel { TotalBadgeViewModel(get(), get()) } diff --git a/app/src/main/java/com/whyranoid/walkie/MainActivity.kt b/app/src/main/java/com/whyranoid/walkie/MainActivity.kt index 480379c4..e7812ffa 100644 --- a/app/src/main/java/com/whyranoid/walkie/MainActivity.kt +++ b/app/src/main/java/com/whyranoid/walkie/MainActivity.kt @@ -1,14 +1,20 @@ package com.whyranoid.walkie +import android.content.Intent import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.annotation.RequiresApi +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewModelScope import com.whyranoid.presentation.screens.AppScreen +import com.whyranoid.presentation.screens.setting.SettingViewModel import com.whyranoid.presentation.theme.WalkieTheme import com.whyranoid.walkie.walkiedialog.AppManageDialog +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -23,5 +29,18 @@ class MainActivity : ComponentActivity() { AppScreen { startWorker(this) } } } + + observeRestart() + } + + private fun observeRestart() { + lifecycleScope.launch { + SettingViewModel.appRestartEvent.collectLatest { isRestart -> + if (isRestart == true) { + startActivity(Intent(this@MainActivity, MainActivity::class.java)) + finish() + } + } + } } } diff --git a/data/src/main/java/com/whyranoid/data/API.kt b/data/src/main/java/com/whyranoid/data/API.kt index a900d3a6..82ba7218 100644 --- a/data/src/main/java/com/whyranoid/data/API.kt +++ b/data/src/main/java/com/whyranoid/data/API.kt @@ -7,6 +7,8 @@ object API { const val SIGN_UP = "api/walkies/signup" + const val LEAVE = "api/walkies/leave" + const val FOLLOW = "api/follow/follow" const val WALKING_FOLLOWING = "api/follow/{uid}/walking-followings" diff --git a/data/src/main/java/com/whyranoid/data/datasource/account/AccountDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/datasource/account/AccountDataSourceImpl.kt index 3afda1ff..8d444385 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/account/AccountDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/account/AccountDataSourceImpl.kt @@ -87,4 +87,10 @@ class AccountDataSourceImpl(private val accountService: AccountService) : Accoun accountService.getMyInfo(walkieId).getResult { it.toUserInfo() } } } + + override suspend fun leave(walkieId: Long): Result { + return kotlin.runCatching { + accountService.leave(walkieId).getResult{ } + } + } } diff --git a/data/src/main/java/com/whyranoid/data/datasource/account/AccountService.kt b/data/src/main/java/com/whyranoid/data/datasource/account/AccountService.kt index d9af3b92..d8a21ec1 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/account/AccountService.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/account/AccountService.kt @@ -2,6 +2,7 @@ package com.whyranoid.data.datasource.account import com.whyranoid.data.API import com.whyranoid.data.model.account.ChangeMyInfoResponse +import com.whyranoid.data.model.account.LeaveResponse import com.whyranoid.data.model.account.LoginDataResponse import com.whyranoid.data.model.account.NickCheckResponse import com.whyranoid.data.model.account.SignUpRequest @@ -10,6 +11,7 @@ import com.whyranoid.data.model.account.UserInfoResponse import okhttp3.MultipartBody import retrofit2.Response import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Part @@ -42,4 +44,9 @@ interface AccountService { suspend fun getMyInfo( @Query("walkieId") id: Long ): Response + + @DELETE(API.LEAVE) + suspend fun leave( + @Query("walkieId") id: Long + ): Response } diff --git a/data/src/main/java/com/whyranoid/data/model/account/LeaveResponse.kt b/data/src/main/java/com/whyranoid/data/model/account/LeaveResponse.kt new file mode 100644 index 00000000..0e9977d1 --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/model/account/LeaveResponse.kt @@ -0,0 +1,6 @@ +package com.whyranoid.data.model.account + +data class LeaveResponse( + val status: Int, + val message: String, +) diff --git a/data/src/main/java/com/whyranoid/data/repository/AccountRepositoryImpl.kt b/data/src/main/java/com/whyranoid/data/repository/AccountRepositoryImpl.kt index 613feb2e..6cfeb901 100644 --- a/data/src/main/java/com/whyranoid/data/repository/AccountRepositoryImpl.kt +++ b/data/src/main/java/com/whyranoid/data/repository/AccountRepositoryImpl.kt @@ -38,7 +38,14 @@ class AccountRepositoryImpl( agreeSubscription: Boolean, ): Result { return kotlin.runCatching { - accountDataSource.signUp(userName, nickName, profileUrl, authId, agreeGps, agreeSubscription) + accountDataSource.signUp( + userName, + nickName, + profileUrl, + authId, + agreeGps, + agreeSubscription + ) .onSuccess { uid -> accountDataStore.updateUId(uid) accountDataStore.updateAuthId(authId) @@ -107,4 +114,10 @@ class AccountRepositoryImpl( override suspend fun getUserInfo(walkieId: Long): Result { return accountDataSource.getUserInfo(walkieId) } + + override suspend fun leave(walkieId: Long): Result { + return kotlin.runCatching { + accountDataSource.leave(walkieId) + } + } } diff --git a/domain/src/main/java/com/whyranoid/domain/datasource/AccountDataSource.kt b/domain/src/main/java/com/whyranoid/domain/datasource/AccountDataSource.kt index 8b8dd4be..a5660296 100644 --- a/domain/src/main/java/com/whyranoid/domain/datasource/AccountDataSource.kt +++ b/domain/src/main/java/com/whyranoid/domain/datasource/AccountDataSource.kt @@ -20,4 +20,6 @@ interface AccountDataSource { suspend fun changeMyInfo(walkieId: Long, nickName: String, profileUrl: String?): Result suspend fun getUserInfo(walkieId: Long): Result + + suspend fun leave(walkieId: Long): Result } 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 e89907e2..a15acb31 100644 --- a/domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt +++ b/domain/src/main/java/com/whyranoid/domain/repository/AccountRepository.kt @@ -36,4 +36,6 @@ interface AccountRepository { suspend fun changeMyInfo(walkieId: Long, nickName: String, profileUrl: String?): Result suspend fun getUserInfo(walkieId: Long): Result + + suspend fun leave(walkieId: Long): Result } diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt index c1e30906..879a5814 100644 --- a/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt +++ b/domain/src/main/java/com/whyranoid/domain/usecase/GetFollowingsPostsUseCase.kt @@ -11,6 +11,6 @@ class GetFollowingsPostsUseCase( ) { suspend operator fun invoke(isEveryPost: Boolean): Result> { val myUid = requireNotNull(accountRepository.walkieId.first()) - return postRepository.getMyFollowingsPost(myUid) + return if (isEveryPost) postRepository.getEveryPost(myUid) else postRepository.getMyFollowingsPost(myUid) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/component/community/RunningFollowerItem.kt b/presentation/src/main/java/com/whyranoid/presentation/component/community/RunningFollowerItem.kt index 426af6dc..d6eadf5b 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/component/community/RunningFollowerItem.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/component/community/RunningFollowerItem.kt @@ -44,7 +44,7 @@ fun RunningFollowerItem( contentAlignment = Alignment.Center, ) { AsyncImage( - model = "https://picsum.photos/250/250 ", + model = user.imageUrl, contentDescription = "달리고 있는 친구의 프로필 이미지", modifier = Modifier .size(65.dp) @@ -57,7 +57,7 @@ fun RunningFollowerItem( if (isDisplayName) { Spacer(modifier = Modifier.size(6.dp)) - Text(text = "내 기록") + Text(text = "내 기록") // 내꺼면 내기록 아니면 사람 이름 } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/component/running/RunningFollowerItemWithLikable.kt b/presentation/src/main/java/com/whyranoid/presentation/component/running/RunningFollowerItemWithLikable.kt index 48029a33..ef3e75c5 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/component/running/RunningFollowerItemWithLikable.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/component/running/RunningFollowerItemWithLikable.kt @@ -12,6 +12,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material3.Icon import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -34,6 +38,8 @@ fun RunningFollowerItemWithLikable( isDisplayName: Boolean = false, isLiked: Boolean = false, ) { + var isLikedStatus by remember { mutableStateOf(isLiked) } + Box( modifier = Modifier.wrapContentSize(), contentAlignment = Alignment.TopEnd, @@ -50,6 +56,7 @@ fun RunningFollowerItemWithLikable( .clip(CircleShape) .clickable { onClick.invoke(user.uid) + isLikedStatus = isLikedStatus.not() } .size(48.dp) .padding(4.dp) @@ -62,8 +69,10 @@ fun RunningFollowerItemWithLikable( ) { Icon( Icons.Default.Favorite, - tint = if (isLiked) WalkieColor.Primary else WalkieColor.GrayBorder, - modifier = Modifier.size(20.dp).align(Alignment.Center), + tint = if (isLikedStatus) WalkieColor.Primary else WalkieColor.GrayBorder, + modifier = Modifier + .size(20.dp) + .align(Alignment.Center), contentDescription = "like image", ) } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt index 1fafc57a..41f8311c 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/CommunityScreen.kt @@ -24,10 +24,12 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -47,6 +49,10 @@ fun CommunityScreen(navController: NavController) { val viewModel = koinViewModel() val state by viewModel.collectAsState() + LaunchedEffect(LocalLifecycleOwner.current) { + viewModel.getRunningFollowingsState() + } + Scaffold( topBar = { WalkieTopBar( @@ -113,8 +119,24 @@ fun CommunityScreen(navController: NavController) { modifier = Modifier.padding(it) ) { LazyRow { - repeat(10) { - item { RunningFollowerItemWithLikable(isDisplayName = true) } + state.runningFollowerState.getDataOrNull()?.let { (running, notRunning) -> + items(running.size) { + RunningFollowerItemWithLikable( + user = running[it].user, + onClickProfile = { user -> + navController.navigate("userPage/${user.uid}/${user.nickname}/${true}") + }, + onClick = viewModel::sendLike, + circleBorderColor = WalkieColor.Primary, + isLiked = running[it].isLiked, + ) + } + items(notRunning.size) { + RunningFollowerItemWithLikable( + user = notRunning[it], + circleBorderColor = WalkieColor.GrayBorder, + ) + } } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingScreen.kt index 66effc2e..21ea5317 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingScreen.kt @@ -1,5 +1,6 @@ package com.whyranoid.presentation.screens.setting +import android.app.Activity import android.content.Intent import android.net.Uri import androidx.annotation.StringRes @@ -41,6 +42,7 @@ import coil.compose.AsyncImage import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import com.whyranoid.presentation.R import com.whyranoid.presentation.reusable.MenuItem +import com.whyranoid.presentation.reusable.SingleToast import com.whyranoid.presentation.screens.Screen import com.whyranoid.presentation.screens.mypage.editprofile.UserInfoUiState import com.whyranoid.presentation.theme.SystemColor @@ -55,6 +57,7 @@ fun SettingsScreen(navHostController: NavHostController) { val user = viewModel.userInfoUiState.collectAsState() val scrollState = rememberScrollState() + val context = LocalContext.current user.value?.let { Column( @@ -68,6 +71,16 @@ fun SettingsScreen(navHostController: NavHostController) { SettingsList( navigateToInAppBrowser = { url -> navHostController.navigate(Screen.WebViewScreen.createRoute(url)) + }, + onClickSignOut = { + viewModel.signOutFromGoogle(context) { + SingleToast.show(context, "로그아웃 되었습니다.") + } + }, + onClickLeave = { + viewModel.revokeGoogleAccess(context) { + SingleToast.show(context, "계정 탈퇴가 완료되었습니다.") + } } ) } @@ -164,6 +177,8 @@ fun ProfileSection( @Composable fun SettingsList( navigateToInAppBrowser: (url: String) -> Unit = {}, + onClickSignOut: () -> Unit = {}, + onClickLeave: () -> Unit = {}, ) { Spacer( modifier = Modifier @@ -234,7 +249,9 @@ fun SettingsList( MenuItem( text = R.string.logout, icon = null - ) + ) { + onClickSignOut() + } Spacer( modifier = Modifier @@ -246,7 +263,9 @@ fun SettingsList( MenuItem( text = R.string.delete_account, icon = null - ) + ) { + onClickLeave() + } } @Composable diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingViewModel.kt index a7bcec25..5d5067e6 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/setting/SettingViewModel.kt @@ -1,12 +1,19 @@ package com.whyranoid.presentation.screens.setting +import android.content.Context +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.whyranoid.domain.repository.AccountRepository import com.whyranoid.domain.usecase.GetMyUidUseCase import com.whyranoid.presentation.screens.mypage.editprofile.UserInfoUiState +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 @@ -14,10 +21,15 @@ import kotlinx.coroutines.launch class SettingViewModel( private val accountRepository: AccountRepository, getMyUidUseCase: GetMyUidUseCase, -): ViewModel() { +) : ViewModel() { private val _userInfoUiState = MutableStateFlow(null) val userInfoUiState: StateFlow = _userInfoUiState.asStateFlow() + companion object { + private val _appRestartEvent = MutableSharedFlow() + val appRestartEvent: SharedFlow = _appRestartEvent.asSharedFlow() + } + init { viewModelScope.launch { val uid = getMyUidUseCase().getOrNull() ?: return@launch @@ -34,4 +46,67 @@ class SettingViewModel( } } } + + fun signOutFromGoogle( + context: Context, + onSignOutComplete: () -> Unit + ) { + val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestEmail() + .requestProfile() + .requestId() + .build() + + val googleSignInClient = GoogleSignIn.getClient(context, gso) + + googleSignInClient.signOut().addOnCompleteListener { task -> + if (task.isSuccessful) { + // 로그아웃 성공 시 처리 + onSignOutComplete() + viewModelScope.launch { + accountRepository.singOut() + _appRestartEvent.emit(true) + } + } else { + // 로그아웃 실패 시 처리 + Log.e("GoogleSignOut", "Sign out failed", task.exception) + } + } + } + + fun revokeGoogleAccess( + context: Context, + onRevokeComplete: () -> Unit, + ) { + val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestEmail() + .requestProfile() + .requestId() + .build() + + val googleSignInClient = GoogleSignIn.getClient(context, gso) + + googleSignInClient.revokeAccess().addOnCompleteListener { task -> + if (task.isSuccessful) { + googleSignInClient.signOut().addOnCompleteListener { signOutTask -> + if (signOutTask.isSuccessful) { + // 3. 클라이언트 초기화 + googleSignInClient.silentSignIn().addOnCompleteListener { silentTask -> + // 캐시 완전 제거 + // 연결 해제 성공 시 처리 + onRevokeComplete() + viewModelScope.launch { + accountRepository.leave(accountRepository.getUID()) + accountRepository.singOut() + _appRestartEvent.emit(true) + } + } + } + } + } else { + // 연결 해제 실패 시 처리 + Log.e("GoogleRevokeAccess", "Revoke access failed", task.exception) + } + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInAgreeScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInAgreeScreen.kt index 0929c07f..7ab727e4 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInAgreeScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInAgreeScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button @@ -46,6 +47,7 @@ fun SignInAgreeScreen( modifier = modifier .fillMaxSize() .background(Color.White) + .systemBarsPadding() .padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInInfoScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInInfoScreen.kt index db6f8a23..1a196ed9 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInInfoScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/signin/SignInInfoScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape @@ -48,7 +49,10 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { Surface(modifier = Modifier.background(Color.White)) { Column( - modifier = Modifier.fillMaxSize().padding(20.dp), + modifier = Modifier + .fillMaxSize() + .systemBarsPadding() + .padding(20.dp), horizontalAlignment = Alignment.Start, ) { Text("알려주세요.", style = WalkieTypography.Title, modifier = Modifier.padding(top = 68.dp)) @@ -59,7 +63,10 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { modifier = Modifier.padding(top = 40.dp), ) Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 10.dp), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(top = 10.dp), horizontalArrangement = Arrangement.SpaceBetween, ) { OutlinedButton( @@ -69,7 +76,9 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { color = if (infoState.sex == Sex.MALE) WalkieColor.Primary else WalkieColor.GrayDefault, ), onClick = { viewModel.setInfoState(infoState.copy(sex = Sex.MALE)) }, - modifier = Modifier.weight(1f).height(40.dp), + modifier = Modifier + .weight(1f) + .height(40.dp), colors = buttonColors( backgroundColor = if (infoState.sex == Sex.MALE) { WalkieColor.Primary.copy( @@ -94,7 +103,9 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { color = if (infoState.sex == Sex.FEMALE) WalkieColor.Primary else WalkieColor.GrayDefault, ), onClick = { viewModel.setInfoState(infoState.copy(sex = Sex.FEMALE)) }, - modifier = Modifier.weight(1f).height(40.dp), + modifier = Modifier + .weight(1f) + .height(40.dp), colors = buttonColors( backgroundColor = if (infoState.sex == Sex.FEMALE) { WalkieColor.Primary.copy( @@ -118,11 +129,15 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { modifier = Modifier.padding(top = 20.dp), ) BasicTextField( - modifier = Modifier.padding(top = 10.dp).fillMaxWidth().height(40.dp).border( - width = 1.dp, - color = WalkieColor.GrayDefault, - shape = RoundedCornerShape(12.dp), - ), + modifier = Modifier + .padding(top = 10.dp) + .fillMaxWidth() + .height(40.dp) + .border( + width = 1.dp, + color = WalkieColor.GrayDefault, + shape = RoundedCornerShape(12.dp), + ), value = infoState.height?.let { it.toString() } ?: "", onValueChange = { text -> text.toIntOrNull()?.let { height -> @@ -147,11 +162,15 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { modifier = Modifier.padding(top = 20.dp), ) BasicTextField( - modifier = Modifier.padding(top = 10.dp).fillMaxWidth().height(40.dp).border( - width = 1.dp, - color = WalkieColor.GrayDefault, - shape = RoundedCornerShape(12.dp), - ), + modifier = Modifier + .padding(top = 10.dp) + .fillMaxWidth() + .height(40.dp) + .border( + width = 1.dp, + color = WalkieColor.GrayDefault, + shape = RoundedCornerShape(12.dp), + ), value = infoState.weight?.let { it.toString() } ?: "", onValueChange = { text -> text.toIntOrNull()?.let { weight -> @@ -176,7 +195,9 @@ fun SignInInfoScreen(onSuccess: () -> Unit) { val enable = infoState.sex != null && infoState.height != null && infoState.weight != null Button( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), onClick = { onSuccess() }, diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt index b7f294b3..daf2a353 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/CommunityScreenViewModel.kt @@ -1,12 +1,18 @@ package com.whyranoid.presentation.viewmodel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.whyranoid.domain.model.post.Post import com.whyranoid.domain.model.user.User import com.whyranoid.domain.usecase.GetFollowingsPostsUseCase import com.whyranoid.domain.usecase.GetMyFollowingUseCase import com.whyranoid.domain.usecase.LikePostUseCase +import com.whyranoid.domain.usecase.running.GetRunningFollowerUseCase +import com.whyranoid.domain.usecase.running.SendLikeUseCase import com.whyranoid.presentation.model.UiState +import com.whyranoid.presentation.model.running.RunningFollower +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.reduce @@ -18,12 +24,15 @@ data class CommunityScreenState( val posts: UiState> = UiState.Idle, val following: UiState> = UiState.Idle, val isEveryPost: UiState = UiState.Success(true), + val runningFollowerState: UiState, List>> = UiState.Idle, ) class CommunityScreenViewModel( private val getMyFollowingUseCase: GetMyFollowingUseCase, private val getFollowingsPostsUseCase: GetFollowingsPostsUseCase, private val likePostUseCase: LikePostUseCase, + private val sendLikeUseCase: SendLikeUseCase, + private val getRunningFollowerUseCase: GetRunningFollowerUseCase, ) : ViewModel(), ContainerHost { override val container = @@ -52,7 +61,7 @@ class CommunityScreenViewModel( fun getPosts() = intent { reduce { - state.copy(posts = UiState.Loading) + state.copy(posts = UiState.Loading) } val isEveryPost = state.isEveryPost.getDataOrNull() ?: true val result = getFollowingsPostsUseCase(isEveryPost) @@ -61,7 +70,7 @@ class CommunityScreenViewModel( state.copy( posts = UiState.Success( (state.posts.getDataOrNull() ?: emptyList()) + - posts, + posts, ), ) } @@ -93,4 +102,54 @@ class CommunityScreenViewModel( // TODO: Error handling } } + + fun sendLike(receiverId: Long) { + viewModelScope.launch { + sendLikeUseCase(receiverId).onSuccess { + intent { + val lists = + state.runningFollowerState.getDataOrNull() ?: Pair(listOf(), listOf()) + reduce { + state.copy( + runningFollowerState = UiState.Success( + Pair( + lists.first.map { it.copy(isLiked = it.user.uid == receiverId) }, + lists.second, + ), + ), + ) + } + } + } + } + } + + fun getRunningFollowingsState() { + viewModelScope.launch { + getRunningFollowerUseCase().distinctUntilChanged().collect { + intent { + val likedFollowings = + state.runningFollowerState.getDataOrNull()?.first?.associateBy( + { it.user.uid }, + { it.isLiked }, + ) + reduce { + state.copy( + runningFollowerState = UiState.Success( + Pair( + it.first.map { u -> + RunningFollower( + u, + likedFollowings?.get(u.uid) ?: false, + ) + }, + it.second, + ), + ), + ) + } + } + } + } + } }