Skip to content

Commit

Permalink
Merge pull request DroidKaigi#907 from Corvus400/feature/open_setting…
Browse files Browse the repository at this point in the history
…s_app_from_timetable_detail_snack_bar_compose_multiplatform

✨ [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.
  • Loading branch information
takahirom authored Sep 1, 2024
2 parents c8b8910 + 9806779 commit cffab40
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 10 deletions.
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,
)
}

0 comments on commit cffab40

Please sign in to comment.