feat: Integrate notification management and preferences across platforms (#4819)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-16 20:17:34 -05:00 committed by GitHub
parent 0b2e89c46f
commit 8c964a15ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1304 additions and 61 deletions

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.prefs.notification
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.repository.NotificationPrefs
class NotificationPrefsTest {
@get:Rule val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
private lateinit var dataStore: DataStore<Preferences>
private lateinit var notificationPrefs: NotificationPrefs
private lateinit var dispatchers: CoroutineDispatchers
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setup() {
dataStore =
PreferenceDataStoreFactory.create(
scope = testScope,
produceFile = { tmpFolder.newFile("test.preferences_pb") },
)
dispatchers = mockk { every { default } returns testDispatcher }
notificationPrefs = NotificationPrefsImpl(dataStore, dispatchers)
}
@Test
fun `messagesEnabled defaults to true`() = testScope.runTest { assertTrue(notificationPrefs.messagesEnabled.value) }
@Test
fun `nodeEventsEnabled defaults to true`() =
testScope.runTest { assertTrue(notificationPrefs.nodeEventsEnabled.value) }
@Test
fun `lowBatteryEnabled defaults to true`() =
testScope.runTest { assertTrue(notificationPrefs.lowBatteryEnabled.value) }
@Test
fun `setting messagesEnabled updates preference`() = testScope.runTest {
notificationPrefs.setMessagesEnabled(false)
assertFalse(notificationPrefs.messagesEnabled.value)
}
@Test
fun `setting nodeEventsEnabled updates preference`() = testScope.runTest {
notificationPrefs.setNodeEventsEnabled(false)
assertFalse(notificationPrefs.nodeEventsEnabled.value)
}
@Test
fun `setting lowBatteryEnabled updates preference`() = testScope.runTest {
notificationPrefs.setLowBatteryEnabled(false)
assertFalse(notificationPrefs.lowBatteryEnabled.value)
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.prefs.notification
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.repository.NotificationPrefs
@Single
class NotificationPrefsImpl(
@Named("UiDataStore") private val dataStore: DataStore<Preferences>,
dispatchers: CoroutineDispatchers,
) : NotificationPrefs {
private val scope = CoroutineScope(SupervisorJob() + dispatchers.default)
override val messagesEnabled: StateFlow<Boolean> =
dataStore.data.map { it[KEY_MESSAGES_ENABLED] ?: true }.stateIn(scope, SharingStarted.Eagerly, true)
override fun setMessagesEnabled(enabled: Boolean) {
scope.launch { dataStore.edit { it[KEY_MESSAGES_ENABLED] = enabled } }
}
override val nodeEventsEnabled: StateFlow<Boolean> =
dataStore.data.map { it[KEY_NODE_EVENTS_ENABLED] ?: true }.stateIn(scope, SharingStarted.Eagerly, true)
override fun setNodeEventsEnabled(enabled: Boolean) {
scope.launch { dataStore.edit { it[KEY_NODE_EVENTS_ENABLED] = enabled } }
}
override val lowBatteryEnabled: StateFlow<Boolean> =
dataStore.data.map { it[KEY_LOW_BATTERY_ENABLED] ?: true }.stateIn(scope, SharingStarted.Eagerly, true)
override fun setLowBatteryEnabled(enabled: Boolean) {
scope.launch { dataStore.edit { it[KEY_LOW_BATTERY_ENABLED] = enabled } }
}
private companion object {
val KEY_MESSAGES_ENABLED = booleanPreferencesKey("notif_messages_enabled")
val KEY_NODE_EVENTS_ENABLED = booleanPreferencesKey("notif_node_events_enabled")
val KEY_LOW_BATTERY_ENABLED = booleanPreferencesKey("notif_low_battery_enabled")
}
}