mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-07-08 20:16:24 +00:00
SettingsManager: use Flows instead of LiveData (#714)
* SettingsManager: use flows instead of LiveData * Fix tests
This commit is contained in:
parent
1cd0df1e6a
commit
b88c35169e
|
@ -149,6 +149,7 @@ dependencies {
|
|||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.fragment)
|
||||
implementation(libs.androidx.hilt.work)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.base)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.paging)
|
||||
|
|
|
@ -5,18 +5,15 @@
|
|||
package at.bitfire.davdroid.settings
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.lifecycle.map
|
||||
import at.bitfire.davdroid.TestUtils.getOrAwaitValue
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -62,51 +59,35 @@ class SettingsManagerTest {
|
|||
|
||||
|
||||
@Test
|
||||
fun test_getBooleanLive_initialValuePostedEvenWhenNull() {
|
||||
val live = settingsManager.getBooleanLive(SETTING_TEST).map { value ->
|
||||
value
|
||||
}
|
||||
assertNull(live.getOrAwaitValue())
|
||||
|
||||
// posts value to main thread, InstantTaskExecutorRule is required to execute it instantly
|
||||
settingsManager.putBoolean(SETTING_TEST, true)
|
||||
runBlocking(Dispatchers.Main) { // observeForever can't be run in background thread
|
||||
assertTrue(live.getOrAwaitValue()!!)
|
||||
fun test_observerFlow_initialValue() = runBlocking {
|
||||
var counter = 0
|
||||
val live = settingsManager.observerFlow {
|
||||
if (counter++ == 0)
|
||||
23
|
||||
else
|
||||
throw AssertionError("A second value was requested")
|
||||
}
|
||||
assertEquals(23, live.first())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getBooleanLive_getValue() {
|
||||
val live = settingsManager.getBooleanLive(SETTING_TEST)
|
||||
assertNull(live.value)
|
||||
|
||||
// posts value to main thread, InstantTaskExecutorRule is required to execute it instantly
|
||||
settingsManager.putBoolean(SETTING_TEST, true)
|
||||
runBlocking(Dispatchers.Main) { // observeForever can't be run in background thread
|
||||
assertTrue(live.getOrAwaitValue()!!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun test_ObserverCalledWhenValueChanges() {
|
||||
val value = CompletableDeferred<Int>()
|
||||
val observer = SettingsManager.OnChangeListener {
|
||||
value.complete(settingsManager.getInt(SETTING_TEST))
|
||||
}
|
||||
|
||||
try {
|
||||
settingsManager.addOnChangeListener(observer)
|
||||
settingsManager.putInt(SETTING_TEST, 123)
|
||||
|
||||
runBlocking {
|
||||
// wait until observer is called
|
||||
assertEquals(123, value.await())
|
||||
fun test_observerFlow_updatedValue() = runBlocking {
|
||||
var counter = 0
|
||||
val live = settingsManager.observerFlow {
|
||||
when (counter++) {
|
||||
0 -> {
|
||||
// update some setting so that we will be called a second time
|
||||
settingsManager.putBoolean(SETTING_TEST, true)
|
||||
// and emit initial value
|
||||
23
|
||||
}
|
||||
1 -> 42 // updated value
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
|
||||
} finally {
|
||||
settingsManager.removeOnChangeListener(observer)
|
||||
}
|
||||
|
||||
val result = live.take(2).toList()
|
||||
assertEquals(listOf(23, 42), result)
|
||||
}
|
||||
|
||||
}
|
|
@ -7,8 +7,9 @@ package at.bitfire.davdroid.settings
|
|||
import android.content.Context
|
||||
import android.util.NoSuchPropertyException
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.settings.SettingsManager.OnChangeListener
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.EntryPoint
|
||||
|
@ -16,6 +17,9 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import java.io.Writer
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.LinkedList
|
||||
|
@ -100,11 +104,34 @@ class SettingsManager internal constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Flow that
|
||||
*
|
||||
* - always emits the initial value of the setting, and then
|
||||
* - emits the new value whenever the setting changes.
|
||||
*
|
||||
* @param getValue used to determine the current value of the setting
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal fun<T> observerFlow(getValue: () -> T): Flow<T> = callbackFlow {
|
||||
// emit value on changes
|
||||
val listener = OnChangeListener {
|
||||
trySend(getValue())
|
||||
}
|
||||
addOnChangeListener(listener)
|
||||
|
||||
// get current value and emit it as first state
|
||||
trySend(getValue())
|
||||
|
||||
// wait and clean up
|
||||
awaitClose { removeOnChangeListener(listener) }
|
||||
}
|
||||
|
||||
|
||||
/*** SETTINGS ACCESS ***/
|
||||
|
||||
fun containsKey(key: String) = providers.any { it.contains(key) }
|
||||
fun containsKeyLive(key: String) = SettingLiveData { containsKey(key) }
|
||||
fun containsKeyFlow(key: String): Flow<Boolean> = observerFlow { containsKey(key) }
|
||||
|
||||
private fun<T> getValue(key: String, reader: (SettingsProvider) -> T?): T? {
|
||||
Logger.log.fine("Looking up setting $key")
|
||||
|
@ -126,17 +153,17 @@ class SettingsManager internal constructor(
|
|||
|
||||
fun getBooleanOrNull(key: String): Boolean? = getValue(key) { provider -> provider.getBoolean(key) }
|
||||
fun getBoolean(key: String): Boolean = getBooleanOrNull(key) ?: throw NoSuchPropertyException(key)
|
||||
fun getBooleanLive(key: String): LiveData<Boolean?> = SettingLiveData { getBooleanOrNull(key) }
|
||||
fun getBooleanFlow(key: String): Flow<Boolean?> = observerFlow { getBooleanOrNull(key) }
|
||||
|
||||
fun getIntOrNull(key: String): Int? = getValue(key) { provider -> provider.getInt(key) }
|
||||
fun getInt(key: String): Int = getIntOrNull(key) ?: throw NoSuchPropertyException(key)
|
||||
fun getIntLive(key: String): LiveData<Int?> = SettingLiveData { getIntOrNull(key) }
|
||||
fun getIntFlow(key: String): Flow<Int?> = observerFlow { getIntOrNull(key) }
|
||||
|
||||
fun getLongOrNull(key: String): Long? = getValue(key) { provider -> provider.getLong(key) }
|
||||
fun getLong(key: String) = getLongOrNull(key) ?: throw NoSuchPropertyException(key)
|
||||
|
||||
fun getString(key: String) = getValue(key) { provider -> provider.getString(key) }
|
||||
fun getStringLive(key: String): LiveData<String?> = SettingLiveData { getString(key) }
|
||||
fun getStringFlow(key: String): Flow<String?> = observerFlow { getString(key) }
|
||||
|
||||
|
||||
fun isWritable(key: String): Boolean {
|
||||
|
@ -175,33 +202,6 @@ class SettingsManager internal constructor(
|
|||
fun remove(key: String) = putString(key, null)
|
||||
|
||||
|
||||
inner class SettingLiveData<T>(
|
||||
val getValueOrNull: () -> T?
|
||||
): LiveData<T>(), OnChangeListener {
|
||||
private var hasValue = false
|
||||
|
||||
override fun onActive() {
|
||||
addOnChangeListener(this)
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
removeOnChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onSettingsChanged() {
|
||||
update()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun update() {
|
||||
val newValue = getValueOrNull()
|
||||
if (!hasValue || value != newValue)
|
||||
postValue(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*** HELPERS ***/
|
||||
|
||||
fun dump(writer: Writer) {
|
||||
|
|
|
@ -57,6 +57,8 @@ import androidx.core.content.getSystemService
|
|||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.preference.PreferenceManager
|
||||
import at.bitfire.cert4android.CustomCertStore
|
||||
|
@ -149,16 +151,16 @@ class AppSettingsActivity: AppCompatActivity() {
|
|||
)
|
||||
|
||||
AppSettings_Connection(
|
||||
proxyType = model.settings.getIntLive(Settings.PROXY_TYPE).observeAsState().value ?: Settings.PROXY_TYPE_NONE,
|
||||
proxyType = model.settings.getIntFlow(Settings.PROXY_TYPE).collectAsStateWithLifecycle(null).value ?: Settings.PROXY_TYPE_NONE,
|
||||
onProxyTypeUpdated = { model.settings.putInt(Settings.PROXY_TYPE, it) },
|
||||
proxyHostName = model.settings.getStringLive(Settings.PROXY_HOST).observeAsState(null).value,
|
||||
proxyHostName = model.settings.getStringFlow(Settings.PROXY_HOST).collectAsStateWithLifecycle(null).value,
|
||||
onProxyHostNameUpdated = { model.settings.putString(Settings.PROXY_HOST, it) },
|
||||
proxyPort = model.settings.getIntLive(Settings.PROXY_PORT).observeAsState(null).value,
|
||||
proxyPort = model.settings.getIntFlow(Settings.PROXY_PORT).collectAsStateWithLifecycle(null).value,
|
||||
onProxyPortUpdated = { model.settings.putInt(Settings.PROXY_PORT, it) }
|
||||
)
|
||||
|
||||
AppSettings_Security(
|
||||
distrustSystemCerts = model.settings.getBooleanLive(Settings.DISTRUST_SYSTEM_CERTIFICATES).observeAsState().value ?: false,
|
||||
distrustSystemCerts = model.settings.getBooleanFlow(Settings.DISTRUST_SYSTEM_CERTIFICATES).collectAsStateWithLifecycle(null).value ?: false,
|
||||
onDistrustSystemCertsUpdated = { model.settings.putBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES, it) },
|
||||
onResetCertificates = {
|
||||
model.resetCertificates()
|
||||
|
@ -170,7 +172,7 @@ class AppSettingsActivity: AppCompatActivity() {
|
|||
)
|
||||
|
||||
AppSettings_UserInterface(
|
||||
theme = model.settings.getIntLive(Settings.PREFERRED_THEME).observeAsState().value ?: Settings.PREFERRED_THEME_DEFAULT,
|
||||
theme = model.settings.getIntFlow(Settings.PREFERRED_THEME).collectAsStateWithLifecycle(null).value ?: Settings.PREFERRED_THEME_DEFAULT,
|
||||
onThemeSelected = {
|
||||
model.settings.putInt(Settings.PREFERRED_THEME, it)
|
||||
UiUtils.updateTheme(context)
|
||||
|
@ -184,7 +186,7 @@ class AppSettingsActivity: AppCompatActivity() {
|
|||
)
|
||||
|
||||
AppSettings_Integration(
|
||||
taskProvider = TaskUtils.currentProviderLive(context).observeAsState().value
|
||||
taskProvider = TaskUtils.currentProviderFlow(context, lifecycleScope).collectAsStateWithLifecycle().value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
|
@ -103,7 +105,7 @@ class TasksActivity: AppCompatActivity() {
|
|||
|
||||
}
|
||||
|
||||
val showAgain = settings.getBooleanLive(HINT_OPENTASKS_NOT_INSTALLED)
|
||||
val showAgain = settings.getBooleanFlow(HINT_OPENTASKS_NOT_INSTALLED)
|
||||
fun setShowAgain(showAgain: Boolean) {
|
||||
if (showAgain)
|
||||
settings.remove(HINT_OPENTASKS_NOT_INSTALLED)
|
||||
|
@ -111,7 +113,7 @@ class TasksActivity: AppCompatActivity() {
|
|||
settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false)
|
||||
}
|
||||
|
||||
val currentProvider = TaskUtils.currentProviderLive(context)
|
||||
val currentProvider = TaskUtils.currentProviderFlow(context, viewModelScope).asLiveData()
|
||||
val jtxSelected = currentProvider.map { it == TaskProvider.ProviderName.JtxBoard }
|
||||
val tasksOrgSelected = currentProvider.map { it == TaskProvider.ProviderName.TasksOrg }
|
||||
val openTasksSelected = currentProvider.map { it == TaskProvider.ProviderName.OpenTasks }
|
||||
|
@ -171,7 +173,7 @@ fun TasksCard(
|
|||
val openTasksInstalled by model.openTasksInstalled.observeAsState(false)
|
||||
val openTasksSelected by model.openTasksSelected.observeAsState(false)
|
||||
|
||||
val showAgain = model.showAgain.observeAsState().value ?: true
|
||||
val showAgain = model.showAgain.collectAsStateWithLifecycle(null).value ?: false
|
||||
|
||||
fun installApp(packageName: String) {
|
||||
val uri = Uri.parse("market://details?id=$packageName&referrer=" +
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -134,7 +135,7 @@ class AccountModel @AssistedInject constructor(
|
|||
)
|
||||
val addressBooksPager = CollectionPager(db, cardDavSvc, Collection.TYPE_ADDRESSBOOK, showOnlyPersonal)
|
||||
|
||||
private val tasksProvider = TaskUtils.currentProviderLive(context)
|
||||
private val tasksProvider = TaskUtils.currentProviderFlow(context, viewModelScope)
|
||||
val calDavSvc = db.serviceDao().getLiveByAccountAndType(account.name, Service.TYPE_CALDAV)
|
||||
val bindableCalendarHomesets = calDavSvc.switchMap { svc ->
|
||||
if (svc != null)
|
||||
|
@ -150,7 +151,7 @@ class AccountModel @AssistedInject constructor(
|
|||
return@switchMap null
|
||||
RefreshCollectionsWorker.exists(context, RefreshCollectionsWorker.workerName(svc.id))
|
||||
}
|
||||
val calDavSyncPending = tasksProvider.switchMap { tasks ->
|
||||
val calDavSyncPending = tasksProvider.asLiveData().switchMap { tasks ->
|
||||
BaseSyncWorker.exists(
|
||||
context,
|
||||
listOf(WorkInfo.State.ENQUEUED),
|
||||
|
@ -162,7 +163,7 @@ class AccountModel @AssistedInject constructor(
|
|||
}
|
||||
)
|
||||
}
|
||||
val calDavSyncing = tasksProvider.switchMap { tasks ->
|
||||
val calDavSyncing = tasksProvider.asLiveData().switchMap { tasks ->
|
||||
BaseSyncWorker.exists(
|
||||
context,
|
||||
listOf(WorkInfo.State.RUNNING),
|
||||
|
|
|
@ -46,6 +46,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.BuildConfig
|
||||
import at.bitfire.davdroid.Constants
|
||||
|
@ -102,7 +103,7 @@ class BatteryOptimizationsPage: IntroPage {
|
|||
model.checkBatteryOptimizations()
|
||||
}
|
||||
|
||||
val hintBatteryOptimizations by model.hintBatteryOptimizations.observeAsState()
|
||||
val hintBatteryOptimizations by model.hintBatteryOptimizations.collectAsStateWithLifecycle(false)
|
||||
val shouldBeExempted by model.shouldBeExempted.observeAsState(false)
|
||||
val isExempted by model.isExempted.observeAsState(false)
|
||||
LaunchedEffect(shouldBeExempted, isExempted) {
|
||||
|
@ -110,7 +111,7 @@ class BatteryOptimizationsPage: IntroPage {
|
|||
ignoreBatteryOptimizationsResultLauncher.launch(BuildConfig.APPLICATION_ID)
|
||||
}
|
||||
|
||||
val hintAutostartPermission by model.hintAutostartPermission.observeAsState()
|
||||
val hintAutostartPermission by model.hintAutostartPermission.collectAsStateWithLifecycle(false)
|
||||
BatteryOptimizationsContent(
|
||||
dontShowBattery = hintBatteryOptimizations == false,
|
||||
onChangeDontShowBattery = {
|
||||
|
@ -178,14 +179,14 @@ class BatteryOptimizationsPage: IntroPage {
|
|||
|
||||
val shouldBeExempted = MutableLiveData<Boolean>()
|
||||
val isExempted = MutableLiveData<Boolean>()
|
||||
val hintBatteryOptimizations = settings.getBooleanLive(HINT_BATTERY_OPTIMIZATIONS)
|
||||
val hintBatteryOptimizations = settings.getBooleanFlow(HINT_BATTERY_OPTIMIZATIONS)
|
||||
private val batteryOptimizationsReceiver = object: BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
checkBatteryOptimizations()
|
||||
}
|
||||
}
|
||||
|
||||
val hintAutostartPermission = settings.getBooleanLive(HINT_AUTOSTART_PERMISSION)
|
||||
val hintAutostartPermission = settings.getBooleanFlow(HINT_AUTOSTART_PERMISSION)
|
||||
|
||||
init {
|
||||
val intentFilter = IntentFilter(PermissionUtils.ACTION_POWER_SAVE_WHITELIST_CHANGED)
|
||||
|
|
|
@ -21,7 +21,6 @@ import androidx.compose.material.OutlinedButton
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
@ -31,6 +30,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.Constants
|
||||
import at.bitfire.davdroid.Constants.withStatParams
|
||||
|
@ -68,7 +68,7 @@ class OpenSourcePage : IntroPage {
|
|||
|
||||
@Composable
|
||||
private fun Page(model: Model = viewModel()) {
|
||||
val dontShow by model.dontShow.observeAsState(false)
|
||||
val dontShow by model.dontShow.collectAsStateWithLifecycle(false)
|
||||
PageContent(
|
||||
dontShow = dontShow,
|
||||
onChangeDontShow = {
|
||||
|
@ -147,7 +147,8 @@ class OpenSourcePage : IntroPage {
|
|||
const val SETTING_NEXT_DONATION_POPUP = "time_nextDonationPopup"
|
||||
}
|
||||
|
||||
val dontShow = settings.containsKeyLive(SETTING_NEXT_DONATION_POPUP)
|
||||
val dontShow = settings.containsKeyFlow(SETTING_NEXT_DONATION_POPUP)
|
||||
|
||||
fun setDontShow(dontShowAgain: Boolean) {
|
||||
if (dontShowAgain) {
|
||||
val nextReminder = System.currentTimeMillis() + 90*86400000L // 90 days (~ 3 months)
|
||||
|
|
|
@ -40,6 +40,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder
|
||||
|
@ -96,8 +97,9 @@ fun AccountDetailsPage(
|
|||
val suggestedAccountNames = foundConfig.calDAV?.emails ?: emptyList()
|
||||
var accountName by remember { mutableStateOf(suggestedAccountNames.firstOrNull() ?: "") }
|
||||
|
||||
val forcedGroupMethod by model.forcedGroupMethod.observeAsState()
|
||||
var groupMethod by remember { mutableStateOf(forcedGroupMethod ?: loginInfo.suggestedGroupMethod) }
|
||||
var groupMethod by remember { mutableStateOf(loginInfo.suggestedGroupMethod) }
|
||||
val forcedGroupMethod by model.forcedGroupMethod.collectAsStateWithLifecycle(null)
|
||||
forcedGroupMethod?.let { groupMethod = it }
|
||||
AccountDetailsPage_Content(
|
||||
suggestedAccountNames = suggestedAccountNames,
|
||||
accountName = accountName,
|
||||
|
|
|
@ -14,7 +14,6 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
|
@ -33,6 +32,7 @@ import at.bitfire.davdroid.util.TaskUtils
|
|||
import at.bitfire.vcard4android.GroupMethod
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import java.util.logging.Level
|
||||
|
@ -45,7 +45,7 @@ class LoginModel @Inject constructor(
|
|||
val settingsManager: SettingsManager
|
||||
): ViewModel() {
|
||||
|
||||
val forcedGroupMethod = settingsManager.getStringLive(AccountSettings.KEY_CONTACT_GROUP_METHOD).map { methodName ->
|
||||
val forcedGroupMethod = settingsManager.getStringFlow(AccountSettings.KEY_CONTACT_GROUP_METHOD).map { methodName ->
|
||||
methodName?.let {
|
||||
try {
|
||||
GroupMethod.valueOf(it)
|
||||
|
|
|
@ -15,8 +15,6 @@ import android.graphics.drawable.BitmapDrawable
|
|||
import android.net.Uri
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.map
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.Service
|
||||
|
@ -34,6 +32,11 @@ import dagger.hilt.EntryPoint
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
object TaskUtils {
|
||||
|
||||
|
@ -57,16 +60,16 @@ object TaskUtils {
|
|||
/**
|
||||
* Returns the currently selected tasks provider (if it's still available = installed).
|
||||
*
|
||||
* @return the currently selected tasks provider, or null if none is available
|
||||
* @return flow with the currently selected tasks provider
|
||||
*/
|
||||
fun currentProviderLive(context: Context): LiveData<ProviderName?> {
|
||||
fun currentProviderFlow(context: Context, externalScope: CoroutineScope): StateFlow<ProviderName?> {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
return settingsManager.getStringLive(Settings.SELECTED_TASKS_PROVIDER).map { preferred ->
|
||||
return settingsManager.getStringFlow(Settings.SELECTED_TASKS_PROVIDER).map { preferred ->
|
||||
if (preferred != null)
|
||||
preferredAuthorityToProviderName(preferred, context.packageManager)
|
||||
else
|
||||
null
|
||||
}
|
||||
}.stateIn(scope = externalScope, started = SharingStarted.WhileSubscribed(), initialValue = null)
|
||||
}
|
||||
|
||||
private fun preferredAuthorityToProviderName(
|
||||
|
|
|
@ -15,6 +15,7 @@ androidx-constraintLayout = "2.1.4"
|
|||
androidx-core = "1.12.0"
|
||||
androidx-fragment = "1.6.2"
|
||||
androidx-hilt = "1.2.0"
|
||||
androidx-lifecycle = "2.7.0"
|
||||
androidx-paging = "3.2.1"
|
||||
androidx-preference = "1.2.1"
|
||||
androidx-security = "1.1.0-alpha06"
|
||||
|
@ -23,7 +24,6 @@ androidx-test-core = "1.5.0"
|
|||
androidx-test-runner = "1.5.2"
|
||||
androidx-test-rules = "1.5.0"
|
||||
androidx-test-junit = "1.1.5"
|
||||
androidx-viewmodel = "2.7.0"
|
||||
androidx-work = "2.9.0"
|
||||
appIntro = "7.0.0-beta02"
|
||||
bitfire-cert4android = "f1cc9b9ca3"
|
||||
|
@ -72,8 +72,9 @@ androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-cor
|
|||
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
|
||||
androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt" }
|
||||
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt" }
|
||||
androidx-lifecycle-viewmodel-base = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-viewmodel" }
|
||||
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-viewmodel" }
|
||||
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-viewmodel-base = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version.ref = "androidx-paging" }
|
||||
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
|
||||
androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
|
||||
|
|
Loading…
Reference in New Issue
Block a user