diff --git a/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt b/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt index 6cc2043a5..8036da622 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt @@ -17,6 +17,9 @@ import kotlinx.coroutines.launch import org.tasks.R import org.tasks.compose.ListSettingsComposables.PrincipalList import org.tasks.compose.ShareInvite.ShareInviteDialog +import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.data.PrincipalWithAccess +import org.tasks.data.dao.PrincipalDao import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount.Companion.SERVER_NEXTCLOUD import org.tasks.data.entity.CaldavAccount.Companion.SERVER_OWNCLOUD @@ -24,8 +27,6 @@ import org.tasks.data.entity.CaldavAccount.Companion.SERVER_SABREDAV import org.tasks.data.entity.CaldavAccount.Companion.SERVER_TASKS import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_OWNER -import org.tasks.data.dao.PrincipalDao -import org.tasks.data.PrincipalWithAccess import javax.inject.Inject @AndroidEntryPoint @@ -51,14 +52,14 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() { } caldavCalendar?.takeIf { it.id > 0 }?.let { - principalDao.getPrincipals(it.id).observe(this) { - findViewById(R.id.people) - .apply { isVisible = it.isNotEmpty() } - .setContent { - MdcTheme { - PrincipalList(it, if (canRemovePrincipals) this::onRemove else null) - } - } + findViewById(R.id.people).setContent { + MdcTheme { + val principals = principalDao.getPrincipals(it.id).collectAsStateLifecycleAware(initial = emptyList()).value + PrincipalList( + principals = principals, + onRemove = if (canRemovePrincipals) { { onRemove(it) } } else null, + ) + } } } if (caldavAccount.canShare && (isNew || caldavCalendar?.access == ACCESS_OWNER)) { diff --git a/app/src/main/java/org/tasks/compose/PrincipalList.kt b/app/src/main/java/org/tasks/compose/PrincipalList.kt index 7bb93d2e8..7c0b821dc 100644 --- a/app/src/main/java/org/tasks/compose/PrincipalList.kt +++ b/app/src/main/java/org/tasks/compose/PrincipalList.kt @@ -29,6 +29,7 @@ import org.tasks.R import org.tasks.compose.Constants.HALF_KEYLINE import org.tasks.compose.Constants.ICON_ALPHA import org.tasks.compose.Constants.KEYLINE_FIRST +import org.tasks.data.PrincipalWithAccess import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_ACCEPTED import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_DECLINED import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_INVALID @@ -36,7 +37,6 @@ import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_NO_RESPONSE import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_UNKNOWN import org.tasks.data.entity.Principal import org.tasks.data.entity.PrincipalAccess -import org.tasks.data.PrincipalWithAccess private val principals = listOf( PrincipalWithAccess( @@ -69,6 +69,9 @@ object ListSettingsComposables { principals: List, onRemove: ((PrincipalWithAccess) -> Unit)?, ) { + if (principals.isEmpty()) { + return + } Column( modifier = Modifier .padding(16.dp) diff --git a/app/src/main/java/org/tasks/location/LocationPickerActivity.kt b/app/src/main/java/org/tasks/location/LocationPickerActivity.kt index 5c4a3615e..75bfb3d44 100644 --- a/app/src/main/java/org/tasks/location/LocationPickerActivity.kt +++ b/app/src/main/java/org/tasks/location/LocationPickerActivity.kt @@ -32,9 +32,9 @@ import org.tasks.activities.PlaceSettingsActivity import org.tasks.analytics.Firebase import org.tasks.billing.Inventory import org.tasks.caldav.GeoUtils.toLikeString +import org.tasks.data.PlaceUsage import org.tasks.data.dao.LocationDao import org.tasks.data.entity.Place -import org.tasks.data.PlaceUsage import org.tasks.data.mapPosition import org.tasks.databinding.ActivityLocationPickerBinding import org.tasks.dialogs.DialogBuilder @@ -150,11 +150,9 @@ class LocationPickerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListe override fun onLayoutChange( v: View, l: Int, t: Int, r: Int, b: Int, ol: Int, ot: Int, or: Int, ob: Int) { coordinatorLayout.removeOnLayoutChangeListener(this) - locationDao - .getPlaceUsage() - .observe(this@LocationPickerActivity) { - places: List -> updatePlaces(places) - } + lifecycleScope.launch { + updatePlaces(locationDao.getPlaceUsage()) + } } }) if (offset != 0) { diff --git a/app/src/main/java/org/tasks/preferences/PreferencesViewModel.kt b/app/src/main/java/org/tasks/preferences/PreferencesViewModel.kt index 0b9c967a4..e280b6ea7 100644 --- a/app/src/main/java/org/tasks/preferences/PreferencesViewModel.kt +++ b/app/src/main/java/org/tasks/preferences/PreferencesViewModel.kt @@ -10,12 +10,13 @@ import com.google.api.services.drive.DriveScopes import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.tasks.R import org.tasks.backup.BackupConstants -import org.tasks.data.entity.CaldavAccount import org.tasks.data.dao.CaldavDao +import org.tasks.data.entity.CaldavAccount import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.googleapis.InvokerFactory import org.tasks.gtasks.GoogleAccountManager @@ -29,13 +30,14 @@ class PreferencesViewModel @Inject constructor( private val preferences: Preferences, invokers: InvokerFactory, private val googleAccountManager: GoogleAccountManager, - caldavDao: CaldavDao, + private val caldavDao: CaldavDao, ) : ViewModel() { private val driveInvoker = invokers.getDriveInvoker() val lastBackup = MutableLiveData() val lastDriveBackup = MutableLiveData() val lastAndroidBackup = MutableLiveData() - var caldavAccounts = caldavDao.watchAccounts() + val caldavAccounts: Flow> + get() = caldavDao.watchAccounts() private fun isStale(timestamp: Long?) = timestamp != null @@ -63,7 +65,8 @@ class PreferencesViewModel @Inject constructor( return if (enabled) account else null } - fun tasksAccount(): CaldavAccount? = caldavAccounts.value?.firstOrNull { it.isTasksOrg } + suspend fun tasksAccount(): CaldavAccount? = + caldavDao.getAccounts(CaldavAccount.TYPE_TASKS).firstOrNull() fun updateDriveBackup() = viewModelScope.launch { if (driveAccount.isNullOrBlank()) { diff --git a/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt b/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt index 59c86ae94..e3e19f378 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/BaseAccountPreference.kt @@ -4,20 +4,43 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import androidx.lifecycle.lifecycleScope +import com.todoroo.astrid.service.TaskDeleter import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.tasks.R import org.tasks.billing.BillingClient import org.tasks.billing.PurchaseActivity +import org.tasks.data.dao.CaldavDao +import org.tasks.data.entity.CaldavAccount import org.tasks.injection.InjectingPreferenceFragment import javax.inject.Inject abstract class BaseAccountPreference : InjectingPreferenceFragment() { @Inject lateinit var billingClient: BillingClient + @Inject lateinit var caldavDao: CaldavDao + @Inject lateinit var taskDeleter: TaskDeleter + + private val accountState = MutableStateFlow(null) + + val account: CaldavAccount + get() = accountState.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!! override suspend fun setupPreferences(savedInstanceState: Bundle?) { + caldavDao + .watchAccount(requireArguments().getParcelable(EXTRA_ACCOUNT)!!.id) + .onEach { + accountState.value = it + if (it != null) { + refreshUi(it) + } + } + .launchIn(lifecycleScope) + findPreference(R.string.logout).setOnPreferenceClickListener { dialogBuilder .newDialog() @@ -36,7 +59,11 @@ abstract class BaseAccountPreference : InjectingPreferenceFragment() { } } - protected abstract suspend fun removeAccount() + protected open suspend fun removeAccount() { + taskDeleter.delete(account) + } + + protected abstract suspend fun refreshUi(account: CaldavAccount) protected fun showPurchaseDialog(): Boolean { startActivityForResult( @@ -59,7 +86,8 @@ abstract class BaseAccountPreference : InjectingPreferenceFragment() { } companion object { + @JvmStatic + protected val EXTRA_ACCOUNT = "extra_account" const val REQUEST_PURCHASE = 10201 - } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/preferences/fragments/GoogleTasksAccount.kt b/app/src/main/java/org/tasks/preferences/fragments/GoogleTasksAccount.kt index b029ea841..e338ccb0a 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/GoogleTasksAccount.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/GoogleTasksAccount.kt @@ -4,10 +4,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Bundle -import androidx.lifecycle.LiveData import androidx.lifecycle.lifecycleScope import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity -import com.todoroo.astrid.service.TaskDeleter import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.tasks.LocalBroadcastManager @@ -15,32 +13,20 @@ import org.tasks.R import org.tasks.billing.Inventory import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired -import org.tasks.data.dao.CaldavDao import org.tasks.preferences.IconPreference import javax.inject.Inject @AndroidEntryPoint class GoogleTasksAccount : BaseAccountPreference() { - @Inject lateinit var taskDeleter: TaskDeleter @Inject lateinit var inventory: Inventory @Inject lateinit var localBroadcastManager: LocalBroadcastManager - @Inject lateinit var caldavDao: CaldavDao - - private lateinit var googleTaskAccountLiveData: LiveData - - val googleTaskAccount: CaldavAccount - get() = googleTaskAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!! private val purchaseReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { lifecycleScope.launch { - googleTaskAccount.let { - if (inventory.subscription.value != null && it.error.isPaymentRequired()) { - it.error = null - caldavDao.update(it) - } - refreshUi(it) + if (inventory.subscription.value != null && account.error.isPaymentRequired()) { + caldavDao.update(account.copy(error = null)) } } } @@ -51,19 +37,10 @@ class GoogleTasksAccount : BaseAccountPreference() { override suspend fun setupPreferences(savedInstanceState: Bundle?) { super.setupPreferences(savedInstanceState) - googleTaskAccountLiveData = caldavDao.watchAccount( - arguments?.getParcelable(EXTRA_ACCOUNT)?.id ?: 0 - ) - googleTaskAccountLiveData.observe(this) { refreshUi(it) } - findPreference(R.string.reinitialize_account) .setOnPreferenceClickListener { requestLogin() } } - override suspend fun removeAccount() { - taskDeleter.delete(googleTaskAccount) - } - override fun onResume() { super.onResume() localBroadcastManager.registerPurchaseReceiver(purchaseReceiver) @@ -76,10 +53,7 @@ class GoogleTasksAccount : BaseAccountPreference() { localBroadcastManager.unregisterReceiver(purchaseReceiver) } - private fun refreshUi(account: CaldavAccount?) { - if (account == null) { - return - } + override suspend fun refreshUi(account: CaldavAccount) { (findPreference(R.string.sign_in_with_google) as IconPreference).apply { if (account.error.isNullOrBlank()) { isVisible = false @@ -116,8 +90,6 @@ class GoogleTasksAccount : BaseAccountPreference() { } companion object { - private const val EXTRA_ACCOUNT = "extra_account" - fun String?.isUnauthorized(): Boolean = this?.startsWith("401 Unauthorized", ignoreCase = true) == true diff --git a/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt b/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt index 314976bfd..6436ffff2 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/MainSettingsFragment.kt @@ -11,6 +11,8 @@ import androidx.preference.PreferenceScreen import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity import com.todoroo.astrid.service.TaskDeleter import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.tasks.BuildConfig import org.tasks.R @@ -21,8 +23,8 @@ import org.tasks.billing.Purchase import org.tasks.billing.PurchaseActivity import org.tasks.caldav.BaseCaldavAccountSettingsActivity import org.tasks.caldav.CaldavAccountSettingsActivity -import org.tasks.data.entity.CaldavAccount import org.tasks.data.accountSettingsClass +import org.tasks.data.entity.CaldavAccount import org.tasks.data.prefIcon import org.tasks.data.prefTitle import org.tasks.etebase.EtebaseAccountSettingsActivity @@ -89,7 +91,11 @@ class MainSettingsFragment : InjectingPreferenceFragment() { viewModel.lastBackup.observe(this) { updateBackupWarning() } viewModel.lastAndroidBackup.observe(this) { updateBackupWarning() } viewModel.lastDriveBackup.observe(this) { updateBackupWarning() } - viewModel.caldavAccounts.observe(this) { refreshAccounts() } + viewModel + .caldavAccounts + .onEach { refreshAccounts(it) } + .launchIn(lifecycleScope) + if (BuildConfig.FLAVOR == "generic") { remove(R.string.upgrade_to_pro) } else { @@ -141,8 +147,7 @@ class MainSettingsFragment : InjectingPreferenceFragment() { updateWidgetVisibility() } - private fun refreshAccounts() { - val caldavAccounts = viewModel.caldavAccounts.value ?: emptyList() + private fun refreshAccounts(caldavAccounts: List) { val addAccount = findPreference(R.string.add_account) val index = preferenceScreen.indexOf(addAccount) var current = 0 @@ -165,8 +170,10 @@ class MainSettingsFragment : InjectingPreferenceFragment() { } private fun addAccount(): Boolean { - newAccountDialog(hasTasksAccount = viewModel.tasksAccount() != null) - .show(parentFragmentManager, FRAG_TAG_ADD_ACCOUNT) + lifecycleScope.launch { + newAccountDialog(hasTasksAccount = viewModel.tasksAccount() != null) + .show(parentFragmentManager, FRAG_TAG_ADD_ACCOUNT) + } return false } diff --git a/app/src/main/java/org/tasks/preferences/fragments/MicrosoftAccount.kt b/app/src/main/java/org/tasks/preferences/fragments/MicrosoftAccount.kt index fc8887588..4e06cf668 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/MicrosoftAccount.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/MicrosoftAccount.kt @@ -5,9 +5,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.fragment.app.viewModels -import androidx.lifecycle.LiveData import androidx.lifecycle.lifecycleScope -import com.todoroo.astrid.service.TaskDeleter import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.tasks.LocalBroadcastManager @@ -15,7 +13,6 @@ import org.tasks.R import org.tasks.billing.Inventory import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired -import org.tasks.data.dao.CaldavDao import org.tasks.preferences.IconPreference import org.tasks.sync.microsoft.MicrosoftSignInViewModel import javax.inject.Inject @@ -23,26 +20,16 @@ import javax.inject.Inject @AndroidEntryPoint class MicrosoftAccount : BaseAccountPreference() { - @Inject lateinit var taskDeleter: TaskDeleter @Inject lateinit var inventory: Inventory @Inject lateinit var localBroadcastManager: LocalBroadcastManager - @Inject lateinit var caldavDao: CaldavDao private val microsoftVM: MicrosoftSignInViewModel by viewModels() - private lateinit var microsoftAccountLiveData: LiveData - - val microsoftAccount: CaldavAccount - get() = microsoftAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!! private val purchaseReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { lifecycleScope.launch { - microsoftAccount.let { - if (inventory.subscription.value != null && it.error.isPaymentRequired()) { - it.error = null - caldavDao.update(it) - } - refreshUi(it) + if (inventory.subscription.value != null && account.error.isPaymentRequired()) { + caldavDao.update(account.copy(error = null)) } } } @@ -53,19 +40,10 @@ class MicrosoftAccount : BaseAccountPreference() { override suspend fun setupPreferences(savedInstanceState: Bundle?) { super.setupPreferences(savedInstanceState) - microsoftAccountLiveData = caldavDao.watchAccount( - arguments?.getParcelable(EXTRA_ACCOUNT)?.id ?: 0 - ) - microsoftAccountLiveData.observe(this) { refreshUi(it) } - findPreference(R.string.reinitialize_account) .setOnPreferenceClickListener { requestLogin() } } - override suspend fun removeAccount() { - taskDeleter.delete(microsoftAccount) - } - override fun onResume() { super.onResume() localBroadcastManager.registerPurchaseReceiver(purchaseReceiver) @@ -78,10 +56,7 @@ class MicrosoftAccount : BaseAccountPreference() { localBroadcastManager.unregisterReceiver(purchaseReceiver) } - private fun refreshUi(account: CaldavAccount?) { - if (account == null) { - return - } + override suspend fun refreshUi(account: CaldavAccount) { (findPreference(R.string.sign_in_with_google) as IconPreference).apply { if (account.error.isNullOrBlank()) { isVisible = false @@ -110,7 +85,7 @@ class MicrosoftAccount : BaseAccountPreference() { } private fun requestLogin(): Boolean { - microsoftAccount.username?.let { + account.username?.let { microsoftVM.signIn(requireActivity()) // should force a specific account } return false diff --git a/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt b/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt index c6d80dc99..4596698b0 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt @@ -13,12 +13,10 @@ import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.LiveData import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceCategory import com.google.android.material.textfield.TextInputLayout import com.todoroo.andlib.utility.DateUtilities -import com.todoroo.astrid.service.TaskDeleter import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.tasks.BuildConfig @@ -28,9 +26,9 @@ import org.tasks.auth.SignInActivity import org.tasks.auth.SignInActivity.Platform import org.tasks.billing.Inventory import org.tasks.billing.Purchase +import org.tasks.data.dao.CaldavDao import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired -import org.tasks.data.dao.CaldavDao import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.toast import org.tasks.jobs.WorkManager @@ -43,33 +41,27 @@ import javax.inject.Inject @AndroidEntryPoint class TasksAccount : BaseAccountPreference() { - @Inject lateinit var taskDeleter: TaskDeleter @Inject lateinit var inventory: Inventory @Inject lateinit var localBroadcastManager: LocalBroadcastManager - @Inject lateinit var caldavDao: CaldavDao @Inject lateinit var workManager: WorkManager @Inject lateinit var locale: Locale private val viewModel: TasksAccountViewModel by viewModels() - private lateinit var caldavAccountLiveData: LiveData - - val caldavAccount: CaldavAccount - get() = caldavAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!! - private val refreshReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - refreshUi(caldavAccount) + lifecycleScope.launch { + refreshUi(account) + } } } override fun getPreferenceXml() = R.xml.preferences_tasks private fun clearPurchaseError(purchase: Purchase?) { - if (purchase?.isTasksSubscription == true && caldavAccount.error.isPaymentRequired()) { - caldavAccount.error = null + if (purchase?.isTasksSubscription == true && account.error.isPaymentRequired()) { lifecycleScope.launch { - caldavDao.update(caldavAccount) + caldavDao.update(account.copy(error = null)) } } } @@ -79,21 +71,18 @@ class TasksAccount : BaseAccountPreference() { inventory.subscription.observe(this) { clearPurchaseError(it) } - caldavAccountLiveData = caldavDao.watchAccount( - requireArguments().getParcelable(EXTRA_ACCOUNT)!!.id - ) if (savedInstanceState == null) { - viewModel.refreshPasswords(caldavAccount) + viewModel.refreshPasswords(account) } findPreference(R.string.local_lists).setOnPreferenceClickListener { - workManager.migrateLocalTasks(caldavAccount) + workManager.migrateLocalTasks(account) context?.toast(R.string.migrating_tasks) false } findPreference(R.string.generate_new_password).setOnPreferenceChangeListener { _, description -> - viewModel.requestNewPassword(caldavAccount, description as String) + viewModel.requestNewPassword(account, description as String) false } @@ -101,16 +90,13 @@ class TasksAccount : BaseAccountPreference() { } override suspend fun removeAccount() { - // try to delete session from caldav.tasks.org - taskDeleter.delete(caldavAccount) + super.removeAccount() + // TODO: try to delete session from caldav.tasks.org inventory.updateTasksAccount() } override fun onResume() { super.onResume() - caldavAccountLiveData.observe(this) { account -> - account?.let { refreshUi(it) } - } viewModel.appPasswords.observe(this) { passwords -> passwords?.let { refreshPasswords(passwords) } } @@ -124,7 +110,7 @@ class TasksAccount : BaseAccountPreference() { .setView(view) .setPositiveButton(R.string.ok) { _, _ -> viewModel.clearNewPassword() - viewModel.refreshPasswords(caldavAccount) + viewModel.refreshPasswords(account) } .setCancelable(false) .setNeutralButton(R.string.help) { _, _ -> @@ -155,9 +141,9 @@ class TasksAccount : BaseAccountPreference() { } private val isGithub: Boolean - get() = caldavAccount.username?.startsWith("github") == true + get() = account.username?.startsWith("github") == true - private fun refreshUi(account: CaldavAccount) { + override suspend fun refreshUi(account: CaldavAccount) { (findPreference(R.string.sign_in_with_google) as IconPreference).apply { if (account.error.isNullOrBlank()) { isVisible = false @@ -243,7 +229,7 @@ class TasksAccount : BaseAccountPreference() { .setTitle(R.string.delete_tag_confirmation, description) .setMessage(R.string.app_password_delete_confirmation) .setPositiveButton(R.string.ok) { _, _ -> - viewModel.deletePassword(caldavAccount, it.id) + viewModel.deletePassword(account, it.id) } .setNegativeButton(R.string.cancel, null) .show() @@ -269,8 +255,6 @@ class TasksAccount : BaseAccountPreference() { } companion object { - private const val EXTRA_ACCOUNT = "extra_account" - fun newTasksAccountPreference(account: CaldavAccount): Fragment { val fragment = TasksAccount() fragment.arguments = Bundle().apply { diff --git a/app/src/main/java/org/tasks/ui/ChipListCache.kt b/app/src/main/java/org/tasks/ui/ChipListCache.kt index c708e7b84..01993e7a8 100644 --- a/app/src/main/java/org/tasks/ui/ChipListCache.kt +++ b/app/src/main/java/org/tasks/ui/ChipListCache.kt @@ -1,11 +1,14 @@ package org.tasks.ui import com.todoroo.astrid.api.TagFilter +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.tasks.LocalBroadcastManager -import org.tasks.data.entity.CaldavCalendar import org.tasks.data.dao.CaldavDao -import org.tasks.data.entity.TagData import org.tasks.data.dao.TagDataDao +import org.tasks.data.entity.CaldavCalendar +import org.tasks.data.entity.TagData import javax.inject.Inject import javax.inject.Singleton @@ -38,7 +41,7 @@ class ChipListCache @Inject internal constructor( fun getTag(tag: String?): TagFilter? = tagDatas[tag] init { - caldavDao.subscribeToCalendars().observeForever { updated: List -> updateCaldavCalendars(updated) } - tagDataDao.subscribeToTags().observeForever { updated: List -> updateTags(updated) } + caldavDao.subscribeToCalendars().onEach { updateCaldavCalendars(it) }.launchIn(GlobalScope) + tagDataDao.subscribeToTags().onEach { updateTags(it) }.launchIn(GlobalScope) } } \ No newline at end of file diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 46e48ee8c..3d4a55fe2 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -50,7 +50,6 @@ android { } dependencies { - implementation(libs.androidx.lifecycle.livedata) implementation(libs.androidx.room) implementation(libs.kotlinx.serialization) implementation(libs.timber) diff --git a/data/src/main/kotlin/org/tasks/data/dao/CaldavDao.kt b/data/src/main/kotlin/org/tasks/data/dao/CaldavDao.kt index ea947ceb5..684cad605 100644 --- a/data/src/main/kotlin/org/tasks/data/dao/CaldavDao.kt +++ b/data/src/main/kotlin/org/tasks/data/dao/CaldavDao.kt @@ -1,13 +1,18 @@ package org.tasks.data.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Transaction import androidx.room.Update -import org.tasks.data.entity.Task +import kotlinx.coroutines.flow.Flow +import org.tasks.data.CaldavFilters +import org.tasks.data.CaldavTaskContainer +import org.tasks.data.NO_ORDER +import org.tasks.data.TaskContainer +import org.tasks.data.db.DbUtils.dbchunk +import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount.Companion.TYPE_ETESYNC import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS @@ -15,13 +20,8 @@ import org.tasks.data.entity.CaldavAccount.Companion.TYPE_LOCAL import org.tasks.data.entity.CaldavAccount.Companion.TYPE_OPENTASKS import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS import org.tasks.data.entity.CaldavCalendar -import org.tasks.data.CaldavFilters import org.tasks.data.entity.CaldavTask -import org.tasks.data.CaldavTaskContainer -import org.tasks.data.NO_ORDER -import org.tasks.data.TaskContainer -import org.tasks.data.db.DbUtils.dbchunk -import org.tasks.data.db.SuspendDbUtils.chunkedMap +import org.tasks.data.entity.Task import org.tasks.time.DateTimeUtils2.currentTimeMillis const val APPLE_EPOCH = 978307200000L // 1/1/2001 GMT @@ -32,7 +32,7 @@ abstract class CaldavDao { abstract fun listCount(account: String): Int @Query("SELECT * FROM caldav_lists") - abstract fun subscribeToCalendars(): LiveData> + abstract fun subscribeToCalendars(): Flow> @Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1") abstract suspend fun getCalendarByUuid(uuid: String): CaldavCalendar? @@ -55,7 +55,7 @@ abstract class CaldavDao { abstract suspend fun getAccount(type: Int, username: String): CaldavAccount? @Query("SELECT * FROM caldav_accounts WHERE cda_id = :id") - abstract fun watchAccount(id: Long): LiveData + abstract fun watchAccount(id: Long): Flow @Query(""" SELECT * @@ -66,7 +66,7 @@ ORDER BY CASE cda_account_type ELSE 1 END, UPPER(cda_name) """) - abstract fun watchAccounts(): LiveData> + abstract fun watchAccounts(): Flow> @Query(""" SELECT * diff --git a/data/src/main/kotlin/org/tasks/data/dao/LocationDao.kt b/data/src/main/kotlin/org/tasks/data/dao/LocationDao.kt index 7d71ae8ba..2e98bfb89 100644 --- a/data/src/main/kotlin/org/tasks/data/dao/LocationDao.kt +++ b/data/src/main/kotlin/org/tasks/data/dao/LocationDao.kt @@ -1,20 +1,19 @@ package org.tasks.data.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update -import org.tasks.data.entity.Geofence import org.tasks.data.Location import org.tasks.data.LocationFilters import org.tasks.data.MergedGeofence import org.tasks.data.NO_ORDER -import org.tasks.data.entity.Place import org.tasks.data.PlaceUsage import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE +import org.tasks.data.entity.Geofence +import org.tasks.data.entity.Place import org.tasks.time.DateTimeUtils2.currentTimeMillis @Dao @@ -110,7 +109,7 @@ interface LocationDao { suspend fun getPlace(uid: String): Place? @Query("SELECT places.*, IFNULL(COUNT(geofence_id),0) AS count FROM places LEFT OUTER JOIN geofences ON geofences.place = places.uid GROUP BY uid ORDER BY COUNT(geofence_id) DESC") - fun getPlaceUsage(): LiveData> + suspend fun getPlaceUsage(): List @Query("SELECT * FROM places WHERE latitude LIKE :latitude AND longitude LIKE :longitude") suspend fun findPlace(latitude: String, longitude: String): Place? diff --git a/data/src/main/kotlin/org/tasks/data/dao/PrincipalDao.kt b/data/src/main/kotlin/org/tasks/data/dao/PrincipalDao.kt index 2a56b2e6f..4f4983e13 100644 --- a/data/src/main/kotlin/org/tasks/data/dao/PrincipalDao.kt +++ b/data/src/main/kotlin/org/tasks/data/dao/PrincipalDao.kt @@ -1,17 +1,17 @@ package org.tasks.data.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Transaction import androidx.room.Update -import org.tasks.data.entity.Principal -import org.tasks.data.entity.PrincipalAccess +import kotlinx.coroutines.flow.Flow import org.tasks.data.PrincipalWithAccess import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavCalendar +import org.tasks.data.entity.Principal +import org.tasks.data.entity.PrincipalAccess @Dao interface PrincipalDao { @@ -72,5 +72,5 @@ WHERE list = :list @Transaction @Query("SELECT * FROM principal_access WHERE list = :id") - fun getPrincipals(id: Long): LiveData> + fun getPrincipals(id: Long): Flow> } \ No newline at end of file diff --git a/data/src/main/kotlin/org/tasks/data/dao/TagDataDao.kt b/data/src/main/kotlin/org/tasks/data/dao/TagDataDao.kt index 7cdf4f5f0..a3616b38b 100644 --- a/data/src/main/kotlin/org/tasks/data/dao/TagDataDao.kt +++ b/data/src/main/kotlin/org/tasks/data/dao/TagDataDao.kt @@ -1,25 +1,25 @@ package org.tasks.data.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Transaction import androidx.room.Update -import org.tasks.data.entity.Task -import org.tasks.data.UUIDHelper +import kotlinx.coroutines.flow.Flow import org.tasks.data.NO_ORDER -import org.tasks.data.entity.Tag -import org.tasks.data.entity.TagData import org.tasks.data.TagFilters +import org.tasks.data.UUIDHelper import org.tasks.data.db.DbUtils +import org.tasks.data.entity.Tag +import org.tasks.data.entity.TagData +import org.tasks.data.entity.Task import org.tasks.time.DateTimeUtils2.currentTimeMillis @Dao abstract class TagDataDao { @Query("SELECT * FROM tagdata") - abstract fun subscribeToTags(): LiveData> + abstract fun subscribeToTags(): Flow> @Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1") abstract suspend fun getTagByName(name: String): TagData? diff --git a/data/src/main/kotlin/org/tasks/data/dao/TaskDao.kt b/data/src/main/kotlin/org/tasks/data/dao/TaskDao.kt index b99e18f16..c58770813 100644 --- a/data/src/main/kotlin/org/tasks/data/dao/TaskDao.kt +++ b/data/src/main/kotlin/org/tasks/data/dao/TaskDao.kt @@ -7,16 +7,16 @@ import androidx.room.RawQuery import androidx.room.Update import androidx.room.withTransaction import androidx.sqlite.db.SimpleSQLiteQuery -import org.tasks.data.sql.Criterion -import org.tasks.data.sql.Functions -import org.tasks.data.db.Database -import org.tasks.data.entity.Task -import org.tasks.data.UUIDHelper import org.tasks.data.BuildConfig import org.tasks.data.TaskContainer -import org.tasks.data.entity.Alarm +import org.tasks.data.UUIDHelper +import org.tasks.data.db.Database import org.tasks.data.db.SuspendDbUtils.chunkedMap import org.tasks.data.db.SuspendDbUtils.eachChunk +import org.tasks.data.entity.Alarm +import org.tasks.data.entity.Task +import org.tasks.data.sql.Criterion +import org.tasks.data.sql.Functions import org.tasks.time.DateTimeUtils2 import timber.log.Timber @@ -149,7 +149,7 @@ FROM ( @Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children) AND _id != :parent") internal abstract suspend fun setParentInternal(parent: Long, children: List) - @Query("UPDATE tasks SET lastNotified = :timestamp WHERE _id = :id AND lastNotified != :timestamp") + @Query("UPDATE tasks SET lastNotified = :timestamp WHERE _id = :id") abstract suspend fun setLastNotified(id: Long, timestamp: Long) suspend fun getChildren(id: Long): List = getChildren(listOf(id)) diff --git a/deps_fdroid.txt b/deps_fdroid.txt index c111f9c65..f869d17dd 100644 --- a/deps_fdroid.txt +++ b/deps_fdroid.txt @@ -213,7 +213,6 @@ +| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) ++--- project :data +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) -+| +--- androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0 (*) +| +--- androidx.room:room-ktx:2.6.1 +| | +--- androidx.room:room-common:2.6.1 +| | | +--- androidx.annotation:annotation:1.3.0 -> 1.7.1 (*) diff --git a/deps_googleplay.txt b/deps_googleplay.txt index fd3c1c420..f0af2aeb3 100644 --- a/deps_googleplay.txt +++ b/deps_googleplay.txt @@ -574,7 +574,6 @@ +| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) ++--- project :data +| +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) -+| +--- androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0 (*) +| +--- androidx.room:room-ktx:2.6.1 +| | +--- androidx.room:room-common:2.6.1 +| | | +--- androidx.annotation:annotation:1.3.0 -> 1.7.1 (*)