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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.google.firebase.crashlytics.setCustomKeys
import com.google.firebase.ktx.Firebase
import dev.johnoreilly.confetti.di.appModule
import dev.johnoreilly.confetti.di.initKoin
import dev.johnoreilly.confetti.work.SessionNotificationSender
import dev.johnoreilly.confetti.work.SessionNotificationWorker
import dev.johnoreilly.confetti.work.setupDailyRefresh
import kotlinx.coroutines.launch
Expand All @@ -33,12 +34,12 @@ class ConfettiApplication : Application() {

if (isFirebaseInstalled) {
if (!BuildConfig.DEBUG) {
Firebase.crashlytics.setCrashlyticsCollectionEnabled(true)
Firebase.crashlytics.isCrashlyticsCollectionEnabled = true
Firebase.crashlytics.setCustomKeys {
key("appName", "androidApp")
}
} else {
Firebase.crashlytics.setCrashlyticsCollectionEnabled(false)
Firebase.crashlytics.isCrashlyticsCollectionEnabled = false
}
}

Expand All @@ -54,13 +55,7 @@ class ConfettiApplication : Application() {
setupDailyRefresh(workManager)

ProcessLifecycleOwner.get().lifecycleScope.launch {
get<AppSettings>().experimentalFeaturesEnabledFlow.collect { isEnabled ->
if (isEnabled) {
SessionNotificationWorker.startPeriodicWorkRequest(workManager)
} else {
SessionNotificationWorker.cancelWorkRequest(workManager)
}
}
get<SessionNotificationSender>().updateSchedule()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import android.content.Context
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationManagerCompat
import androidx.work.WorkManager
import com.apollographql.cache.normalized.FetchPolicy
import dev.johnoreilly.confetti.AppSettings
import dev.johnoreilly.confetti.ConfettiRepository
import dev.johnoreilly.confetti.auth.Authentication
import dev.johnoreilly.confetti.notifications.SessionNotificationBuilder
import dev.johnoreilly.confetti.notifications.SummaryNotificationBuilder
import dev.johnoreilly.confetti.utils.DateService
import dev.johnoreilly.confetti.work.NotificationSender.Selector
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.random.Random

class SessionNotificationSender(
Expand All @@ -21,7 +25,10 @@ class SessionNotificationSender(
private val dateService: DateService,
private val notificationManager: NotificationManagerCompat,
private val authentication: Authentication,
): NotificationSender {
private val appSettings: AppSettings,
private val coroutineScope: CoroutineScope,
private val workManager: WorkManager,
) : NotificationSender {
private val sessionNotificationBuilder = SessionNotificationBuilder(context)
private val summaryNotificationBuilder = SummaryNotificationBuilder(context)

Expand Down Expand Up @@ -85,13 +92,19 @@ class SessionNotificationSender(

// If there are multiple notifications, we create a summary to group them.
if (upcomingSessions.count() > 1) {
sendNotification(SUMMARY_ID, summaryNotificationBuilder.createSummaryNotification(upcomingSessions, SUMMARY_ID).build())
sendNotification(
SUMMARY_ID,
summaryNotificationBuilder.createSummaryNotification(upcomingSessions, SUMMARY_ID).build()
)
}

// We reverse the sessions to show early sessions first.
for (session in upcomingSessions.reversed()) {
val notificationId = Random.nextInt(Integer.MAX_VALUE / 2, Integer.MAX_VALUE)
sendNotification(notificationId, sessionNotificationBuilder.createNotification(session, conferenceId, notificationId).build())
sendNotification(
notificationId,
sessionNotificationBuilder.createNotification(session, conferenceId, notificationId).build()
)
}
}

Expand All @@ -110,6 +123,18 @@ class SessionNotificationSender(
}
}

override suspend fun updateSchedule() {
updateSchedule(appSettings.notificationsEnabledFlow.first())
}

override fun updateSchedule(enabled: Boolean) {
if (enabled) {
SessionNotificationWorker.startPeriodicWorkRequest(workManager)
} else {
SessionNotificationWorker.cancelWorkRequest(workManager)
}
}

companion object {
internal val CHANNEL_ID = "SessionNotification"
internal val GROUP = "dev.johnoreilly.confetti.SESSIONS_ALERT"
Expand Down
1 change: 1 addition & 0 deletions shared/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<string name="dynamic_color_no">No</string>
<string name="dismiss_dialog_button_text">OK</string>
<string name="use_experimental_features">Use Experimental Features</string>
<string name="enable_notifications">Enable Notifications</string>
<string name="settings_boolean_true">Yes</string>
<string name="settings_boolean_false">No</string>
<string name="developerSettings">Developer Settings\n</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class AppSettings(val settings: FlowSettings) {
)
}

val notificationsActiveFlow: Flow<Boolean>
get() = experimentalFeaturesEnabledFlow
val notificationsEnabledFlow: Flow<Boolean> = settings
.getBooleanFlow(NOTIFICATIONS_ENABLED, false)

val experimentalFeaturesEnabledFlow = settings
.getBooleanFlow(EXPERIMENTAL_FEATURES_ENABLED, false)
Expand All @@ -37,6 +37,10 @@ class AppSettings(val settings: FlowSettings) {
settings.putBoolean(EXPERIMENTAL_FEATURES_ENABLED, value)
}

suspend fun setNotificationsEnabled(value: Boolean) {
settings.putBoolean(NOTIFICATIONS_ENABLED, value)
}

suspend fun getConference(): String {
return settings.getStringFlow(CONFERENCE_SETTING, CONFERENCE_NOT_SET).first()
}
Expand Down Expand Up @@ -70,6 +74,7 @@ class AppSettings(val settings: FlowSettings) {
companion object {
const val DEVELOPER_MODE = "developer_mode"
const val EXPERIMENTAL_FEATURES_ENABLED = "experimental_features_enabled"
const val NOTIFICATIONS_ENABLED = "notifications_enabled"
const val ENABLED_LANGUAGES_SETTING = "enabled_languages_2"
const val CONFERENCE_SETTING = "conference"
const val CONFERENCE_THEME_COLOR_SETTING = "conferenceThemeColor"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class SessionsSimpleComponent(
isRefreshing,
searchQuery,
selectedSessionId,
appSettings.notificationsActiveFlow,
appSettings.notificationsEnabledFlow,
::uiStates
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
@file:OptIn(ExperimentalSettingsApi::class, ExperimentalCoroutinesApi::class)

package dev.johnoreilly.confetti.decompose

import com.arkivanov.decompose.ComponentContext
import com.russhwolf.settings.ExperimentalSettingsApi
import dev.johnoreilly.confetti.AppSettings
import dev.johnoreilly.confetti.auth.Authentication
import dev.johnoreilly.confetti.work.NotificationSender
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
Expand All @@ -23,6 +27,7 @@ data class DeveloperSettings(
data class UserEditableSettings(
val darkThemeConfig: DarkThemeConfig,
val useExperimentalFeatures: Boolean,
val notificationsEnabled: Boolean,
)

enum class ThemeBrand {
Expand All @@ -40,6 +45,7 @@ interface SettingsComponent {

fun updateDarkThemeConfig(darkThemeConfig: DarkThemeConfig)
fun updateUseExperimentalFeatures(value: Boolean)
fun updateNotificationsEnabled(value: Boolean)
fun enableDeveloperMode()
fun sendNotifications()
val supportsNotifications: Boolean
Expand Down Expand Up @@ -73,10 +79,12 @@ class DefaultSettingsComponent(
combine(
settings.getStringFlow(darkThemeConfigKey, DarkThemeConfig.FOLLOW_SYSTEM.toString()),
appSettings.experimentalFeaturesEnabledFlow,
) { darkThemeConfig, useExperimentalFeatures ->
appSettings.notificationsEnabledFlow,
) { darkThemeConfig, useExperimentalFeatures, useNotifications ->
UserEditableSettings(
useExperimentalFeatures = useExperimentalFeatures,
darkThemeConfig = DarkThemeConfig.valueOf(darkThemeConfig),
notificationsEnabled = useNotifications
)
}.stateIn(
scope = coroutineScope,
Expand All @@ -96,6 +104,12 @@ class DefaultSettingsComponent(
}
}

override fun updateNotificationsEnabled(value: Boolean) {
coroutineScope.launch {
appSettings.setNotificationsEnabled(value)
notificationSender?.updateSchedule(value)
}
}

override fun enableDeveloperMode() {
coroutineScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import confetti.shared.generated.resources.dark_mode_config_light
import confetti.shared.generated.resources.dark_mode_config_system_default
import confetti.shared.generated.resources.dark_mode_preference
import confetti.shared.generated.resources.developerSettings
import confetti.shared.generated.resources.enable_notifications
import confetti.shared.generated.resources.settings_boolean_false
import confetti.shared.generated.resources.settings_boolean_true
import confetti.shared.generated.resources.settings_title
Expand All @@ -72,6 +73,7 @@ fun SettingsUI(
onEnableDeveloperMode = component::enableDeveloperMode,
onSendNotifications = component::sendNotifications,
supportsNotifications = component.supportsNotifications,
onNotificationsEnabled = component::updateNotificationsEnabled,
popBack = popBack
)
}
Expand All @@ -85,6 +87,7 @@ fun SettingsUI(
onEnableDeveloperMode: () -> Unit,
onSendNotifications: () -> Unit,
supportsNotifications: Boolean,
onNotificationsEnabled: (value: Boolean) -> Unit,
popBack: () -> Unit
) {
val scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Expand Down Expand Up @@ -131,6 +134,8 @@ fun SettingsUI(
settings = userEditableSettings,
onChangeDarkThemeConfig = onChangeDarkThemeConfig,
onChangeUseExperimentalFeatures = onChangeUseExperimentalFeatures,
onChangeNotificationsEnabled = onNotificationsEnabled,
supportsNotifications = supportsNotifications,
)
}
}
Expand Down Expand Up @@ -210,10 +215,19 @@ fun SettingsUI(
@Composable
private fun SettingsPanel(
settings: UserEditableSettings?,
supportsNotifications: Boolean,
onChangeUseExperimentalFeatures: (value: Boolean) -> Unit,
onChangeDarkThemeConfig: (darkThemeConfig: DarkThemeConfig) -> Unit,
onChangeNotificationsEnabled: (value: Boolean) -> Unit,
) {
if (settings != null) {
BooleanSettings(
title = stringResource(Res.string.enable_notifications),
value = settings.notificationsEnabled,
onValueChange = { value -> onChangeNotificationsEnabled(value) },
enabled = supportsNotifications
)

BooleanSettings(
title = stringResource(Res.string.use_experimental_features),
value = settings.useExperimentalFeatures,
Expand Down Expand Up @@ -246,18 +260,21 @@ private fun BooleanSettings(
title: String,
value: Boolean,
onValueChange: (Boolean) -> Unit,
enabled: Boolean = true,
) {
SettingsDialogSectionTitle(text = title)
Column(Modifier.selectableGroup()) {
SettingsDialogThemeChooserRow(
text = stringResource(Res.string.settings_boolean_true),
selected = value,
onClick = { onValueChange(true) },
enabled = enabled,
)
SettingsDialogThemeChooserRow(
text = stringResource(Res.string.settings_boolean_false),
selected = !value,
onClick = { onValueChange(false) },
enabled = enabled,
)
}
}
Expand All @@ -276,6 +293,7 @@ fun SettingsDialogThemeChooserRow(
text: String,
selected: Boolean,
onClick: () -> Unit,
enabled: Boolean = true,
) {
Row(
Modifier
Expand All @@ -284,13 +302,15 @@ fun SettingsDialogThemeChooserRow(
selected = selected,
role = Role.RadioButton,
onClick = onClick,
enabled = enabled,
)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = selected,
onClick = null,
enabled = enabled,
)
Spacer(Modifier.width(8.dp))
Text(text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ interface NotificationSender {
}

suspend fun sendNotification(selector: Selector = Today())

suspend fun updateSchedule() {}

fun updateSchedule(enabled: Boolean) {}
}
Loading