From c24ad47dc78a9e1fc02038ad1da8219f598aec04 Mon Sep 17 00:00:00 2001 From: JUNWON LEE <87055456+murjune@users.noreply.github.com> Date: Fri, 9 Feb 2024 03:11:42 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20Match=20=EB=AA=A8=EB=93=88=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=20Home=EC=97=90=EC=84=9C=20Match=20Navigation=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81,=20Match=20presentation=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [init] matching Module * [build] : AndroidHiltPlugin에 hilt-navigation-compose 라이브러리 추가 * [build] lifecycle-compose dependency 추가 * [build] domain, testing dependency * [feat] MatchViewModel 구현 * [feat] MatchScreen Composable 구현 * [feat] MatchNavigation 구현 * [build] domain 의존성 추가 * [feat] CanMatchProfileUseCase 구헌 * [refactor] HomeViewModel 코드 위치 변경 * [feat] 프로필 매칭 로직 구현, event 발행 로직 구현 * [refactor] HomeScreen ktLint * [feat] HomeScreen match Event * [feat] SingleEventArea 적용 * [feat] HomeNavigation 구현 * [feat] Home, Match 네비게이션 로직 구현 * [refactor] matchProfile - userId 매개변수 삭제 --- app/build.gradle.kts | 2 +- .../com/moya/funch/navigation/FunchNavHost.kt | 27 ++++-- .../funch/repository/MatchingRepository.kt | 4 +- .../funch/usecase/CanMatchProfileUseCase.kt | 15 +++ .../moya/funch/usecase/MatchProfileUseCase.kt | 5 +- feature/home/build.gradle.kts | 1 + .../main/java/com/moya/funch/HomeScreen.kt | 85 ++++++++++------- .../main/java/com/moya/funch/HomeViewModel.kt | 73 +++++++++----- .../moya/funch/navigation/HomeNavigation.kt | 2 +- feature/match/.gitignore | 1 + feature/match/build.gradle.kts | 14 +++ feature/match/proguard-rules.pro | 21 +++++ feature/match/src/main/AndroidManifest.xml | 4 + .../main/java/com/moya/funch/MatchScreen.kt | 94 +++++++++++++++++++ .../java/com/moya/funch/MatchViewModel.kt | 84 +++++++++++++++++ .../moya/funch/navigation/MatchNavigatoin.kt | 33 +++++++ feature/match/src/main/res/values/strings.xml | 2 + gradle/libs.versions.toml | 3 +- settings.gradle.kts | 2 +- 19 files changed, 400 insertions(+), 72 deletions(-) create mode 100644 core/domain/src/main/java/com/moya/funch/usecase/CanMatchProfileUseCase.kt create mode 100644 feature/match/.gitignore create mode 100644 feature/match/build.gradle.kts create mode 100644 feature/match/proguard-rules.pro create mode 100644 feature/match/src/main/AndroidManifest.xml create mode 100644 feature/match/src/main/java/com/moya/funch/MatchScreen.kt create mode 100644 feature/match/src/main/java/com/moya/funch/MatchViewModel.kt create mode 100644 feature/match/src/main/java/com/moya/funch/navigation/MatchNavigatoin.kt create mode 100644 feature/match/src/main/res/values/strings.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8060b045..365fe9c9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { // feature implementation(projects.feature.profile) implementation(projects.feature.home) -// implementation(projects.feature.match) + implementation(projects.feature.match) // implementation(libs.coil.core) implementation(libs.startup) diff --git a/app/src/main/java/com/moya/funch/navigation/FunchNavHost.kt b/app/src/main/java/com/moya/funch/navigation/FunchNavHost.kt index b1e3f1a5..f4b06815 100644 --- a/app/src/main/java/com/moya/funch/navigation/FunchNavHost.kt +++ b/app/src/main/java/com/moya/funch/navigation/FunchNavHost.kt @@ -1,9 +1,11 @@ package com.moya.funch.navigation import androidx.compose.runtime.Composable +import androidx.navigation.NavController import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController +import androidx.navigation.navOptions @Composable fun FunchNavHost(hasProfile: Boolean, navController: NavHostController = rememberNavController()) { @@ -11,17 +13,26 @@ fun FunchNavHost(hasProfile: Boolean, navController: NavHostController = remembe navController = navController, startDestination = determineStartDestination(hasProfile) ) { - profileGraph( - onNavigateToHome = navController::navigateToHome, - onCloseMyProfile = navController::closeMyProfile - ) - homeScreen( - onNavigateToMatching = { /* @Gun Hyung TODO : 매칭 라우터 연결 */ }, - onNavigateToMyProfile = navController::navigateToMyProfile - ) + with(navController) { + profileGraph( + onNavigateToHome = ::navigateToHome, + onCloseMyProfile = ::closeMyProfile + ) + homeScreen( + onNavigateToMatching = ::onNavigateToMatching, + onNavigateToMyProfile = ::navigateToMyProfile + ) + matchingScreen(onClose = { popBackStack(HOME_ROUTE, false) }) + } } } +private val singleTopNavOptions = navOptions { + launchSingleTop = true +} + +private fun NavController.onNavigateToMatching(route: String) = navigateToMatching(route, singleTopNavOptions) + private fun determineStartDestination(hasProfile: Boolean): String { return if (hasProfile) HOME_ROUTE else PROFILE_GRAPH_ROUTE } diff --git a/core/domain/src/main/java/com/moya/funch/repository/MatchingRepository.kt b/core/domain/src/main/java/com/moya/funch/repository/MatchingRepository.kt index 6b5049e6..be0cb701 100644 --- a/core/domain/src/main/java/com/moya/funch/repository/MatchingRepository.kt +++ b/core/domain/src/main/java/com/moya/funch/repository/MatchingRepository.kt @@ -2,6 +2,6 @@ package com.moya.funch.repository import com.moya.funch.entity.match.Matching -fun interface MatchingRepository { - suspend fun matchProfile(userId: String, targetCode: String): Matching +interface MatchingRepository { + suspend fun matchProfile(targetCode: String): Matching } diff --git a/core/domain/src/main/java/com/moya/funch/usecase/CanMatchProfileUseCase.kt b/core/domain/src/main/java/com/moya/funch/usecase/CanMatchProfileUseCase.kt new file mode 100644 index 00000000..6cdd328e --- /dev/null +++ b/core/domain/src/main/java/com/moya/funch/usecase/CanMatchProfileUseCase.kt @@ -0,0 +1,15 @@ +package com.moya.funch.usecase + +import com.moya.funch.repository.MatchingRepository +import javax.inject.Inject + +class CanMatchProfileUseCaseImpl @Inject constructor( + private val matchingRepository: MatchingRepository +) : CanMatchProfileUseCase { + override suspend operator fun invoke(targetCode: String): Boolean = + runCatching { matchingRepository.matchProfile(targetCode) }.isSuccess +} + +fun interface CanMatchProfileUseCase { + suspend operator fun invoke(targetCode: String): Boolean +} diff --git a/core/domain/src/main/java/com/moya/funch/usecase/MatchProfileUseCase.kt b/core/domain/src/main/java/com/moya/funch/usecase/MatchProfileUseCase.kt index d4623909..6790918f 100644 --- a/core/domain/src/main/java/com/moya/funch/usecase/MatchProfileUseCase.kt +++ b/core/domain/src/main/java/com/moya/funch/usecase/MatchProfileUseCase.kt @@ -7,10 +7,9 @@ import javax.inject.Inject class MatchProfileUseCaseImpl @Inject constructor( private val matchingRepository: MatchingRepository ) : MatchProfileUseCase { - override suspend operator fun invoke(userId: String, targetCode: String): Matching = - matchingRepository.matchProfile(userId, targetCode) + override suspend operator fun invoke(targetCode: String): Matching = matchingRepository.matchProfile(targetCode) } fun interface MatchProfileUseCase { - suspend operator fun invoke(userId: String, targetCode: String): Matching + suspend operator fun invoke(targetCode: String): Matching } diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index a231addf..45e95f7d 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -9,4 +9,5 @@ android { dependencies { implementation(projects.core.designsystem) + implementation(projects.core.domain) } diff --git a/feature/home/src/main/java/com/moya/funch/HomeScreen.kt b/feature/home/src/main/java/com/moya/funch/HomeScreen.kt index 4a2c0b89..a049a4b4 100644 --- a/feature/home/src/main/java/com/moya/funch/HomeScreen.kt +++ b/feature/home/src/main/java/com/moya/funch/HomeScreen.kt @@ -1,5 +1,6 @@ package com.moya.funch +import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -16,16 +17,18 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -33,6 +36,7 @@ import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.moya.funch.component.FunchButtonTextField import com.moya.funch.component.FunchIcon import com.moya.funch.component.FunchIconButton @@ -49,27 +53,44 @@ import com.moya.funch.theme.Lemon500 import com.moya.funch.theme.LocalBackgroundTheme import com.moya.funch.theme.White import com.moya.funch.theme.Yellow500 +import com.moya.funch.ui.SingleEventArea +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach -private val brush = - Brush.horizontalGradient( - 0.5f to Lemon500, - 0.5f to Color(0xFFFFD440) - ) +private val brush = Brush.horizontalGradient( + 0.5f to Lemon500, + 0.5f to Color(0xFFFFD440) +) @Composable internal fun HomeRoute( viewModel: HomeViewModel = hiltViewModel(), onNavigateToMyProfile: () -> Unit, - onNavigateToMatching: () -> Unit + onNavigateToMatching: (String) -> Unit ) { - val homeModel = viewModel.homeModel.collectAsState().value + val homeModel by viewModel.homeModel.collectAsStateWithLifecycle() + val matched by viewModel.matched.collectAsStateWithLifecycle(false) + val context = LocalContext.current + val matchDone by rememberUpdatedState(viewModel::matchDone) + + LaunchedEffect(viewModel) { + viewModel.homeErrorMessage + .onEach { + Toast.makeText(context, it, Toast.LENGTH_SHORT).show() + }.launchIn(this) + } + + if (matched) { + matchDone() + onNavigateToMatching(homeModel.matchingCode) + } HomeScreen( myCode = homeModel.myCode, viewCount = homeModel.viewCount, matchingCode = homeModel.matchingCode, - onMatchingCodeChange = { code -> viewModel.setMatchingCode(code) }, - onNavigateToMatching = onNavigateToMatching, + onMatchingCodeChange = viewModel::setMatchingCode, + matchProfile = viewModel::matchProfile, onNavigateToMyProfile = onNavigateToMyProfile ) } @@ -80,7 +101,7 @@ internal fun HomeScreen( viewCount: Int, matchingCode: String, onMatchingCodeChange: (String) -> Unit, - onNavigateToMatching: () -> Unit, + matchProfile: () -> Unit, onNavigateToMyProfile: () -> Unit ) { Column( @@ -97,7 +118,7 @@ internal fun HomeScreen( MatchingCard( value = matchingCode, onValueChange = onMatchingCodeChange, - onNavigateToMatching = onNavigateToMatching + matchProfile = matchProfile ) Row( modifier = Modifier.fillMaxWidth(), @@ -118,10 +139,9 @@ internal fun HomeScreen( } @Composable -private fun MatchingCard(value: String, onValueChange: (String) -> Unit, onNavigateToMatching: () -> Unit) { +private fun MatchingCard(value: String, onValueChange: (String) -> Unit, matchProfile: () -> Unit) { Column( - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .border( width = 1.dp, @@ -155,18 +175,20 @@ private fun MatchingCard(value: String, onValueChange: (String) -> Unit, onNavig onValueChange = onValueChange, hint = stringResource(id = R.string.matching_card_hint), iconButton = { - FunchIconButton( - modifier = Modifier.size(40.dp), - roundedCornerShape = RoundedCornerShape(12.dp), - backgroundColor = Gray500, - onClick = onNavigateToMatching, - funchIcon = - FunchIcon( - resId = FunchIconAsset.Search.search_24, - description = "", - tint = Yellow500 + SingleEventArea { cutter -> + FunchIconButton( + modifier = Modifier.size(40.dp), + roundedCornerShape = RoundedCornerShape(12.dp), + backgroundColor = Gray500, + onClick = { cutter.handle(matchProfile) }, + funchIcon = + FunchIcon( + resId = FunchIconAsset.Search.search_24, + description = "", + tint = Yellow500 + ) ) - ) + } } ) } @@ -175,8 +197,7 @@ private fun MatchingCard(value: String, onValueChange: (String) -> Unit, onNavig @Composable private fun CodeCard(modifier: Modifier = Modifier, myCode: String) { Row( - modifier = - modifier + modifier = modifier .background( color = Gray800, shape = FunchTheme.shapes.medium @@ -227,8 +248,7 @@ private fun CodeCard(modifier: Modifier = Modifier, myCode: String) { @Composable private fun MyProfileCard(modifier: Modifier = Modifier, onMyProfileClick: () -> Unit) { Column( - modifier = - modifier + modifier = modifier .background( color = Gray800, shape = FunchTheme.shapes.medium @@ -259,8 +279,7 @@ private fun MyProfileCard(modifier: Modifier = Modifier, onMyProfileClick: () -> @Composable private fun ProfileViewCounterCard(viewCount: Int) { Row( - modifier = - Modifier + modifier = Modifier .fillMaxWidth() .background( color = Gray800, @@ -318,7 +337,7 @@ private fun Preview1() { viewCount = 23, matchingCode = text, onMatchingCodeChange = { text = it }, - onNavigateToMatching = {}, + matchProfile = {}, onNavigateToMyProfile = {} ) } diff --git a/feature/home/src/main/java/com/moya/funch/HomeViewModel.kt b/feature/home/src/main/java/com/moya/funch/HomeViewModel.kt index 91e5b915..03f92a8d 100644 --- a/feature/home/src/main/java/com/moya/funch/HomeViewModel.kt +++ b/feature/home/src/main/java/com/moya/funch/HomeViewModel.kt @@ -1,16 +1,22 @@ package com.moya.funch import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import timber.log.Timber // @Gun Hyung TODO : 모델 모듈로 분리 data class HomeModel( val myCode: String, val viewCount: Int, - val matchingCode: String + val matchingCode: String = "" ) { companion object { fun empty() = HomeModel( @@ -22,41 +28,64 @@ data class HomeModel( } @HiltViewModel -internal class HomeViewModel -@Inject -constructor( - // @Gun Hyung TODO : UseCase 추가 +internal class HomeViewModel @Inject constructor( +// private val canMatchProfileUse: CanMatchProfileUseCase, ) : ViewModel() { private val _homeModel = MutableStateFlow(HomeModel.empty()) // @Gun Hyung TODO : 모델 초기화 필요 val homeModel = _homeModel.asStateFlow() + private val _homeErrorMessage: MutableSharedFlow = MutableSharedFlow() + val homeErrorMessage = _homeErrorMessage.asSharedFlow() + + private val _matched = MutableStateFlow(false) + val matched = _matched.asStateFlow() + init { initHome() } + fun setMatchingCode(code: String) { + Timber.e("setMatchingCode: $code") + _homeModel.value = _homeModel.value.copy( + matchingCode = code.uppercase() + ) + } + + fun matchProfile() { + viewModelScope.launch { + // @murjune TODO : canMatchProfileUse(userId, targetCode) 로 대체 +// if (canMatchProfileUse(userId, targetCode)) { +// _homeModel.value = _homeModel.value.copy(canMatched = true) +// } else { +// _homeErrorMessage.emit("매칭할 수 없는 코드입니다.") +// } + if (homeModel.value.matchingCode.isBlank()) { + _homeErrorMessage.emit("매칭할 수 없는 코드입니다.") + } else { + delay(1000L) + _matched.emit(true) + } + } + } + + fun matchDone() { + _matched.value = false + } + private fun initHome() { // @Gun Hyung TODO : 도메인 완성시 init 함수 제작 - // setMyCode("u23c") - // setViewCount(12) + setMyCode("u23c") + setViewCount(12) } private fun setMyCode(code: String) { // @Gun Hyung TODO : 도메인에서 .uppercase() 처리 - _homeModel.value = - _homeModel.value.copy( - myCode = code.uppercase() - ) + _homeModel.value = _homeModel.value.copy( + myCode = code.uppercase() + ) } private fun setViewCount(count: Int) { - _homeModel.value = - _homeModel.value.copy( - viewCount = count - ) - } - - fun setMatchingCode(code: String) { - _homeModel.value = - _homeModel.value.copy( - matchingCode = code.uppercase() - ) + _homeModel.value = _homeModel.value.copy( + viewCount = count + ) } } diff --git a/feature/home/src/main/java/com/moya/funch/navigation/HomeNavigation.kt b/feature/home/src/main/java/com/moya/funch/navigation/HomeNavigation.kt index f8041b1f..250a019e 100644 --- a/feature/home/src/main/java/com/moya/funch/navigation/HomeNavigation.kt +++ b/feature/home/src/main/java/com/moya/funch/navigation/HomeNavigation.kt @@ -11,7 +11,7 @@ fun NavController.navigateToHome() = navigate(HOME_ROUTE) { popUpTo(graph.id) } -fun NavGraphBuilder.homeScreen(onNavigateToMyProfile: () -> Unit, onNavigateToMatching: () -> Unit) { +fun NavGraphBuilder.homeScreen(onNavigateToMyProfile: () -> Unit, onNavigateToMatching: (String) -> Unit) { composable(route = HOME_ROUTE) { HomeRoute( onNavigateToMatching = onNavigateToMatching, diff --git a/feature/match/.gitignore b/feature/match/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/match/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/match/build.gradle.kts b/feature/match/build.gradle.kts new file mode 100644 index 00000000..d47b59fa --- /dev/null +++ b/feature/match/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + alias(libs.plugins.funch.feature) + alias(libs.plugins.funch.compose) +} + +android { + namespace = "com.moya.funch.match" +} + +dependencies { + implementation(projects.core.designsystem) + implementation(projects.core.domain) + implementation(projects.core.testing) +} diff --git a/feature/match/proguard-rules.pro b/feature/match/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/match/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/match/src/main/AndroidManifest.xml b/feature/match/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e1000761 --- /dev/null +++ b/feature/match/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/feature/match/src/main/java/com/moya/funch/MatchScreen.kt b/feature/match/src/main/java/com/moya/funch/MatchScreen.kt new file mode 100644 index 00000000..ff3ab0c2 --- /dev/null +++ b/feature/match/src/main/java/com/moya/funch/MatchScreen.kt @@ -0,0 +1,94 @@ +package com.moya.funch + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Button +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.moya.funch.entity.SubwayStation +import com.moya.funch.entity.match.Chemistry +import com.moya.funch.entity.match.Recommend +import com.moya.funch.entity.profile.Profile + +@Composable +internal fun MatchRoute(onClose: () -> Unit, code: String, matchViewModel: MatchViewModel = hiltViewModel()) { + val uiState by matchViewModel.uiState.collectAsStateWithLifecycle() + val matchCode by matchViewModel.matchCode.collectAsStateWithLifecycle() + + LaunchedEffect(matchViewModel) { + matchViewModel.saveMatchCode(code) + } + + MatchScreen( + onClose = onClose, + memberCode = matchCode, + matchUiState = uiState + ) +} + +@Composable +private fun MatchScreen(onClose: () -> Unit, memberCode: String, matchUiState: MatchUiState) { + CompositionLocalProvider(LocalContentColor provides Color.White) { // @murjune TODO Delete this line + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + when (matchUiState) { + is MatchUiState.Loading -> Loading() + is MatchUiState.Error -> ErrorMatchContent(code = memberCode) + is MatchUiState.Success -> { + val (profile, similarity, chemistrys, recommends, subways) = matchUiState.matching + MatchContent( + profile = profile, + similarity = similarity, + chemistrys = chemistrys, + recommends = recommends, + subways = subways + ) + } + } + Button(onClick = onClose) { + Text("Close") + } + } + } +} + +@Composable +internal fun MatchContent( + profile: Profile, + similarity: Int, + chemistrys: List, + recommends: List, + subways: List +) { + Column { + Text("This is Match Screen") + Text(text = "Profile : $profile") + Text(text = "Similarity : $similarity") + Text(text = "Chemistrys : $chemistrys") + Text(text = "Recommends : $recommends") + Text(text = "Subways : $subways") + } +} + +@Composable +internal fun ErrorMatchContent(code: String) { + Text("There is no match code $code. Please try again.") +} + +@Composable +internal fun Loading() { + Text(text = "Loading...") +} diff --git a/feature/match/src/main/java/com/moya/funch/MatchViewModel.kt b/feature/match/src/main/java/com/moya/funch/MatchViewModel.kt new file mode 100644 index 00000000..b5a3a872 --- /dev/null +++ b/feature/match/src/main/java/com/moya/funch/MatchViewModel.kt @@ -0,0 +1,84 @@ +package com.moya.funch + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.moya.funch.entity.Blood +import com.moya.funch.entity.Club +import com.moya.funch.entity.Job +import com.moya.funch.entity.Mbti +import com.moya.funch.entity.SubwayLine +import com.moya.funch.entity.SubwayStation +import com.moya.funch.entity.match.Chemistry +import com.moya.funch.entity.match.Matching +import com.moya.funch.entity.match.Recommend +import com.moya.funch.entity.profile.Profile +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn + +@HiltViewModel +class MatchViewModel @Inject constructor( +// private val matchProfileUseCase : MatchProfileUseCase, + private val savedStateHandle: SavedStateHandle +) : ViewModel() { + + val matchCode: StateFlow = savedStateHandle.getStateFlow(MATCH_CODE, "") + + val uiState: StateFlow = matchCode.mapLatest { code -> + if (code.isEmpty()) { + MatchUiState.Loading + } else { + // MatchUiState.Success(matchProfileUseCase("1", it)) + // TODO : Remove 목 데이터, delay + delay(1000L) + MatchUiState.Success(MOCK_MATCHING) + } + }.catch { + emit(MatchUiState.Error) + }.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = MatchUiState.Loading) + + fun saveMatchCode(code: String) { + savedStateHandle[MATCH_CODE] = code + } + + companion object { + private const val MATCH_CODE = "matchCode" + + // id: 65c27d3232e6054951260d3d, code: V3H5, device : bbb + // id: 65c27d8b32e6054951260d3e, code: 6V2Q, device: ccc + private val MOCK_MATCHING = Matching( + profile = Profile().copy( + name = "abc", + job = Job.DEVELOPER, + clubs = listOf(Club.NEXTERS), + mbti = Mbti.INFP, + blood = Blood.A, + subways = listOf( + SubwayStation("목동역", lines = listOf(SubwayLine.FIVE)) + ) + ), + similarity = 80, + chemistrys = listOf( + Chemistry("대한민국 선수분들", "정말 고생 많으셨습니다...") + ), + recommends = listOf( + Recommend("지금은"), + Recommend("새벽"), + Recommend("3시"), + Recommend("48뷴") + ) + ) + } +} + +sealed class MatchUiState { + data object Loading : MatchUiState() + data object Error : MatchUiState() + data class Success(val matching: Matching) : MatchUiState() +} diff --git a/feature/match/src/main/java/com/moya/funch/navigation/MatchNavigatoin.kt b/feature/match/src/main/java/com/moya/funch/navigation/MatchNavigatoin.kt new file mode 100644 index 00000000..f4d59192 --- /dev/null +++ b/feature/match/src/main/java/com/moya/funch/navigation/MatchNavigatoin.kt @@ -0,0 +1,33 @@ +package com.moya.funch.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.NavType +import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import com.moya.funch.MatchRoute + +private const val MATCH_ROUTE = "match/{code}" +private const val MEMBER_CODE_KEY = "code" +private const val NO_MEMBER_CODE = "no member code" + +fun NavController.navigateToMatching(memberCode: String, navOptions: NavOptions? = null) = + navigate(createMatchRoute(memberCode), navOptions) + +fun NavGraphBuilder.matchingScreen(onClose: () -> Unit) { + composable( + route = MATCH_ROUTE, + arguments = listOf( + navArgument(MEMBER_CODE_KEY) { + type = NavType.StringType + defaultValue = NO_MEMBER_CODE + } + ) + ) { backStackEntry -> + val code = backStackEntry.arguments?.getString(MEMBER_CODE_KEY) ?: NO_MEMBER_CODE + MatchRoute(code = code, onClose = onClose) + } +} + +private fun createMatchRoute(memberCode: String) = MATCH_ROUTE.replace("{$MEMBER_CODE_KEY}", memberCode) diff --git a/feature/match/src/main/res/values/strings.xml b/feature/match/src/main/res/values/strings.xml new file mode 100644 index 00000000..85420055 --- /dev/null +++ b/feature/match/src/main/res/values/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 734380ae..3a37166d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -106,6 +106,7 @@ coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coi activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" } navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "dagger-hilt-navigation-compose" } +lifecycle-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } # dagger-hilt hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "dagger-hilt" } @@ -193,7 +194,7 @@ funch-jvm-library = { id = "com.moya.funch.jvm.library", version = "unspecified" [bundles] firebase = ["firebase-analytics", "firebase-crashlytics"] lifecycle = ["lifecycle", "lifecycle-viewmodel"] -compose = ["ui", "ui-graphics", "ui-tooling", "ui-tooling-preview", "material3-compose", "coil-compose", "ui-foundation", "activity-compose", "navigation-compose"] +compose = ["ui", "ui-graphics", "ui-tooling", "ui-tooling-preview", "material3-compose", "coil-compose", "ui-foundation", "activity-compose", "navigation-compose", "lifecycle-compose"] retrofit = ["retrofit", "retrofit-kotlin-serialization-converter"] junit5 = ["junit5", "junit5-engine", "junit5-params", "junit5-vintage"] androidx-android-test = ["androidx-test-core", "androidx-test-espresso", "androidx-test-espresso-intents", "androidx-test-junit", "androidx-test-junit-ktx", "androidx-test-rules", "androidx-test-runner", "androidx-test-truth"] diff --git a/settings.gradle.kts b/settings.gradle.kts index 8425c4dd..a5535105 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,4 +30,4 @@ include(":core:data") // feature include(":feature:profile") include(":feature:home") -// include(":feature:match") + include(":feature:match")