diff --git a/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml b/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml index 04ed06b08..ca02fc4a3 100644 --- a/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml +++ b/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml @@ -1,4 +1,5 @@ セッションを予定として追加するには、カレンダーへのアクセス権限が必要です。 + 設定を開く diff --git a/app-ios-shared/src/commonMain/composeResources/values/strings.xml b/app-ios-shared/src/commonMain/composeResources/values/strings.xml index b6e4f8499..e758198ee 100644 --- a/app-ios-shared/src/commonMain/composeResources/values/strings.xml +++ b/app-ios-shared/src/commonMain/composeResources/values/strings.xml @@ -1,4 +1,5 @@ To add a session as a scheduled event, you need access permission to the calendar. + Open settings. diff --git a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt index c91f03397..7fcbe7e53 100644 --- a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt +++ b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -22,6 +23,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import co.touchlab.kermit.Logger import conference_app_2024.app_ios_shared.generated.resources.permission_required +import conference_app_2024.app_ios_shared.generated.resources.open_settings import io.github.droidkaigi.confsched.about.aboutScreen import io.github.droidkaigi.confsched.about.aboutScreenRoute import io.github.droidkaigi.confsched.about.navigateAboutScreen @@ -93,11 +95,13 @@ import platform.Foundation.NSDate import platform.Foundation.NSURL import platform.Foundation.dateWithTimeIntervalSince1970 import platform.UIKit.UIApplication +import platform.UIKit.UIApplicationOpenSettingsURLString import platform.UIKit.UIViewController import platform.darwin.NSObject data class IosComposeKaigiAppUiState( val userMessageStateHolder: UserMessageStateHolder, + val shouldGoToSettingsApp: Boolean, ) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @@ -142,6 +146,7 @@ fun KaigiApp( snackbarHostState: SnackbarHostState, onLicenseScreenRequest: () -> Unit, modifier: Modifier = Modifier, + externalNavController: ExternalNavController = rememberExternalNavController(), ) { val eventFlow = rememberEventFlow() val uiState = iosComposeKaigiAppPresenter(events = eventFlow) @@ -151,6 +156,13 @@ fun KaigiApp( userMessageStateHolder = uiState.userMessageStateHolder, ) + LaunchedEffect(uiState.shouldGoToSettingsApp) { + if (uiState.shouldGoToSettingsApp) { + eventFlow.tryEmit(IosComposeKaigiAppEvent.SettingsAppNavigated) + externalNavController.navigateToSettingsApp() + } + } + KaigiTheme( fontFamily = fontFamily, ) { @@ -159,13 +171,17 @@ fun KaigiApp( color = MaterialTheme.colorScheme.background, ) { val snackbarMessage = stringResource(AppIosSharedRes.string.permission_required) + val snackbarActionLabel = stringResource(AppIosSharedRes.string.open_settings) + KaigiNavHost( windowSize = windowSize, + externalNavController = externalNavController, onLicenseScreenRequest = onLicenseScreenRequest, onAccessCalendarIsDenied = { eventFlow.tryEmit( IosComposeKaigiAppEvent.ShowRequiresAuthorization( snackbarMessage = snackbarMessage, + actionLabel = snackbarActionLabel, ) ) } @@ -177,10 +193,10 @@ fun KaigiApp( @Composable private fun KaigiNavHost( windowSize: WindowSizeClass, + externalNavController: ExternalNavController, onLicenseScreenRequest: () -> Unit, onAccessCalendarIsDenied: () -> Unit, navController: NavHostController = rememberNavController(), - externalNavController: ExternalNavController = rememberExternalNavController(), ) { NavHostWithSharedAxisX(navController = navController, startDestination = mainScreenRoute) { mainScreen( @@ -357,7 +373,7 @@ private fun rememberExternalNavController(): ExternalNavController { } } -private class ExternalNavController( +class ExternalNavController( private val shareNavigator: ShareNavigator, private val coroutineScope: CoroutineScope, ) { @@ -430,6 +446,13 @@ private class ExternalNavController( } } + fun navigateToSettingsApp() { + val settingsUrl = NSURL.URLWithString(UIApplicationOpenSettingsURLString) + if (settingsUrl != null) { + UIApplication.sharedApplication.openURL(settingsUrl) + } + } + fun onShareClick(timetableItem: TimetableItem) { shareNavigator.share( "[${timetableItem.room.name.currentLangTitle}] ${timetableItem.startsTimeString} - ${timetableItem.endsTimeString}\n" + diff --git a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt index db59c70e0..92ae2c93c 100644 --- a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt +++ b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt @@ -1,34 +1,51 @@ package io.github.droidkaigi.confsched.shared 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 io.github.droidkaigi.confsched.compose.EventEffect import io.github.droidkaigi.confsched.compose.EventFlow +import io.github.droidkaigi.confsched.droidkaigiui.UserMessageResult.ActionPerformed import io.github.droidkaigi.confsched.droidkaigiui.providePresenterDefaults +import io.github.droidkaigi.confsched.shared.IosComposeKaigiAppEvent.SettingsAppNavigated import io.github.droidkaigi.confsched.shared.IosComposeKaigiAppEvent.ShowRequiresAuthorization sealed interface IosComposeKaigiAppEvent { - val snackbarMessage: String - data class ShowRequiresAuthorization( - override val snackbarMessage: String, + val snackbarMessage: String, + val actionLabel: String, ) : IosComposeKaigiAppEvent + + data object SettingsAppNavigated : IosComposeKaigiAppEvent } @Composable fun iosComposeKaigiAppPresenter( events: EventFlow ) : IosComposeKaigiAppUiState = providePresenterDefaults { userMessageStateHolder -> + var shouldGoToSettingsApp by remember { mutableStateOf(false) } + EventEffect(events) { event -> when (event) { is ShowRequiresAuthorization -> { - userMessageStateHolder.showMessage( + val result = userMessageStateHolder.showMessage( message = event.snackbarMessage, - // TODO Add code to transition to the settings screen when the action button is pressed. - // TODO Perhaps UIApplication.openSettingsURLString can be used to achieve this. - actionLabel = null, + actionLabel = event.actionLabel, ) + if (result == ActionPerformed) { + shouldGoToSettingsApp = true + } + } + + SettingsAppNavigated -> { + shouldGoToSettingsApp = false } } } - IosComposeKaigiAppUiState(userMessageStateHolder) + IosComposeKaigiAppUiState( + userMessageStateHolder = userMessageStateHolder, + shouldGoToSettingsApp = shouldGoToSettingsApp, + ) }