Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ [Compose Multiplatform] When you tap the action label on the snack bar that appears when you don't have permission to access the calendar, the iOS Settings app will now open. #907

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="permission_required">セッションを予定として追加するには、カレンダーへのアクセス権限が必要です。</string>
<string name="open_settings">設定を開く</string>
</resources>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="permission_required">To add a session as a scheduled event, you need access permission to the calendar.</string>
<string name="open_settings">Open settings.</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -142,6 +146,7 @@ fun KaigiApp(
snackbarHostState: SnackbarHostState,
onLicenseScreenRequest: () -> Unit,
modifier: Modifier = Modifier,
externalNavController: ExternalNavController = rememberExternalNavController(),
) {
val eventFlow = rememberEventFlow<IosComposeKaigiAppEvent>()
val uiState = iosComposeKaigiAppPresenter(events = eventFlow)
Expand All @@ -151,6 +156,13 @@ fun KaigiApp(
userMessageStateHolder = uiState.userMessageStateHolder,
)

LaunchedEffect(uiState.shouldGoToSettingsApp) {
if (uiState.shouldGoToSettingsApp) {
eventFlow.tryEmit(IosComposeKaigiAppEvent.SettingsAppNavigated)
externalNavController.navigateToSettingsApp()
}
}

KaigiTheme(
fontFamily = fontFamily,
) {
Expand All @@ -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,
)
)
}
Expand All @@ -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(
Expand Down Expand Up @@ -357,7 +373,7 @@ private fun rememberExternalNavController(): ExternalNavController {
}
}

private class ExternalNavController(
class ExternalNavController(
private val shareNavigator: ShareNavigator,
private val coroutineScope: CoroutineScope,
) {
Expand Down Expand Up @@ -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" +
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IosComposeKaigiAppEvent>
) : 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,
)
}
Loading