Remove livedata from data module

pull/2883/head
Alex Baker 2 years ago
parent ebe5e5c009
commit 0ba901be69

@ -17,6 +17,9 @@ import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.compose.ListSettingsComposables.PrincipalList import org.tasks.compose.ListSettingsComposables.PrincipalList
import org.tasks.compose.ShareInvite.ShareInviteDialog 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
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_NEXTCLOUD import org.tasks.data.entity.CaldavAccount.Companion.SERVER_NEXTCLOUD
import org.tasks.data.entity.CaldavAccount.Companion.SERVER_OWNCLOUD 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.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_OWNER import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.dao.PrincipalDao
import org.tasks.data.PrincipalWithAccess
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -51,13 +52,13 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
} }
caldavCalendar?.takeIf { it.id > 0 }?.let { caldavCalendar?.takeIf { it.id > 0 }?.let {
principalDao.getPrincipals(it.id).observe(this) { findViewById<ComposeView>(R.id.people).setContent {
findViewById<ComposeView>(R.id.people)
.apply { isVisible = it.isNotEmpty() }
.setContent {
MdcTheme { MdcTheme {
PrincipalList(it, if (canRemovePrincipals) this::onRemove else null) val principals = principalDao.getPrincipals(it.id).collectAsStateLifecycleAware(initial = emptyList()).value
} PrincipalList(
principals = principals,
onRemove = if (canRemovePrincipals) { { onRemove(it) } } else null,
)
} }
} }
} }

@ -29,6 +29,7 @@ import org.tasks.R
import org.tasks.compose.Constants.HALF_KEYLINE import org.tasks.compose.Constants.HALF_KEYLINE
import org.tasks.compose.Constants.ICON_ALPHA import org.tasks.compose.Constants.ICON_ALPHA
import org.tasks.compose.Constants.KEYLINE_FIRST 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_ACCEPTED
import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_DECLINED import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_DECLINED
import org.tasks.data.entity.CaldavCalendar.Companion.INVITE_INVALID 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.CaldavCalendar.Companion.INVITE_UNKNOWN
import org.tasks.data.entity.Principal import org.tasks.data.entity.Principal
import org.tasks.data.entity.PrincipalAccess import org.tasks.data.entity.PrincipalAccess
import org.tasks.data.PrincipalWithAccess
private val principals = listOf( private val principals = listOf(
PrincipalWithAccess( PrincipalWithAccess(
@ -69,6 +69,9 @@ object ListSettingsComposables {
principals: List<PrincipalWithAccess>, principals: List<PrincipalWithAccess>,
onRemove: ((PrincipalWithAccess) -> Unit)?, onRemove: ((PrincipalWithAccess) -> Unit)?,
) { ) {
if (principals.isEmpty()) {
return
}
Column( Column(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)

@ -32,9 +32,9 @@ import org.tasks.activities.PlaceSettingsActivity
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.caldav.GeoUtils.toLikeString import org.tasks.caldav.GeoUtils.toLikeString
import org.tasks.data.PlaceUsage
import org.tasks.data.dao.LocationDao import org.tasks.data.dao.LocationDao
import org.tasks.data.entity.Place import org.tasks.data.entity.Place
import org.tasks.data.PlaceUsage
import org.tasks.data.mapPosition import org.tasks.data.mapPosition
import org.tasks.databinding.ActivityLocationPickerBinding import org.tasks.databinding.ActivityLocationPickerBinding
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
@ -150,10 +150,8 @@ class LocationPickerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListe
override fun onLayoutChange( override fun onLayoutChange(
v: View, l: Int, t: Int, r: Int, b: Int, ol: Int, ot: Int, or: Int, ob: Int) { v: View, l: Int, t: Int, r: Int, b: Int, ol: Int, ot: Int, or: Int, ob: Int) {
coordinatorLayout.removeOnLayoutChangeListener(this) coordinatorLayout.removeOnLayoutChangeListener(this)
locationDao lifecycleScope.launch {
.getPlaceUsage() updatePlaces(locationDao.getPlaceUsage())
.observe(this@LocationPickerActivity) {
places: List<PlaceUsage> -> updatePlaces(places)
} }
} }
}) })

@ -10,12 +10,13 @@ import com.google.api.services.drive.DriveScopes
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.tasks.R import org.tasks.R
import org.tasks.backup.BackupConstants import org.tasks.backup.BackupConstants
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.dao.CaldavDao import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.googleapis.InvokerFactory import org.tasks.googleapis.InvokerFactory
import org.tasks.gtasks.GoogleAccountManager import org.tasks.gtasks.GoogleAccountManager
@ -29,13 +30,14 @@ class PreferencesViewModel @Inject constructor(
private val preferences: Preferences, private val preferences: Preferences,
invokers: InvokerFactory, invokers: InvokerFactory,
private val googleAccountManager: GoogleAccountManager, private val googleAccountManager: GoogleAccountManager,
caldavDao: CaldavDao, private val caldavDao: CaldavDao,
) : ViewModel() { ) : ViewModel() {
private val driveInvoker = invokers.getDriveInvoker() private val driveInvoker = invokers.getDriveInvoker()
val lastBackup = MutableLiveData<Long?>() val lastBackup = MutableLiveData<Long?>()
val lastDriveBackup = MutableLiveData<Long?>() val lastDriveBackup = MutableLiveData<Long?>()
val lastAndroidBackup = MutableLiveData<Long>() val lastAndroidBackup = MutableLiveData<Long>()
var caldavAccounts = caldavDao.watchAccounts() val caldavAccounts: Flow<List<CaldavAccount>>
get() = caldavDao.watchAccounts()
private fun isStale(timestamp: Long?) = private fun isStale(timestamp: Long?) =
timestamp != null timestamp != null
@ -63,7 +65,8 @@ class PreferencesViewModel @Inject constructor(
return if (enabled) account else null 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 { fun updateDriveBackup() = viewModelScope.launch {
if (driveAccount.isNullOrBlank()) { if (driveAccount.isNullOrBlank()) {

@ -4,20 +4,43 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.service.TaskDeleter
import kotlinx.coroutines.NonCancellable 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.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.tasks.R import org.tasks.R
import org.tasks.billing.BillingClient import org.tasks.billing.BillingClient
import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivity
import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.CaldavAccount
import org.tasks.injection.InjectingPreferenceFragment import org.tasks.injection.InjectingPreferenceFragment
import javax.inject.Inject import javax.inject.Inject
abstract class BaseAccountPreference : InjectingPreferenceFragment() { abstract class BaseAccountPreference : InjectingPreferenceFragment() {
@Inject lateinit var billingClient: BillingClient @Inject lateinit var billingClient: BillingClient
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskDeleter: TaskDeleter
private val accountState = MutableStateFlow<CaldavAccount?>(null)
val account: CaldavAccount
get() = accountState.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!!
override suspend fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
caldavDao
.watchAccount(requireArguments().getParcelable<CaldavAccount>(EXTRA_ACCOUNT)!!.id)
.onEach {
accountState.value = it
if (it != null) {
refreshUi(it)
}
}
.launchIn(lifecycleScope)
findPreference(R.string.logout).setOnPreferenceClickListener { findPreference(R.string.logout).setOnPreferenceClickListener {
dialogBuilder dialogBuilder
.newDialog() .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 { protected fun showPurchaseDialog(): Boolean {
startActivityForResult( startActivityForResult(
@ -59,7 +86,8 @@ abstract class BaseAccountPreference : InjectingPreferenceFragment() {
} }
companion object { companion object {
@JvmStatic
protected val EXTRA_ACCOUNT = "extra_account"
const val REQUEST_PURCHASE = 10201 const val REQUEST_PURCHASE = 10201
} }
} }

@ -4,10 +4,8 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
@ -15,32 +13,20 @@ import org.tasks.R
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired
import org.tasks.data.dao.CaldavDao
import org.tasks.preferences.IconPreference import org.tasks.preferences.IconPreference
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class GoogleTasksAccount : BaseAccountPreference() { class GoogleTasksAccount : BaseAccountPreference() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var inventory: Inventory @Inject lateinit var inventory: Inventory
@Inject lateinit var localBroadcastManager: LocalBroadcastManager @Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var caldavDao: CaldavDao
private lateinit var googleTaskAccountLiveData: LiveData<CaldavAccount>
val googleTaskAccount: CaldavAccount
get() = googleTaskAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!!
private val purchaseReceiver = object : BroadcastReceiver() { private val purchaseReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
lifecycleScope.launch { lifecycleScope.launch {
googleTaskAccount.let { if (inventory.subscription.value != null && account.error.isPaymentRequired()) {
if (inventory.subscription.value != null && it.error.isPaymentRequired()) { caldavDao.update(account.copy(error = null))
it.error = null
caldavDao.update(it)
}
refreshUi(it)
} }
} }
} }
@ -51,19 +37,10 @@ class GoogleTasksAccount : BaseAccountPreference() {
override suspend fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
super.setupPreferences(savedInstanceState) super.setupPreferences(savedInstanceState)
googleTaskAccountLiveData = caldavDao.watchAccount(
arguments?.getParcelable<CaldavAccount>(EXTRA_ACCOUNT)?.id ?: 0
)
googleTaskAccountLiveData.observe(this) { refreshUi(it) }
findPreference(R.string.reinitialize_account) findPreference(R.string.reinitialize_account)
.setOnPreferenceClickListener { requestLogin() } .setOnPreferenceClickListener { requestLogin() }
} }
override suspend fun removeAccount() {
taskDeleter.delete(googleTaskAccount)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
localBroadcastManager.registerPurchaseReceiver(purchaseReceiver) localBroadcastManager.registerPurchaseReceiver(purchaseReceiver)
@ -76,10 +53,7 @@ class GoogleTasksAccount : BaseAccountPreference() {
localBroadcastManager.unregisterReceiver(purchaseReceiver) localBroadcastManager.unregisterReceiver(purchaseReceiver)
} }
private fun refreshUi(account: CaldavAccount?) { override suspend fun refreshUi(account: CaldavAccount) {
if (account == null) {
return
}
(findPreference(R.string.sign_in_with_google) as IconPreference).apply { (findPreference(R.string.sign_in_with_google) as IconPreference).apply {
if (account.error.isNullOrBlank()) { if (account.error.isNullOrBlank()) {
isVisible = false isVisible = false
@ -116,8 +90,6 @@ class GoogleTasksAccount : BaseAccountPreference() {
} }
companion object { companion object {
private const val EXTRA_ACCOUNT = "extra_account"
fun String?.isUnauthorized(): Boolean = fun String?.isUnauthorized(): Boolean =
this?.startsWith("401 Unauthorized", ignoreCase = true) == true this?.startsWith("401 Unauthorized", ignoreCase = true) == true

@ -11,6 +11,8 @@ import androidx.preference.PreferenceScreen
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity
import com.todoroo.astrid.service.TaskDeleter import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
@ -21,8 +23,8 @@ import org.tasks.billing.Purchase
import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivity
import org.tasks.caldav.BaseCaldavAccountSettingsActivity import org.tasks.caldav.BaseCaldavAccountSettingsActivity
import org.tasks.caldav.CaldavAccountSettingsActivity import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.accountSettingsClass import org.tasks.data.accountSettingsClass
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.prefIcon import org.tasks.data.prefIcon
import org.tasks.data.prefTitle import org.tasks.data.prefTitle
import org.tasks.etebase.EtebaseAccountSettingsActivity import org.tasks.etebase.EtebaseAccountSettingsActivity
@ -89,7 +91,11 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
viewModel.lastBackup.observe(this) { updateBackupWarning() } viewModel.lastBackup.observe(this) { updateBackupWarning() }
viewModel.lastAndroidBackup.observe(this) { updateBackupWarning() } viewModel.lastAndroidBackup.observe(this) { updateBackupWarning() }
viewModel.lastDriveBackup.observe(this) { updateBackupWarning() } viewModel.lastDriveBackup.observe(this) { updateBackupWarning() }
viewModel.caldavAccounts.observe(this) { refreshAccounts() } viewModel
.caldavAccounts
.onEach { refreshAccounts(it) }
.launchIn(lifecycleScope)
if (BuildConfig.FLAVOR == "generic") { if (BuildConfig.FLAVOR == "generic") {
remove(R.string.upgrade_to_pro) remove(R.string.upgrade_to_pro)
} else { } else {
@ -141,8 +147,7 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
updateWidgetVisibility() updateWidgetVisibility()
} }
private fun refreshAccounts() { private fun refreshAccounts(caldavAccounts: List<CaldavAccount>) {
val caldavAccounts = viewModel.caldavAccounts.value ?: emptyList()
val addAccount = findPreference(R.string.add_account) val addAccount = findPreference(R.string.add_account)
val index = preferenceScreen.indexOf(addAccount) val index = preferenceScreen.indexOf(addAccount)
var current = 0 var current = 0
@ -165,8 +170,10 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
} }
private fun addAccount(): Boolean { private fun addAccount(): Boolean {
lifecycleScope.launch {
newAccountDialog(hasTasksAccount = viewModel.tasksAccount() != null) newAccountDialog(hasTasksAccount = viewModel.tasksAccount() != null)
.show(parentFragmentManager, FRAG_TAG_ADD_ACCOUNT) .show(parentFragmentManager, FRAG_TAG_ADD_ACCOUNT)
}
return false return false
} }

@ -5,9 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
@ -15,7 +13,6 @@ import org.tasks.R
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired
import org.tasks.data.dao.CaldavDao
import org.tasks.preferences.IconPreference import org.tasks.preferences.IconPreference
import org.tasks.sync.microsoft.MicrosoftSignInViewModel import org.tasks.sync.microsoft.MicrosoftSignInViewModel
import javax.inject.Inject import javax.inject.Inject
@ -23,26 +20,16 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MicrosoftAccount : BaseAccountPreference() { class MicrosoftAccount : BaseAccountPreference() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var inventory: Inventory @Inject lateinit var inventory: Inventory
@Inject lateinit var localBroadcastManager: LocalBroadcastManager @Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var caldavDao: CaldavDao
private val microsoftVM: MicrosoftSignInViewModel by viewModels() private val microsoftVM: MicrosoftSignInViewModel by viewModels()
private lateinit var microsoftAccountLiveData: LiveData<CaldavAccount>
val microsoftAccount: CaldavAccount
get() = microsoftAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!!
private val purchaseReceiver = object : BroadcastReceiver() { private val purchaseReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
lifecycleScope.launch { lifecycleScope.launch {
microsoftAccount.let { if (inventory.subscription.value != null && account.error.isPaymentRequired()) {
if (inventory.subscription.value != null && it.error.isPaymentRequired()) { caldavDao.update(account.copy(error = null))
it.error = null
caldavDao.update(it)
}
refreshUi(it)
} }
} }
} }
@ -53,19 +40,10 @@ class MicrosoftAccount : BaseAccountPreference() {
override suspend fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
super.setupPreferences(savedInstanceState) super.setupPreferences(savedInstanceState)
microsoftAccountLiveData = caldavDao.watchAccount(
arguments?.getParcelable<CaldavAccount>(EXTRA_ACCOUNT)?.id ?: 0
)
microsoftAccountLiveData.observe(this) { refreshUi(it) }
findPreference(R.string.reinitialize_account) findPreference(R.string.reinitialize_account)
.setOnPreferenceClickListener { requestLogin() } .setOnPreferenceClickListener { requestLogin() }
} }
override suspend fun removeAccount() {
taskDeleter.delete(microsoftAccount)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
localBroadcastManager.registerPurchaseReceiver(purchaseReceiver) localBroadcastManager.registerPurchaseReceiver(purchaseReceiver)
@ -78,10 +56,7 @@ class MicrosoftAccount : BaseAccountPreference() {
localBroadcastManager.unregisterReceiver(purchaseReceiver) localBroadcastManager.unregisterReceiver(purchaseReceiver)
} }
private fun refreshUi(account: CaldavAccount?) { override suspend fun refreshUi(account: CaldavAccount) {
if (account == null) {
return
}
(findPreference(R.string.sign_in_with_google) as IconPreference).apply { (findPreference(R.string.sign_in_with_google) as IconPreference).apply {
if (account.error.isNullOrBlank()) { if (account.error.isNullOrBlank()) {
isVisible = false isVisible = false
@ -110,7 +85,7 @@ class MicrosoftAccount : BaseAccountPreference() {
} }
private fun requestLogin(): Boolean { private fun requestLogin(): Boolean {
microsoftAccount.username?.let { account.username?.let {
microsoftVM.signIn(requireActivity()) // should force a specific account microsoftVM.signIn(requireActivity()) // should force a specific account
} }
return false return false

@ -13,12 +13,10 @@ import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.BuildConfig import org.tasks.BuildConfig
@ -28,9 +26,9 @@ import org.tasks.auth.SignInActivity
import org.tasks.auth.SignInActivity.Platform import org.tasks.auth.SignInActivity.Platform
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.billing.Purchase import org.tasks.billing.Purchase
import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired 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.openUri
import org.tasks.extensions.Context.toast import org.tasks.extensions.Context.toast
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
@ -43,33 +41,27 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class TasksAccount : BaseAccountPreference() { class TasksAccount : BaseAccountPreference() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var inventory: Inventory @Inject lateinit var inventory: Inventory
@Inject lateinit var localBroadcastManager: LocalBroadcastManager @Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var workManager: WorkManager @Inject lateinit var workManager: WorkManager
@Inject lateinit var locale: Locale @Inject lateinit var locale: Locale
private val viewModel: TasksAccountViewModel by viewModels() private val viewModel: TasksAccountViewModel by viewModels()
private lateinit var caldavAccountLiveData: LiveData<CaldavAccount>
val caldavAccount: CaldavAccount
get() = caldavAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!!
private val refreshReceiver = object : BroadcastReceiver() { private val refreshReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
refreshUi(caldavAccount) lifecycleScope.launch {
refreshUi(account)
}
} }
} }
override fun getPreferenceXml() = R.xml.preferences_tasks override fun getPreferenceXml() = R.xml.preferences_tasks
private fun clearPurchaseError(purchase: Purchase?) { private fun clearPurchaseError(purchase: Purchase?) {
if (purchase?.isTasksSubscription == true && caldavAccount.error.isPaymentRequired()) { if (purchase?.isTasksSubscription == true && account.error.isPaymentRequired()) {
caldavAccount.error = null
lifecycleScope.launch { lifecycleScope.launch {
caldavDao.update(caldavAccount) caldavDao.update(account.copy(error = null))
} }
} }
} }
@ -79,21 +71,18 @@ class TasksAccount : BaseAccountPreference() {
inventory.subscription.observe(this) { clearPurchaseError(it) } inventory.subscription.observe(this) { clearPurchaseError(it) }
caldavAccountLiveData = caldavDao.watchAccount(
requireArguments().getParcelable<CaldavAccount>(EXTRA_ACCOUNT)!!.id
)
if (savedInstanceState == null) { if (savedInstanceState == null) {
viewModel.refreshPasswords(caldavAccount) viewModel.refreshPasswords(account)
} }
findPreference(R.string.local_lists).setOnPreferenceClickListener { findPreference(R.string.local_lists).setOnPreferenceClickListener {
workManager.migrateLocalTasks(caldavAccount) workManager.migrateLocalTasks(account)
context?.toast(R.string.migrating_tasks) context?.toast(R.string.migrating_tasks)
false false
} }
findPreference(R.string.generate_new_password).setOnPreferenceChangeListener { _, description -> findPreference(R.string.generate_new_password).setOnPreferenceChangeListener { _, description ->
viewModel.requestNewPassword(caldavAccount, description as String) viewModel.requestNewPassword(account, description as String)
false false
} }
@ -101,16 +90,13 @@ class TasksAccount : BaseAccountPreference() {
} }
override suspend fun removeAccount() { override suspend fun removeAccount() {
// try to delete session from caldav.tasks.org super.removeAccount()
taskDeleter.delete(caldavAccount) // TODO: try to delete session from caldav.tasks.org
inventory.updateTasksAccount() inventory.updateTasksAccount()
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
caldavAccountLiveData.observe(this) { account ->
account?.let { refreshUi(it) }
}
viewModel.appPasswords.observe(this) { passwords -> viewModel.appPasswords.observe(this) { passwords ->
passwords?.let { refreshPasswords(passwords) } passwords?.let { refreshPasswords(passwords) }
} }
@ -124,7 +110,7 @@ class TasksAccount : BaseAccountPreference() {
.setView(view) .setView(view)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
viewModel.clearNewPassword() viewModel.clearNewPassword()
viewModel.refreshPasswords(caldavAccount) viewModel.refreshPasswords(account)
} }
.setCancelable(false) .setCancelable(false)
.setNeutralButton(R.string.help) { _, _ -> .setNeutralButton(R.string.help) { _, _ ->
@ -155,9 +141,9 @@ class TasksAccount : BaseAccountPreference() {
} }
private val isGithub: Boolean 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 { (findPreference(R.string.sign_in_with_google) as IconPreference).apply {
if (account.error.isNullOrBlank()) { if (account.error.isNullOrBlank()) {
isVisible = false isVisible = false
@ -243,7 +229,7 @@ class TasksAccount : BaseAccountPreference() {
.setTitle(R.string.delete_tag_confirmation, description) .setTitle(R.string.delete_tag_confirmation, description)
.setMessage(R.string.app_password_delete_confirmation) .setMessage(R.string.app_password_delete_confirmation)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
viewModel.deletePassword(caldavAccount, it.id) viewModel.deletePassword(account, it.id)
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
@ -269,8 +255,6 @@ class TasksAccount : BaseAccountPreference() {
} }
companion object { companion object {
private const val EXTRA_ACCOUNT = "extra_account"
fun newTasksAccountPreference(account: CaldavAccount): Fragment { fun newTasksAccountPreference(account: CaldavAccount): Fragment {
val fragment = TasksAccount() val fragment = TasksAccount()
fragment.arguments = Bundle().apply { fragment.arguments = Bundle().apply {

@ -1,11 +1,14 @@
package org.tasks.ui package org.tasks.ui
import com.todoroo.astrid.api.TagFilter 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.LocalBroadcastManager
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.dao.CaldavDao import org.tasks.data.dao.CaldavDao
import org.tasks.data.entity.TagData
import org.tasks.data.dao.TagDataDao 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.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -38,7 +41,7 @@ class ChipListCache @Inject internal constructor(
fun getTag(tag: String?): TagFilter? = tagDatas[tag] fun getTag(tag: String?): TagFilter? = tagDatas[tag]
init { init {
caldavDao.subscribeToCalendars().observeForever { updated: List<CaldavCalendar> -> updateCaldavCalendars(updated) } caldavDao.subscribeToCalendars().onEach { updateCaldavCalendars(it) }.launchIn(GlobalScope)
tagDataDao.subscribeToTags().observeForever { updated: List<TagData> -> updateTags(updated) } tagDataDao.subscribeToTags().onEach { updateTags(it) }.launchIn(GlobalScope)
} }
} }

@ -50,7 +50,6 @@ android {
} }
dependencies { dependencies {
implementation(libs.androidx.lifecycle.livedata)
implementation(libs.androidx.room) implementation(libs.androidx.room)
implementation(libs.kotlinx.serialization) implementation(libs.kotlinx.serialization)
implementation(libs.timber) implementation(libs.timber)

@ -1,13 +1,18 @@
package org.tasks.data.dao package org.tasks.data.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update 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
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_ETESYNC import org.tasks.data.entity.CaldavAccount.Companion.TYPE_ETESYNC
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_GOOGLE_TASKS 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_OPENTASKS
import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS import org.tasks.data.entity.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.CaldavFilters
import org.tasks.data.entity.CaldavTask import org.tasks.data.entity.CaldavTask
import org.tasks.data.CaldavTaskContainer import org.tasks.data.entity.Task
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.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
const val APPLE_EPOCH = 978307200000L // 1/1/2001 GMT const val APPLE_EPOCH = 978307200000L // 1/1/2001 GMT
@ -32,7 +32,7 @@ abstract class CaldavDao {
abstract fun listCount(account: String): Int abstract fun listCount(account: String): Int
@Query("SELECT * FROM caldav_lists") @Query("SELECT * FROM caldav_lists")
abstract fun subscribeToCalendars(): LiveData<List<CaldavCalendar>> abstract fun subscribeToCalendars(): Flow<List<CaldavCalendar>>
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1") @Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :uuid LIMIT 1")
abstract suspend fun getCalendarByUuid(uuid: String): CaldavCalendar? abstract suspend fun getCalendarByUuid(uuid: String): CaldavCalendar?
@ -55,7 +55,7 @@ abstract class CaldavDao {
abstract suspend fun getAccount(type: Int, username: String): CaldavAccount? abstract suspend fun getAccount(type: Int, username: String): CaldavAccount?
@Query("SELECT * FROM caldav_accounts WHERE cda_id = :id") @Query("SELECT * FROM caldav_accounts WHERE cda_id = :id")
abstract fun watchAccount(id: Long): LiveData<CaldavAccount> abstract fun watchAccount(id: Long): Flow<CaldavAccount?>
@Query(""" @Query("""
SELECT * SELECT *
@ -66,7 +66,7 @@ ORDER BY CASE cda_account_type
ELSE 1 ELSE 1
END, UPPER(cda_name) END, UPPER(cda_name)
""") """)
abstract fun watchAccounts(): LiveData<List<CaldavAccount>> abstract fun watchAccounts(): Flow<List<CaldavAccount>>
@Query(""" @Query("""
SELECT * SELECT *

@ -1,20 +1,19 @@
package org.tasks.data.dao package org.tasks.data.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import org.tasks.data.entity.Geofence
import org.tasks.data.Location import org.tasks.data.Location
import org.tasks.data.LocationFilters import org.tasks.data.LocationFilters
import org.tasks.data.MergedGeofence import org.tasks.data.MergedGeofence
import org.tasks.data.NO_ORDER import org.tasks.data.NO_ORDER
import org.tasks.data.entity.Place
import org.tasks.data.PlaceUsage import org.tasks.data.PlaceUsage
import org.tasks.data.entity.Alarm.Companion.TYPE_SNOOZE 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 import org.tasks.time.DateTimeUtils2.currentTimeMillis
@Dao @Dao
@ -110,7 +109,7 @@ interface LocationDao {
suspend fun getPlace(uid: String): Place? 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") @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<List<PlaceUsage>> suspend fun getPlaceUsage(): List<PlaceUsage>
@Query("SELECT * FROM places WHERE latitude LIKE :latitude AND longitude LIKE :longitude") @Query("SELECT * FROM places WHERE latitude LIKE :latitude AND longitude LIKE :longitude")
suspend fun findPlace(latitude: String, longitude: String): Place? suspend fun findPlace(latitude: String, longitude: String): Place?

@ -1,17 +1,17 @@
package org.tasks.data.dao package org.tasks.data.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import org.tasks.data.entity.Principal import kotlinx.coroutines.flow.Flow
import org.tasks.data.entity.PrincipalAccess
import org.tasks.data.PrincipalWithAccess import org.tasks.data.PrincipalWithAccess
import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Principal
import org.tasks.data.entity.PrincipalAccess
@Dao @Dao
interface PrincipalDao { interface PrincipalDao {
@ -72,5 +72,5 @@ WHERE list = :list
@Transaction @Transaction
@Query("SELECT * FROM principal_access WHERE list = :id") @Query("SELECT * FROM principal_access WHERE list = :id")
fun getPrincipals(id: Long): LiveData<List<PrincipalWithAccess>> fun getPrincipals(id: Long): Flow<List<PrincipalWithAccess>>
} }

@ -1,25 +1,25 @@
package org.tasks.data.dao package org.tasks.data.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import org.tasks.data.entity.Task import kotlinx.coroutines.flow.Flow
import org.tasks.data.UUIDHelper
import org.tasks.data.NO_ORDER 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.TagFilters
import org.tasks.data.UUIDHelper
import org.tasks.data.db.DbUtils 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 import org.tasks.time.DateTimeUtils2.currentTimeMillis
@Dao @Dao
abstract class TagDataDao { abstract class TagDataDao {
@Query("SELECT * FROM tagdata") @Query("SELECT * FROM tagdata")
abstract fun subscribeToTags(): LiveData<List<TagData>> abstract fun subscribeToTags(): Flow<List<TagData>>
@Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1") @Query("SELECT * FROM tagdata WHERE name = :name COLLATE NOCASE LIMIT 1")
abstract suspend fun getTagByName(name: String): TagData? abstract suspend fun getTagByName(name: String): TagData?

@ -7,16 +7,16 @@ import androidx.room.RawQuery
import androidx.room.Update import androidx.room.Update
import androidx.room.withTransaction import androidx.room.withTransaction
import androidx.sqlite.db.SimpleSQLiteQuery 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.BuildConfig
import org.tasks.data.TaskContainer 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.chunkedMap
import org.tasks.data.db.SuspendDbUtils.eachChunk 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 org.tasks.time.DateTimeUtils2
import timber.log.Timber import timber.log.Timber
@ -149,7 +149,7 @@ FROM (
@Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children) AND _id != :parent") @Query("UPDATE tasks SET parent = :parent WHERE _id IN (:children) AND _id != :parent")
internal abstract suspend fun setParentInternal(parent: Long, children: List<Long>) internal abstract suspend fun setParentInternal(parent: Long, children: List<Long>)
@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) abstract suspend fun setLastNotified(id: Long, timestamp: Long)
suspend fun getChildren(id: Long): List<Long> = getChildren(listOf(id)) suspend fun getChildren(id: Long): List<Long> = getChildren(listOf(id))

@ -213,7 +213,6 @@
+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) +| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*)
++--- project :data ++--- project :data
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) +| +--- 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-ktx:2.6.1
+| | +--- androidx.room:room-common:2.6.1 +| | +--- androidx.room:room-common:2.6.1
+| | | +--- androidx.annotation:annotation:1.3.0 -> 1.7.1 (*) +| | | +--- androidx.annotation:annotation:1.3.0 -> 1.7.1 (*)

@ -574,7 +574,6 @@
+| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) +| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*)
++--- project :data ++--- project :data
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.9.23 (*) +| +--- 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-ktx:2.6.1
+| | +--- androidx.room:room-common:2.6.1 +| | +--- androidx.room:room-common:2.6.1
+| | | +--- androidx.annotation:annotation:1.3.0 -> 1.7.1 (*) +| | | +--- androidx.annotation:annotation:1.3.0 -> 1.7.1 (*)

Loading…
Cancel
Save