Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ materialkolor = "2.0.0"
multiplatform-settings = "1.3.0"
nav-compose = "2.8.9"
okio = "3.10.2"
permissions = "0.19.1"
permissionsCompose = "0.19.1"
permissionsNotifications = "0.19.1"
protolayout = "1.3.0-alpha10"
robolectric = "4.14.1"
room = "2.6.1"
Expand Down Expand Up @@ -86,6 +89,9 @@ apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime" }
apollo-execution-spring = { module = "com.apollographql.execution:apollo-execution-spring", version.ref = "apollo-kotlin-execution" }
apollo-execution-reporting = { module = "com.apollographql.execution:apollo-execution-reporting", version.ref = "apollo-kotlin-execution" }
apollo-execution-gradle-plugin = { module = "com.apollographql.execution:apollo-execution-gradle-plugin", version.ref = "apollo-kotlin-execution" }
permissions-compose = { module = "dev.icerock.moko:permissions-compose", version.ref = "permissionsCompose" }
permissions-notifications = { module = "dev.icerock.moko:permissions-notifications", version.ref = "permissionsNotifications" }
permissions = { module = "dev.icerock.moko:permissions", version.ref = "permissions" }
spring-boot = { module = "org.springframework.boot:spring-boot", version.ref = "spring" }
spring-boot-starter-logging = { module = "org.springframework.boot:spring-boot-starter-logging", version.ref = "spring" }
spring-webflux = "org.springframework:spring-webflux:6.2.5"
Expand Down
5 changes: 5 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ kotlin {
dependencies {
api(libs.firebase.mpp.auth)
api(libs.apollo.normalized.cache.sqlite)

implementation(libs.permissions)
implementation(libs.permissions.notifications)
implementation(libs.permissions.compose)
}
}

Expand All @@ -121,6 +125,7 @@ kotlin {

androidMain {
dependsOn(mobileMain)

dependencies {
api(project(":proto"))
api(libs.androidx.lifecycle.viewmodel.ktx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class AppSettings(val settings: FlowSettings) {
)
}

val notificationsActiveFlow: Flow<Boolean>
get() = experimentalFeaturesEnabledFlow

val experimentalFeaturesEnabledFlow = settings
.getBooleanFlow(EXPERIMENTAL_FEATURES_ENABLED, false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.arkivanov.decompose.childContext
import com.arkivanov.decompose.value.Value
import com.arkivanov.essenty.lifecycle.doOnStart
import conferenceDateFormat
import dev.johnoreilly.confetti.AppSettings
import dev.johnoreilly.confetti.ConfettiRepository
import dev.johnoreilly.confetti.GetBookmarksQuery
import dev.johnoreilly.confetti.GetConferenceDataQuery
Expand Down Expand Up @@ -140,6 +141,7 @@ class SessionsSimpleComponent(
private val searchQuery = MutableStateFlow("")
private val isRefreshing = MutableStateFlow(false)
private val selectedSessionId = MutableStateFlow<String?>(null)
private val appSettings: AppSettings by inject()

val uiState: StateFlow<SessionsUiState> =
combineUiState()
Expand Down Expand Up @@ -251,6 +253,7 @@ class SessionsSimpleComponent(
isRefreshing,
searchQuery,
selectedSessionId,
appSettings.notificationsActiveFlow,
::uiStates
)
}
Expand All @@ -265,6 +268,7 @@ class SessionsSimpleComponent(
isRefreshing: Boolean,
searchString: String,
selectedSessionId: String?,
notificationsActive: Boolean,
): SessionsUiState {
val bookmarksResponse = refreshData.bookmarksResponse
val sessionsResponse = refreshData.sessionsResponse
Expand Down Expand Up @@ -317,6 +321,7 @@ class SessionsSimpleComponent(
isRefreshing = isRefreshing,
searchString = searchString,
selectedSessionId = selectedSessionId,
notificationsActive = notificationsActive
)
}
}
Expand All @@ -340,5 +345,6 @@ sealed interface SessionsUiState {
val isRefreshing: Boolean,
val searchString: String,
val selectedSessionId: String?,
val notificationsActive: Boolean
) : SessionsUiState
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.johnoreilly.confetti.permissions

sealed interface NotificationPermissionState {
fun maybeRequest() {}

data object NotApplicable: NotificationPermissionState

object NotDetermined: NotificationPermissionState

class Requestable(private val onRequest: () -> Unit): NotificationPermissionState {
override fun maybeRequest() {
onRequest()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dev.johnoreilly.confetti.permissions

import androidx.compose.runtime.Composable

@Composable
expect fun rememberNotificationPermissionState(notificationsActive: Boolean?): NotificationPermissionState
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import dev.johnoreilly.confetti.decompose.SessionsComponent
import dev.johnoreilly.confetti.decompose.SessionsUiState
import dev.johnoreilly.confetti.permissions.rememberNotificationPermissionState
import dev.johnoreilly.confetti.ui.HomeScaffold
import dev.johnoreilly.confetti.utils.isExpanded
import kotlinx.coroutines.flow.receiveAsFlow
Expand All @@ -26,6 +27,9 @@ fun SessionsUI(
) {
val uiState by component.uiState.subscribeAsState()

val notificationPermissionState =
rememberNotificationPermissionState((uiState as? SessionsUiState.Success)?.notificationsActive)

val title = (uiState as? SessionsUiState.Success)?.conferenceName ?: ""
HomeScaffold(
title = title,
Expand All @@ -48,7 +52,11 @@ fun SessionsUI(
SessionListView(
uiState = uiState,
sessionSelected = component::onSessionClicked,
addBookmark = component::addBookmark,
addBookmark = {
// Bookmarks might be sent as notifications
notificationPermissionState.maybeRequest()
component.addBookmark(it)
},
removeBookmark = component::removeBookmark,
onRefresh = component::refresh,
onNavigateToSignIn = component::onSignInClicked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
Expand Down Expand Up @@ -55,6 +54,7 @@ import dev.johnoreilly.confetti.decompose.DarkThemeConfig
import dev.johnoreilly.confetti.decompose.DeveloperSettings
import dev.johnoreilly.confetti.decompose.SettingsComponent
import dev.johnoreilly.confetti.decompose.UserEditableSettings
import dev.johnoreilly.confetti.permissions.rememberNotificationPermissionState
import org.jetbrains.compose.resources.stringResource

@Composable
Expand Down Expand Up @@ -150,6 +150,29 @@ fun SettingsUI(
}
}
}

if (developerSettings != null && supportsNotifications) {
item {
val notificationPermissionState =
rememberNotificationPermissionState(userEditableSettings?.useExperimentalFeatures)

Column(modifier = Modifier.padding(8.dp)) {
Button(
onClick = { notificationPermissionState.maybeRequest() },
) {
Text("Request Notification Permission")
}
}
}

// item {
// Column(modifier = Modifier.padding(8.dp)) {
// Button(onClick = { controller.openAppSettings() }) {
// Text("App Notification Settings")
// }
// }
// }
}
}

HorizontalDivider()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.johnoreilly.confetti.permissions

import androidx.compose.runtime.Composable

@Composable
actual fun rememberNotificationPermissionState(notificationsActive: Boolean?): NotificationPermissionState {
return NotificationPermissionState.NotApplicable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.johnoreilly.confetti.permissions

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import dev.icerock.moko.permissions.Permission
import dev.icerock.moko.permissions.PermissionState
import dev.icerock.moko.permissions.PermissionsController
import dev.icerock.moko.permissions.compose.BindEffect
import dev.icerock.moko.permissions.compose.PermissionsControllerFactory
import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory
import dev.icerock.moko.permissions.notifications.REMOTE_NOTIFICATION
import kotlinx.coroutines.launch

@Composable
actual fun rememberNotificationPermissionState(notificationsActive: Boolean?): NotificationPermissionState {
return when (notificationsActive) {
true -> {
val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
val controller: PermissionsController = remember(factory) { factory.createPermissionsController() }
BindEffect(controller)
val coroutineScope = rememberCoroutineScope()
return remember(coroutineScope) {
NotificationPermissionState.Requestable {
coroutineScope.launch {
val permissionState = controller.getPermissionState(Permission.REMOTE_NOTIFICATION)
when (permissionState) {
PermissionState.NotDetermined, PermissionState.NotGranted -> {
controller.providePermission(Permission.REMOTE_NOTIFICATION)
}

else -> {}
}

}
}
}
}
false -> NotificationPermissionState.NotApplicable
else -> NotificationPermissionState.NotDetermined
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.johnoreilly.confetti.permissions

import androidx.compose.runtime.Composable

@Composable
actual fun rememberNotificationPermissionState(notificationsActive: Boolean?): NotificationPermissionState {
return NotificationPermissionState.NotApplicable
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ fun SessionListViewPreview() {
isRefreshing = false,
searchString = "",
selectedSessionId = null,
notificationsActive = false,
),
sessionSelected = {},
addBookmark = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class SessionsScreenTest(override val device: WearDevice) : BaseScreenshotTest()
listOf(),
setOf(),
false, "",
null
null,
false
)

@Test
Expand Down
Loading