Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ kotlin {
api("com.mikepenz:multiplatform-markdown-renderer-m3:0.27.0")
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0")

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

api(libs.generativeai)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ class SessionNotificationSender(
override suspend fun sendNotification(selector: Selector) {
val notificationsEnabled = notificationManager.areNotificationsEnabled()

println("notificationsEnabled")

if (!notificationsEnabled) {
// return
return
}

// If there is no signed-in user, skip.
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,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
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
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 @@ -51,10 +50,14 @@ import confetti.shared.generated.resources.settings_boolean_false
import confetti.shared.generated.resources.settings_boolean_true
import confetti.shared.generated.resources.settings_title
import confetti.shared.generated.resources.use_experimental_features
import dev.icerock.moko.permissions.PermissionsController
import dev.icerock.moko.permissions.compose.PermissionsControllerFactory
import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory
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 @@ -87,6 +90,10 @@ fun SettingsUI(
supportsNotifications: Boolean,
popBack: () -> Unit
) {
val factory: PermissionsControllerFactory = rememberPermissionsControllerFactory()
val controller: PermissionsController =
remember(factory) { factory.createPermissionsController() }

val scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
/**
* usePlatformDefaultWidth = false is use as a temporary fix to allow
Expand Down Expand Up @@ -150,6 +157,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
Expand Up @@ -104,6 +104,7 @@ fun SessionListViewPreview() {
isRefreshing = false,
searchString = "",
selectedSessionId = null,
notificationsActive = false,
),
sessionSelected = {},
addBookmark = {},
Expand Down
Loading