diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 82ec8662..ad8bada4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -125,4 +125,12 @@ dependencies { implementation(platform("com.google.firebase:firebase-bom:32.7.0")) implementation("com.google.firebase:firebase-crashlytics") implementation("com.google.firebase:firebase-analytics") + + // orbit + // TODO: change To Core For Compose Multiplatform + val orbitVersion = "4.6.1" + implementation("org.orbit-mvi:orbit-viewmodel:$orbitVersion") + implementation("org.orbit-mvi:orbit-compose:$orbitVersion") + // Tests + testImplementation("org.orbit-mvi:orbit-test:$orbitVersion") } diff --git a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt index 11d27013..d8e970e8 100644 --- a/app/src/main/java/com/whyranoid/walkie/KoinModules.kt +++ b/app/src/main/java/com/whyranoid/walkie/KoinModules.kt @@ -49,6 +49,7 @@ import com.whyranoid.domain.repository.PostRepository import com.whyranoid.domain.repository.RunningHistoryRepository import com.whyranoid.domain.repository.RunningRepository import com.whyranoid.domain.repository.UserRepository +import com.whyranoid.domain.usecase.ChangeChallengeStatusUseCase import com.whyranoid.domain.usecase.GetChallengeDetailUseCase import com.whyranoid.domain.usecase.GetChallengePreviewsByTypeUseCase import com.whyranoid.domain.usecase.GetChallengingPreviewsUseCase @@ -105,6 +106,7 @@ import okhttp3.OkHttpClient import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory @@ -112,22 +114,22 @@ import java.util.concurrent.TimeUnit val viewModelModule = module { - single { ChallengeMainViewModel(get(), get(), get(), get(), get()) } - single { ChallengeDetailViewModel(get(), get()) } - single { ChallengeExitViewModel(get()) } - factory { UserPageViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get()) } - factory { RunningViewModel(get(), get(), get(), get(), get(), get()) } - factory { RunningEditViewModel() } - factory { SplashViewModel(get()) } - factory { SignInViewModel(get()) } - factory { SelectHistoryViewModel(get()) } - factory { EditProfileViewModel(get()) } - factory { AddPostViewModel(get()) } - factory { SearchFriendViewModel(get(), get(), get()) } - factory { DialogViewModel(get(), get(), get(), get(), get(), get()) } - factory { CommunityScreenViewModel(get(), get(), get()) } - factory { FollowingViewModel(get(), get(), get(), get(), get(), get()) } - factory { SettingViewModel(get(), get()) } + viewModel { ChallengeMainViewModel(get(), get(), get(), get(), get()) } + viewModel { ChallengeDetailViewModel(get(), get()) } + viewModel { ChallengeExitViewModel(get(), get()) } + viewModel { UserPageViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get()) } + viewModel { RunningViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { RunningEditViewModel() } + viewModel { SplashViewModel(get()) } + viewModel { SignInViewModel(get()) } + viewModel { SelectHistoryViewModel(get()) } + viewModel { EditProfileViewModel(get()) } + viewModel { AddPostViewModel(get()) } + viewModel { SearchFriendViewModel(get(), get(), get()) } + viewModel { DialogViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { CommunityScreenViewModel(get(), get(), get()) } + viewModel { FollowingViewModel(get(), get(), get(), get(), get(), get()) } + viewModel { SettingViewModel(get(), get()) } } val repositoryModule = @@ -193,6 +195,7 @@ val useCaseModule = single { GetMyFollowingUseCase(get(), get()) } single { SendCommentUseCase(get(), get()) } single { GetUserUseCase(get()) } + single { ChangeChallengeStatusUseCase(get(), get())} } val databaseModule = diff --git a/data/src/main/java/com/whyranoid/data/API.kt b/data/src/main/java/com/whyranoid/data/API.kt index dae43999..9a2061b1 100644 --- a/data/src/main/java/com/whyranoid/data/API.kt +++ b/data/src/main/java/com/whyranoid/data/API.kt @@ -45,6 +45,8 @@ object API { const val CHALLENGE_START = "api/challenge/challenge-detail/start" + const val CHALLENGE_CHANGE_STATUS = "api/challenge/challenge-detail/update-status" + object WalkingControl { const val RUNNING_START = "api/walk/start" diff --git a/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeDataSourceImpl.kt index 8947b816..70e2c6ae 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeDataSourceImpl.kt @@ -1,7 +1,7 @@ package com.whyranoid.data.datasource.challenge -import android.util.Log import com.whyranoid.data.getResult +import com.whyranoid.data.model.challenge.request.ChallengeChangeStatusRequest import com.whyranoid.data.model.challenge.request.ChallengeStartRequest import com.whyranoid.domain.datasource.ChallengeDataSource import com.whyranoid.domain.model.challenge.Badge @@ -71,4 +71,10 @@ class ChallengeDataSourceImpl( challengeService.startChallenge(ChallengeStartRequest(uid, challengeId)) } } + + override suspend fun changeChallengeStatus(challengeId: Int, status: String, walkieId: Int): Result { + return runCatching { + challengeService.changeChallengeStatus(ChallengeChangeStatusRequest(challengeId, status, walkieId)) + } + } } diff --git a/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeService.kt b/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeService.kt index df34ac36..75e7b06d 100644 --- a/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeService.kt +++ b/data/src/main/java/com/whyranoid/data/datasource/challenge/ChallengeService.kt @@ -5,6 +5,7 @@ import com.whyranoid.data.model.StatusWithMessage import com.whyranoid.data.model.challenge.BadgeResponse import com.whyranoid.data.model.challenge.ChallengeDetailResponse import com.whyranoid.data.model.challenge.ChallengePreviewResponse +import com.whyranoid.data.model.challenge.request.ChallengeChangeStatusRequest import com.whyranoid.data.model.challenge.request.ChallengeStartRequest import retrofit2.Response import retrofit2.http.Body @@ -46,4 +47,9 @@ interface ChallengeService { suspend fun getBadgeList( @Query("walkieId") uid: Long ): Response> + + @POST(API.CHALLENGE_CHANGE_STATUS) + suspend fun changeChallengeStatus( + @Body changeChallengeStatusRequest: ChallengeChangeStatusRequest + ) : Response } \ No newline at end of file diff --git a/data/src/main/java/com/whyranoid/data/model/challenge/request/ChallengeChangeStatusRequest.kt b/data/src/main/java/com/whyranoid/data/model/challenge/request/ChallengeChangeStatusRequest.kt new file mode 100644 index 00000000..8dab83b3 --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/model/challenge/request/ChallengeChangeStatusRequest.kt @@ -0,0 +1,7 @@ +package com.whyranoid.data.model.challenge.request + +data class ChallengeChangeStatusRequest( + val challengeId: Int, + val status: String, + val walkieId: Int +) \ No newline at end of file diff --git a/data/src/main/java/com/whyranoid/data/repository/ChallengeRepositoryImpl.kt b/data/src/main/java/com/whyranoid/data/repository/ChallengeRepositoryImpl.kt index 9d9f313a..2035f8b7 100644 --- a/data/src/main/java/com/whyranoid/data/repository/ChallengeRepositoryImpl.kt +++ b/data/src/main/java/com/whyranoid/data/repository/ChallengeRepositoryImpl.kt @@ -37,4 +37,8 @@ class ChallengeRepositoryImpl( override suspend fun startChallenge(uid: Int, challengeId: Int): Result { return challengeDataSource.startChallenge(uid, challengeId) } + + override suspend fun changeChallengeStatus(challengeId: Int, status: String, walkieId: Int): Result { + return challengeDataSource.changeChallengeStatus(challengeId, status, walkieId) + } } diff --git a/domain/src/main/java/com/whyranoid/domain/datasource/ChallengeDataSource.kt b/domain/src/main/java/com/whyranoid/domain/datasource/ChallengeDataSource.kt index ec208d5f..884f7a63 100644 --- a/domain/src/main/java/com/whyranoid/domain/datasource/ChallengeDataSource.kt +++ b/domain/src/main/java/com/whyranoid/domain/datasource/ChallengeDataSource.kt @@ -20,4 +20,6 @@ interface ChallengeDataSource { suspend fun getUserBadges(uid: Long): Result> suspend fun startChallenge(uid: Int, challengeId: Int): Result + + suspend fun changeChallengeStatus(challengeId: Int, status: String, walkieId: Int): Result } diff --git a/domain/src/main/java/com/whyranoid/domain/repository/ChallengeRepository.kt b/domain/src/main/java/com/whyranoid/domain/repository/ChallengeRepository.kt index ea9e84fb..a7cdb975 100644 --- a/domain/src/main/java/com/whyranoid/domain/repository/ChallengeRepository.kt +++ b/domain/src/main/java/com/whyranoid/domain/repository/ChallengeRepository.kt @@ -20,4 +20,6 @@ interface ChallengeRepository { suspend fun getUserBadges(uid: Long): Result> suspend fun startChallenge(uid: Int, challengeId: Int): Result + + suspend fun changeChallengeStatus(challengeId: Int, status: String, walkieId: Int): Result } diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/ChangeChallengeStatusUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/ChangeChallengeStatusUseCase.kt new file mode 100644 index 00000000..f35662d4 --- /dev/null +++ b/domain/src/main/java/com/whyranoid/domain/usecase/ChangeChallengeStatusUseCase.kt @@ -0,0 +1,15 @@ +package com.whyranoid.domain.usecase + +import com.whyranoid.domain.repository.ChallengeRepository +import javax.inject.Inject + +class ChangeChallengeStatusUseCase @Inject constructor( + private val challengeRepository: ChallengeRepository, + private val getMyUidUseCase: GetMyUidUseCase +) { + suspend operator fun invoke(challengeId: Int, status: String): Result { + val myId = getMyUidUseCase() + return challengeRepository.changeChallengeStatus(challengeId, status, myId.getOrNull()?.toInt() ?: -1) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/whyranoid/presentation/screens/challenge/ChallengeExitScreen.kt b/presentation/src/main/java/com/whyranoid/presentation/screens/challenge/ChallengeExitScreen.kt index be20e4b9..c2638fe1 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/screens/challenge/ChallengeExitScreen.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/screens/challenge/ChallengeExitScreen.kt @@ -29,10 +29,12 @@ import com.whyranoid.presentation.component.button.WalkieNegativeButton import com.whyranoid.presentation.component.button.WalkiePositiveButton import com.whyranoid.presentation.reusable.WalkieCircularProgressIndicator import com.whyranoid.presentation.theme.WalkieTypography +import com.whyranoid.presentation.viewmodel.challenge.ChallengeExitSideEffect import com.whyranoid.presentation.viewmodel.challenge.ChallengeExitState import com.whyranoid.presentation.viewmodel.challenge.ChallengeExitViewModel import org.koin.androidx.compose.koinViewModel import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect @Composable fun ChallengeExitScreen( @@ -40,6 +42,7 @@ fun ChallengeExitScreen( challengeId: Long, ) { + val context = LocalContext.current val viewModel = koinViewModel() LaunchedEffect(true) { @@ -48,10 +51,23 @@ fun ChallengeExitScreen( val state by viewModel.collectAsState() + viewModel.collectSideEffect { + when (it) { + ChallengeExitSideEffect.StopChallengeSuccess -> { + Toast.makeText(context, "챌린지를 성공적으로 중단하였습니다.", Toast.LENGTH_SHORT).show() + navController.popBackStack() + } + + ChallengeExitSideEffect.StopChallengeFailure -> { + Toast.makeText(context, "챌린지 중단에 실패하였습니다.", Toast.LENGTH_SHORT).show() + } + } + } + ChallengeExitContent( state, onPositiveButtonClicked = { - // TODO + viewModel.stopChallenge() }, onNegativeButtonClicked = { navController.popBackStack() @@ -135,7 +151,6 @@ fun ChallengeExitContent( ) { WalkiePositiveButton(text = "확인") { onPositiveButtonClicked() - Toast.makeText(context, "확인", Toast.LENGTH_SHORT).show() } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/challenge/ChallengeExitViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/challenge/ChallengeExitViewModel.kt index 274ae3dd..4d213eda 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/viewmodel/challenge/ChallengeExitViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/viewmodel/challenge/ChallengeExitViewModel.kt @@ -2,15 +2,18 @@ package com.whyranoid.presentation.viewmodel.challenge import androidx.lifecycle.ViewModel import com.whyranoid.domain.model.challenge.Challenge +import com.whyranoid.domain.usecase.ChangeChallengeStatusUseCase import com.whyranoid.domain.usecase.GetChallengeDetailUseCase import com.whyranoid.presentation.model.UiState import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container sealed class ChallengeExitSideEffect { - + object StopChallengeSuccess : ChallengeExitSideEffect() + object StopChallengeFailure : ChallengeExitSideEffect() } data class ChallengeExitState( @@ -19,6 +22,7 @@ data class ChallengeExitState( class ChallengeExitViewModel( private val getChallengeDetailUseCase: GetChallengeDetailUseCase, + private val changeChallengeStatusUseCase: ChangeChallengeStatusUseCase ) : ViewModel(), ContainerHost { @@ -35,4 +39,15 @@ class ChallengeExitViewModel( } } + fun stopChallenge() = intent { + changeChallengeStatusUseCase( + state.challenge.getDataOrNull()?.id?.toInt() ?: 0, + "N", + ).onSuccess { + postSideEffect(ChallengeExitSideEffect.StopChallengeSuccess) + }.onFailure { + postSideEffect(ChallengeExitSideEffect.StopChallengeFailure) + } + } + } \ No newline at end of file