diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b881960d..45f6e395 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -72,7 +72,11 @@ dependencies { implementation(project(":core:ui")) implementation(project(":data")) implementation(project(":domain")) + implementation(project(":feature:addlink")) + implementation(project(":feature:addpokit")) implementation(project(":feature:login")) + implementation(project(":feature:pokitdetail")) + implementation(project(":feature:search")) // hilt implementation(libs.hilt) @@ -81,4 +85,13 @@ dependencies { // firebase implementation(platform(libs.firebase.bom)) implementation(libs.firebase.auth.ktx) + + // navigation + implementation(libs.androidx.navigation.compose) + implementation(libs.hilt.navigation.compose) + + // orbit + implementation(libs.orbit.compose) + implementation(libs.orbit.core) + implementation(libs.orbit.viewmodel) } diff --git a/app/src/main/java/pokitmons/pokit/MainActivity.kt b/app/src/main/java/pokitmons/pokit/MainActivity.kt index 8d20def4..a3dfa521 100644 --- a/app/src/main/java/pokitmons/pokit/MainActivity.kt +++ b/app/src/main/java/pokitmons/pokit/MainActivity.kt @@ -3,9 +3,15 @@ package pokitmons.pokit import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import dagger.hilt.android.AndroidEntryPoint import pokitmons.pokit.core.ui.theme.PokitTheme -import pokitmons.pokit.navigation.LoginNavHost +import pokitmons.pokit.navigation.RootNavHost @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -13,7 +19,17 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { PokitTheme { - LoginNavHost() + val navHostController = rememberNavController() + val navBackStackEntry by navHostController.currentBackStackEntryAsState() + val currentDestination by remember(navBackStackEntry) { derivedStateOf { navBackStackEntry?.destination } } + + LaunchedEffect(currentDestination) { + currentDestination?.route?.let { route -> + // 믹스패널/파베 애널리틱스 화면 이동 로깅용 + } + } + + RootNavHost(navHostController = navHostController) } } } diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt b/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt new file mode 100644 index 00000000..5c35e25f --- /dev/null +++ b/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt @@ -0,0 +1,47 @@ +package pokitmons.pokit.navigation + +import androidx.navigation.NavType +import androidx.navigation.navArgument + +object Login { + val route: String = "login" +} + +object Home { + val route: String = "home" +} + +object AddLink { + val route: String = "addLink" + val linkIdArg = "link_id" + val routeWithArgs = "$route?$linkIdArg={$linkIdArg}" + var arguments = listOf( + navArgument(linkIdArg) { + nullable = true + type = NavType.StringType + } + ) +} + +object AddPokit { + val route: String = "addPokit" + val pokitIdArg = "pokit_id" + val routeWithArgs = "$route?$pokitIdArg={$pokitIdArg}" + var arguments = listOf( + navArgument(pokitIdArg) { + nullable = true + type = NavType.StringType + } + ) +} + +object PokitDetail { + val route: String = "pokitDetail" + val pokitIdArg = "pokit_id" + val routeWithArgs = "$route/{$pokitIdArg}" + var arguments = listOf(navArgument(pokitIdArg) { defaultValue = "-" }) +} + +object Search { + val route: String = "search" +} diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt new file mode 100644 index 00000000..e3d66b49 --- /dev/null +++ b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt @@ -0,0 +1,96 @@ +package pokitmons.pokit.navigation + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.strayalpaca.addlink.AddLinkScreenContainer +import com.strayalpaca.addlink.AddLinkViewModel +import com.strayalpaca.addpokit.AddPokitScreenContainer +import com.strayalpaca.addpokit.AddPokitViewModel +import com.strayalpaca.pokitdetail.PokitDetailScreenContainer +import com.strayalpaca.pokitdetail.PokitDetailViewModel +import pokitmons.pokit.LoginViewModel +import pokitmons.pokit.login.LoginScreen +import pokitmons.pokit.search.SearchScreenContainer +import pokitmons.pokit.search.SearchViewModel + +@Composable +fun RootNavHost( + navHostController: NavHostController, +) { + NavHost(navController = navHostController, startDestination = Login.route) { + composable(Login.route) { + val viewModel: LoginViewModel = hiltViewModel() + LoginScreen( + loginViewModel = viewModel, + onNavigateToTermsOfServiceScreen = {} + ) + } + + composable(Home.route) { + Box(modifier = Modifier.fillMaxSize()) + } + + composable( + route = AddLink.routeWithArgs, + arguments = AddLink.arguments + ) { navBackStackEntry -> + val viewModel: AddLinkViewModel = hiltViewModel() + val linkId = navBackStackEntry.arguments?.getString(AddLink.linkIdArg) + AddLinkScreenContainer( + linkId = linkId, + viewModel = viewModel, + onBackPressed = navHostController::popBackStack, + onNavigateToAddPokit = { + navHostController.navigate(AddPokit.route) + } + ) + } + + composable( + route = AddPokit.routeWithArgs, + arguments = AddPokit.arguments + ) { + val viewModel: AddPokitViewModel = hiltViewModel() + AddPokitScreenContainer( + viewModel = viewModel, + onBackPressed = navHostController::popBackStack + ) + } + + composable( + route = PokitDetail.routeWithArgs, + arguments = PokitDetail.arguments + ) { + val viewModel: PokitDetailViewModel = hiltViewModel() + PokitDetailScreenContainer( + viewModel = viewModel, + onBackPressed = navHostController::popBackStack, + onNavigateToLinkModify = { linkId -> + navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$linkId") + }, + onNavigateToPokitModify = { pokitId -> + navHostController.navigate("${AddPokit.route}?${AddPokit.pokitIdArg}=$pokitId") + } + ) + } + + composable( + route = Search.route + ) { + val viewModel: SearchViewModel = hiltViewModel() + SearchScreenContainer( + viewModel = viewModel, + onBackPressed = navHostController::popBackStack, + onNavigateToLinkModify = { linkId -> + navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$linkId") + } + ) + } + } +} diff --git a/feature/addlink/build.gradle.kts b/feature/addlink/build.gradle.kts index d37b8318..f86c99bc 100644 --- a/feature/addlink/build.gradle.kts +++ b/feature/addlink/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.com.android.library) alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.hilt) + id("kotlin-kapt") } android { @@ -60,5 +62,9 @@ dependencies { implementation(libs.orbit.core) implementation(libs.orbit.viewmodel) + // hilt + implementation(libs.hilt) + kapt(libs.hilt.compiler) + implementation(project(":core:ui")) } diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt index 04332847..998b2298 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkScreen.kt @@ -56,6 +56,7 @@ fun AddLinkScreenContainer( linkId: String?, viewModel: AddLinkViewModel, onBackPressed: () -> Unit, + onNavigateToAddPokit: () -> Unit, ) { val state by viewModel.collectAsState() val context = LocalContext.current @@ -100,7 +101,7 @@ fun AddLinkScreenContainer( inputTitle = viewModel::inputTitle, inputMemo = viewModel::inputMemo, inputNewPokitName = viewModel::inputNewPokitName, - onClickAddPokit = viewModel::showAddPokitBottomSheet, + onClickAddPokit = onNavigateToAddPokit, onClickSavePokit = viewModel::savePokit, dismissPokitAddBottomSheet = viewModel::hideAddPokitBottomSheet, onClickSelectPokit = viewModel::showSelectPokitBottomSheet, diff --git a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt index 8f607b45..88b24f77 100644 --- a/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt +++ b/feature/addlink/src/main/java/com/strayalpaca/addlink/AddLinkViewModel.kt @@ -8,6 +8,7 @@ import com.strayalpaca.addlink.model.Pokit import com.strayalpaca.addlink.model.ScreenStep import com.strayalpaca.addlink.model.sampleLink import com.strayalpaca.addlink.model.samplePokitList +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -22,8 +23,10 @@ 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 +import javax.inject.Inject -class AddLinkViewModel : ContainerHost, ViewModel() { +@HiltViewModel +class AddLinkViewModel @Inject constructor() : ContainerHost, ViewModel() { override val container: Container = container(AddLinkScreenState()) private val _linkUrl = MutableStateFlow("") diff --git a/feature/addpokit/build.gradle.kts b/feature/addpokit/build.gradle.kts index 0c700c02..e2892997 100644 --- a/feature/addpokit/build.gradle.kts +++ b/feature/addpokit/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.com.android.library) alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.hilt) + id("kotlin-kapt") } android { @@ -60,5 +62,9 @@ dependencies { implementation(libs.orbit.core) implementation(libs.orbit.viewmodel) + // hilt + implementation(libs.hilt) + kapt(libs.hilt.compiler) + implementation(project(":core:ui")) } diff --git a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt index 590775db..d4ae1ec5 100644 --- a/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt +++ b/feature/addpokit/src/main/java/com/strayalpaca/addpokit/AddPokitViewModel.kt @@ -9,6 +9,7 @@ import com.strayalpaca.addpokit.model.AddPokitSideEffect import com.strayalpaca.addpokit.model.PokitInputErrorMessage import com.strayalpaca.addpokit.model.PokitProfile import com.strayalpaca.addpokit.model.samplePokitList +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -21,8 +22,10 @@ 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 +import javax.inject.Inject -class AddPokitViewModel : ContainerHost, ViewModel() { +@HiltViewModel +class AddPokitViewModel @Inject constructor() : ContainerHost, ViewModel() { override val container: Container = container(AddPokitScreenState()) private val _pokitName = MutableStateFlow("") diff --git a/feature/pokitdetail/build.gradle.kts b/feature/pokitdetail/build.gradle.kts index ad7fc721..35609bd5 100644 --- a/feature/pokitdetail/build.gradle.kts +++ b/feature/pokitdetail/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.com.android.library) alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.hilt) + id("kotlin-kapt") } android { @@ -60,5 +62,9 @@ dependencies { implementation(libs.orbit.core) implementation(libs.orbit.viewmodel) + // hilt + implementation(libs.hilt) + kapt(libs.hilt.compiler) + implementation(project(":core:ui")) } diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt index 1264b5ad..d806f94e 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -38,6 +39,8 @@ import pokitmons.pokit.core.ui.R.drawable as coreDrawable fun PokitDetailScreenContainer( viewModel: PokitDetailViewModel, onBackPressed: () -> Unit, + onNavigateToLinkModify: (String) -> Unit, + onNavigateToPokitModify: (String) -> Unit, ) { val state by viewModel.state.collectAsState() val linkList by viewModel.linkList.collectAsState() @@ -61,7 +64,9 @@ fun PokitDetailScreenContainer( state = state, linkList = linkList, pokitList = pokitList, - onClickLink = viewModel::showLinkDetailBottomSheet + onClickLink = viewModel::showLinkDetailBottomSheet, + onClickPokitModify = onNavigateToLinkModify, + onClickLinkModify = onNavigateToPokitModify ) } @@ -85,6 +90,8 @@ fun PokitDetailScreen( linkList: List = emptyList(), pokitList: List = emptyList(), onClickLink: (Link) -> Unit = {}, + onClickPokitModify: (String) -> Unit = {}, + onClickLinkModify: (String) -> Unit = {}, ) { Column( modifier = Modifier.fillMaxSize() @@ -169,7 +176,14 @@ fun PokitDetailScreen( BottomSheetType.MODIFY -> { ModifyBottomSheetContent( onClickShare = {}, - onClickModify = {}, + onClickModify = remember { + { + state.currentLink?.let { link -> + hideLinkModifyBottomSheet() + onClickLinkModify(link.id) + } + } + }, onClickRemove = showLinkRemoveBottomSheet ) } @@ -195,7 +209,12 @@ fun PokitDetailScreen( BottomSheetType.MODIFY -> { ModifyBottomSheetContent( onClickShare = {}, - onClickModify = {}, + onClickModify = remember { + { + hidePokitModifyBottomSheet() + onClickPokitModify(state.currentPokit.id) + } + }, onClickRemove = showPokitRemoveBottomSheet ) } diff --git a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt index 4284774f..aaf5f63b 100644 --- a/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt +++ b/feature/pokitdetail/src/main/java/com/strayalpaca/pokitdetail/PokitDetailViewModel.kt @@ -8,12 +8,15 @@ import com.strayalpaca.pokitdetail.model.Pokit import com.strayalpaca.pokitdetail.model.PokitDetailScreenState import com.strayalpaca.pokitdetail.model.sampleLinkList import com.strayalpaca.pokitdetail.model.samplePokitList +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import javax.inject.Inject -class PokitDetailViewModel : ViewModel() { +@HiltViewModel +class PokitDetailViewModel @Inject constructor() : ViewModel() { private val _state = MutableStateFlow(PokitDetailScreenState()) val state: StateFlow = _state.asStateFlow() diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index ef9a8247..1387bd7f 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.com.android.library) alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.hilt) + id("kotlin-kapt") } android { @@ -60,5 +62,9 @@ dependencies { implementation(libs.orbit.core) implementation(libs.orbit.viewmodel) + // hilt + implementation(libs.hilt) + kapt(libs.hilt.compiler) + implementation(project(":core:ui")) } diff --git a/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt b/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt index 545e0dec..358aa676 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet +import pokitmons.pokit.core.ui.components.template.modifybottomsheet.ModifyBottomSheetContent import pokitmons.pokit.core.ui.theme.PokitTheme import pokitmons.pokit.search.components.filter.FilterArea import pokitmons.pokit.search.components.filterbottomsheet.FilterBottomSheet @@ -26,6 +28,7 @@ import pokitmons.pokit.search.model.SearchScreenStep fun SearchScreenContainer( viewModel: SearchViewModel, onBackPressed: () -> Unit, + onNavigateToLinkModify: (String) -> Unit, ) { val state by viewModel.state.collectAsState() val searchWord by viewModel.searchWord.collectAsState() @@ -46,7 +49,10 @@ fun SearchScreenContainer( onClickFilterItem = viewModel::showFilterBottomSheetWithType, hideBottomSheet = viewModel::hideFilterBottomSheet, onClickFilterSave = viewModel::setFilter, - toggleSortOrder = viewModel::toggleSortOrder + toggleSortOrder = viewModel::toggleSortOrder, + showLinkModifyBottomSheet = viewModel::showLinkModifyBottomSheet, + hideLinkModifyBottomSheet = viewModel::hideLinkModifyBottomSheet, + onClickLinkModify = onNavigateToLinkModify ) } @@ -67,6 +73,9 @@ fun SearchScreen( hideBottomSheet: () -> Unit = {}, onClickFilterSave: (Filter) -> Unit = {}, toggleSortOrder: () -> Unit = {}, + showLinkModifyBottomSheet: (Link) -> Unit = {}, + hideLinkModifyBottomSheet: () -> Unit = {}, + onClickLinkModify: (String) -> Unit = {}, ) { Column( modifier = Modifier.fillMaxSize() @@ -112,6 +121,7 @@ fun SearchScreen( .weight(1f), onToggleSort = toggleSortOrder, useRecentOrder = state.sortRecent, + onClickLinkKebab = showLinkModifyBottomSheet, links = linkList ) } @@ -123,5 +133,21 @@ fun SearchScreen( onDismissRequest = hideBottomSheet, onSaveClilck = onClickFilterSave ) + + PokitBottomSheet( + onHideBottomSheet = hideLinkModifyBottomSheet, + show = state.currentLink != null + ) { + ModifyBottomSheetContent( + onClickModify = remember { + { + state.currentLink?.let { link -> + hideLinkModifyBottomSheet() + onClickLinkModify(link.id) + } + } + } + ) + } } } diff --git a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt index b8c0055a..9d6d2210 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/SearchViewModel.kt @@ -1,6 +1,7 @@ package pokitmons.pokit.search import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -10,8 +11,10 @@ import pokitmons.pokit.search.model.Link import pokitmons.pokit.search.model.SearchScreenState import pokitmons.pokit.search.model.SearchScreenStep import pokitmons.pokit.search.model.sampleLinks +import javax.inject.Inject -class SearchViewModel : ViewModel() { +@HiltViewModel +class SearchViewModel @Inject constructor() : ViewModel() { private val _searchWord = MutableStateFlow("") val searchWord = _searchWord.asStateFlow() @@ -104,6 +107,22 @@ class SearchViewModel : ViewModel() { } } + fun showLinkModifyBottomSheet(link: Link) { + _state.update { state -> + state.copy( + currentLink = link + ) + } + } + + fun hideLinkModifyBottomSheet() { + _state.update { state -> + state.copy( + currentLink = null + ) + } + } + fun setFilter(filter: Filter) { _state.update { state -> state.copy( diff --git a/feature/search/src/main/java/pokitmons/pokit/search/model/SearchScreenState.kt b/feature/search/src/main/java/pokitmons/pokit/search/model/SearchScreenState.kt index 034bf214..77f053d3 100644 --- a/feature/search/src/main/java/pokitmons/pokit/search/model/SearchScreenState.kt +++ b/feature/search/src/main/java/pokitmons/pokit/search/model/SearchScreenState.kt @@ -8,6 +8,7 @@ data class SearchScreenState( val showFilterBottomSheet: Boolean = false, val firstBottomSheetFilterType: FilterType = FilterType.Pokit, val sortRecent: Boolean = true, + val currentLink: Link? = null, ) enum class SearchScreenStep { diff --git a/settings.gradle.kts b/settings.gradle.kts index b862fac0..ff5a1ddd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,4 +27,6 @@ include(":feature:login") include(":core:ui") include(":feature:addlink") include(":feature:addpokit") +include(":feature:login") include(":feature:pokitdetail") +include(":feature:search")