diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt b/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt index e56f4623a..fda7ece61 100644 --- a/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt +++ b/app-android/src/main/java/io/github/droidkaigi/confsched2023/KaigiApp.kt @@ -110,6 +110,9 @@ private fun KaigiNavHost( onTimetableItemClick = navController::navigateToTimetableItemDetailScreen, onNavigateToBookmarkScreenRequested = navController::navigateToBookmarkScreen, onLinkClick = externalNavController::navigate, + onRoomClick = { + navController.popBackStack(navController.graph.startDestinationId, false) + }, onCalendarRegistrationClick = externalNavController::navigateToCalendarRegistration, onShareClick = externalNavController::onShareClick, ) diff --git a/core/data/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/data/navigation/NavigationModule.kt b/core/data/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/data/navigation/NavigationModule.kt new file mode 100644 index 000000000..f92eb3bbe --- /dev/null +++ b/core/data/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/data/navigation/NavigationModule.kt @@ -0,0 +1,31 @@ +package io.github.droidkaigi.confsched2023.data.navigation + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import io.github.droidkaigi.confsched2023.model.NavigationRequester +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +class NavigationModule { + @Provides + @Singleton + fun provideNavigationRequester(): NavigationRequester = DefaultNavigationRequester() +} + +class DefaultNavigationRequester : NavigationRequester { + private val _navigateRequestStateFlow = MutableStateFlow("") + override fun getNavigationRouteFlow(): Flow = _navigateRequestStateFlow + + override fun navigateTo(route: String) { + _navigateRequestStateFlow.value = route + } + + override fun navigated() { + _navigateRequestStateFlow.value = "" + } +} diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/NavigationRequester.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/NavigationRequester.kt new file mode 100644 index 000000000..01860e48c --- /dev/null +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/NavigationRequester.kt @@ -0,0 +1,9 @@ +package io.github.droidkaigi.confsched2023.model + +import kotlinx.coroutines.flow.Flow + +interface NavigationRequester { + fun getNavigationRouteFlow(): Flow + fun navigateTo(route: String) + fun navigated() +} diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableItemDetailScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableItemDetailScreenRobot.kt index 60f7fad8f..a816f02e5 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableItemDetailScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableItemDetailScreenRobot.kt @@ -43,6 +43,7 @@ class TimetableItemDetailScreenRobot @Inject constructor( TimetableItemDetailScreen( onNavigationIconClick = { }, onLinkClick = { }, + onRoomClick = { }, onCalendarRegistrationClick = { }, onShareClick = { }, onNavigateToBookmarkScreenRequested = { }, diff --git a/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt b/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt index 738eca40f..ba76ad87e 100644 --- a/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt +++ b/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -108,6 +109,7 @@ fun MainScreen( navigationType = navigationType, routeToTab = mainNestedGraphStateHolder::routeToTab, onTabSelected = mainNestedGraphStateHolder::onTabSelected, + onNavigated = viewModel::onNavigated, mainNestedNavGraph = mainNestedNavGraph, ) } @@ -152,6 +154,7 @@ enum class MainScreenTab( data class MainScreenUiState( val isAchievementsEnabled: Boolean = false, + val navigationRoute: String, ) @Composable @@ -161,11 +164,18 @@ private fun MainScreen( navigationType: NavigationType, routeToTab: String.() -> MainScreenTab?, onTabSelected: (NavController, MainScreenTab) -> Unit, + onNavigated: () -> Unit, mainNestedNavGraph: NavGraphBuilder.(NavController, PaddingValues) -> Unit, ) { val mainNestedNavController = rememberNavController() val navBackStackEntry by mainNestedNavController.currentBackStackEntryAsState() val currentTab = navBackStackEntry?.destination?.route?.routeToTab() + LaunchedEffect(uiState.navigationRoute) { + if (uiState.navigationRoute.isNotBlank()) { + mainNestedNavController.navigate(uiState.navigationRoute) + onNavigated() + } + } Row(modifier = Modifier.fillMaxSize()) { AnimatedVisibility(visible = navigationType == NAVIGATION_RAIL) { KaigiNavigationRail( diff --git a/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreenViewModel.kt b/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreenViewModel.kt index 1832cf45d..0730b8f89 100644 --- a/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreenViewModel.kt +++ b/feature/main/src/main/java/io/github/droidkaigi/confsched2023/main/MainScreenViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.droidkaigi.confsched2023.data.contributors.AchievementRepository import io.github.droidkaigi.confsched2023.designsystem.strings.AppStrings +import io.github.droidkaigi.confsched2023.model.NavigationRequester import io.github.droidkaigi.confsched2023.ui.UserMessageStateHolder import io.github.droidkaigi.confsched2023.ui.buildUiState import io.github.droidkaigi.confsched2023.ui.handleErrorAndRetry @@ -16,6 +17,7 @@ import javax.inject.Inject @HiltViewModel class MainScreenViewModel @Inject constructor( val userMessageStateHolder: UserMessageStateHolder, + private val navigationRequester: NavigationRequester, achievementRepository: AchievementRepository, ) : ViewModel(), UserMessageStateHolder by userMessageStateHolder { @@ -30,11 +32,24 @@ class MainScreenViewModel @Inject constructor( initialValue = false, ) + private val navigationRouteStateFlow: StateFlow = navigationRequester.getNavigationRouteFlow() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = "", + ) + val uiState: StateFlow = buildUiState( isAchievementsEnabledStateFlow, - ) { isAchievementsEnabled -> + navigationRouteStateFlow, + ) { isAchievementsEnabled, navigationRoute -> MainScreenUiState( isAchievementsEnabled = isAchievementsEnabled, + navigationRoute = navigationRoute, ) } + + fun onNavigated() { + navigationRequester.navigated() + } } diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailScreen.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailScreen.kt index fbec85ea9..4d454fed4 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailScreen.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailScreen.kt @@ -49,6 +49,7 @@ fun NavGraphBuilder.sessionScreens( onTimetableItemClick: (TimetableItem) -> Unit, onNavigateToBookmarkScreenRequested: () -> Unit, onLinkClick: (url: String) -> Unit, + onRoomClick: () -> Unit, onCalendarRegistrationClick: (TimetableItem) -> Unit, onShareClick: (TimetableItem) -> Unit, ) { @@ -56,6 +57,7 @@ fun NavGraphBuilder.sessionScreens( TimetableItemDetailScreen( onNavigationIconClick = onNavigationIconClick, onLinkClick = onLinkClick, + onRoomClick = onRoomClick, onCalendarRegistrationClick = onCalendarRegistrationClick, onNavigateToBookmarkScreenRequested = onNavigateToBookmarkScreenRequested, onShareClick = onShareClick, @@ -84,6 +86,7 @@ fun NavController.navigateToTimetableItemDetailScreen( fun TimetableItemDetailScreen( onNavigationIconClick: () -> Unit, onLinkClick: (url: String) -> Unit, + onRoomClick: () -> Unit, onCalendarRegistrationClick: (TimetableItem) -> Unit, onNavigateToBookmarkScreenRequested: () -> Unit, onShareClick: (TimetableItem) -> Unit, @@ -109,6 +112,10 @@ fun TimetableItemDetailScreen( onNavigationIconClick = onNavigationIconClick, onBookmarkClick = viewModel::onBookmarkClick, onLinkClick = onLinkClick, + onRoomClick = { + viewModel.navigateTo("floorMap") + onRoomClick() + }, onCalendarRegistrationClick = onCalendarRegistrationClick, onShareClick = onShareClick, onSelectedLanguage = viewModel::onSelectDescriptionLanguage, @@ -145,6 +152,7 @@ private fun TimetableItemDetailScreen( onLinkClick: (url: String) -> Unit, onCalendarRegistrationClick: (TimetableItem) -> Unit, onShareClick: (TimetableItem) -> Unit, + onRoomClick: () -> Unit, onSelectedLanguage: (Lang) -> Unit, snackbarHostState: SnackbarHostState, ) { @@ -194,6 +202,7 @@ private fun TimetableItemDetailScreen( uiState = it.timetableItemDetailSectionUiState, selectedLanguage = it.currentLang, onLinkClick = onLinkClick, + onRoomClick = onRoomClick, contentPadding = innerPadding, ) } @@ -214,7 +223,9 @@ fun TimetableItemDetailScreenPreview() { TimetableItemDetailScreen( uiState = Loaded( timetableItem = fakeSession, - timetableItemDetailSectionUiState = TimetableItemDetailSectionUiState(fakeSession), + timetableItemDetailSectionUiState = TimetableItemDetailSectionUiState( + fakeSession, + ), isBookmarked = isBookMarked, isLangSelectable = true, viewBookmarkListRequestState = ViewBookmarkListRequestState.NotRequested, @@ -225,6 +236,7 @@ fun TimetableItemDetailScreenPreview() { isBookMarked = !isBookMarked }, onLinkClick = {}, + onRoomClick = {}, onCalendarRegistrationClick = {}, onShareClick = {}, onSelectedLanguage = {}, diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailViewModel.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailViewModel.kt index 739d36785..815a2c301 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailViewModel.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableItemDetailViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.droidkaigi.confsched2023.designsystem.strings.AppStrings import io.github.droidkaigi.confsched2023.model.Lang +import io.github.droidkaigi.confsched2023.model.NavigationRequester import io.github.droidkaigi.confsched2023.model.SessionsRepository import io.github.droidkaigi.confsched2023.model.TimetableItem import io.github.droidkaigi.confsched2023.model.TimetableItemId @@ -29,6 +30,7 @@ import javax.inject.Inject @HiltViewModel class TimetableItemDetailViewModel @Inject constructor( private val sessionsRepository: SessionsRepository, + private val navigationRequester: NavigationRequester, val userMessageStateHolder: UserMessageStateHolder, savedStateHandle: SavedStateHandle, ) : ViewModel(), @@ -108,4 +110,10 @@ class TimetableItemDetailViewModel @Inject constructor( ) { selectedDescriptionLanguageStateFlow.value = language } + + fun navigateTo( + route: String, + ) { + navigationRequester.navigateTo(route) + } } diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt index 715dfccc4..78ac390b3 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCard.kt @@ -40,6 +40,7 @@ import java.util.Locale @Composable fun TimetableItemDetailSummaryCard( timetableItem: TimetableItem, + onRoomClick: () -> Unit, modifier: Modifier = Modifier, ) { Column(modifier = modifier) { @@ -88,6 +89,7 @@ fun TimetableItemDetailSummaryCard( leadingIcon = Icons.Outlined.Place, label = SessionsStrings.Place.asString(), content = timetableItem.room.nameAndFloor, + onContentClick = onRoomClick, ) TimetableItemDetailSummaryCardRow( leadingIcon = Icons.Outlined.Language, @@ -110,7 +112,10 @@ fun TimetableItemDetailSummaryCard( fun TimetableItemDetailSummaryPreview() { KaigiTheme { Surface { - TimetableItemDetailSummaryCard(timetableItem = Session.fake()) + TimetableItemDetailSummaryCard( + timetableItem = Session.fake(), + onRoomClick = {}, + ) } } } diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCardRow.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCardRow.kt index 2e88fafea..89dbe2193 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCardRow.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/component/TimetableItemDetailSummaryCardRow.kt @@ -16,9 +16,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import io.github.droidkaigi.confsched2023.designsystem.component.ClickableLinkText import io.github.droidkaigi.confsched2023.designsystem.preview.MultiLanguagePreviews import io.github.droidkaigi.confsched2023.designsystem.preview.MultiThemePreviews import io.github.droidkaigi.confsched2023.designsystem.theme.KaigiTheme +import io.github.droidkaigi.confsched2023.model.TimetableItem.Session +import io.github.droidkaigi.confsched2023.model.fake +import io.github.droidkaigi.confsched2023.model.nameAndFloor import io.github.droidkaigi.confsched2023.sessions.SessionsStrings @Composable @@ -28,6 +32,7 @@ fun TimetableItemDetailSummaryCardRow( content: String, modifier: Modifier = Modifier, leadingIconContentDescription: String? = null, + onContentClick: (() -> Unit)? = null, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -46,7 +51,16 @@ fun TimetableItemDetailSummaryCardRow( color = MaterialTheme.colorScheme.onSurfaceVariant, ) Spacer(modifier = Modifier.width(12.dp)) - Text(text = content, style = MaterialTheme.typography.bodyMedium) + if (onContentClick == null) { + Text(text = content, style = MaterialTheme.typography.bodyMedium) + } else { + ClickableLinkText( + style = MaterialTheme.typography.bodyMedium, + content = content, + onLinkClick = { _ -> onContentClick() }, + regex = ".*".toRegex(), + ) + } } } @@ -64,3 +78,18 @@ fun TimetableItemDetailSummaryCardRowPreview() { } } } + +@MultiThemePreviews +@Composable +fun TimetableItemDetailSummaryCardRowRoomPreview() { + KaigiTheme { + Surface { + TimetableItemDetailSummaryCardRow( + leadingIcon = Icons.Outlined.Schedule, + label = SessionsStrings.Place.asString(), + content = Session.fake().room.nameAndFloor, + onContentClick = {}, + ) + } + } +} diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableItemDetail.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableItemDetail.kt index 278ea8adc..4f5218b49 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableItemDetail.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableItemDetail.kt @@ -21,6 +21,7 @@ internal fun TimetableItemDetail( contentPadding: PaddingValues, selectedLanguage: Lang?, onLinkClick: (String) -> Unit, + onRoomClick: () -> Unit, modifier: Modifier = Modifier, ) { LazyColumn( @@ -31,6 +32,7 @@ internal fun TimetableItemDetail( TimetableItemDetailSummaryCard( modifier = Modifier.padding(horizontal = 16.dp, vertical = 20.dp), timetableItem = uiState.timetableItem, + onRoomClick = onRoomClick, ) }