Implement proper network check before manual sync (bitfireAT/davx5#259)

* Implement proper network check before manual sync

Do a proper connectivity check, to show a message to the user, about the sync being scheduled for when connectivity is available, before enqueueing the SyncWorker.

* Update the sync status in accounts adapter to include pending syncs

This is relevant for when a manual sync is triggered without connectivity.

* Replace Toast by Snackbar

* Replace ViewModel by AndroidViewModel

---------

Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
Sunik Kupfer 2023-05-11 13:40:23 +02:00 committed by Ricki Hirner
parent 5cbf14fa27
commit 2ebb40b152
5 changed files with 54 additions and 22 deletions

View file

@ -235,7 +235,13 @@ class AccountListFragment: Fragment() {
// Accounts
private val accountsUpdated = MutableLiveData<Boolean>()
private val syncWorkersActive = SyncWorker.exists(application, listOf(WorkInfo.State.RUNNING))
private val syncWorkersActive = SyncWorker.exists(
application,
listOf(
WorkInfo.State.RUNNING,
WorkInfo.State.ENQUEUED
)
)
val accounts = object : MediatorLiveData<List<AccountInfo>>() {
init {

View file

@ -6,23 +6,28 @@ package at.bitfire.davdroid.ui
import android.accounts.AccountManager
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.core.view.GravityCompat
import androidx.lifecycle.AndroidViewModel
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.ActivityAccountsBinding
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.syncadapter.SyncWorker
import at.bitfire.davdroid.ui.intro.IntroActivity
import at.bitfire.davdroid.ui.setup.LoginActivity
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -38,6 +43,7 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele
@Inject lateinit var accountsDrawerHandler: AccountsDrawerHandler
private lateinit var binding: ActivityAccountsBinding
val model by viewModels<Model>()
override fun onCreate(savedInstanceState: Bundle?) {
@ -110,16 +116,28 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele
if (Build.VERSION.SDK_INT >= 25)
getSystemService<ShortcutManager>()?.reportShortcutUsed(UiUtils.SHORTCUT_SYNC_ALL)
// Check we are connected
if (!SyncWorker.wifiAvailable(applicationContext)) {
Toast.makeText(this, R.string.no_internet_connection, Toast.LENGTH_LONG).show()
return
// Notify user that sync will get enqueued if we're not connected to the internet
model.networkAvailable.value?.let { networkAvailable ->
if (!networkAvailable)
Snackbar.make(binding.drawerLayout, R.string.no_internet_sync_scheduled, Snackbar.LENGTH_LONG).show()
}
// Enqueue sync worker for all accounts and authorities
// Enqueue sync worker for all accounts and authorities. Will sync once internet is available
val accounts = allAccounts()
for (account in accounts)
SyncWorker.enqueueAllAuthorities(this, account)
}
@HiltViewModel
class Model @Inject constructor(
application: Application,
val settings: SettingsManager,
warnings: AppWarningsManager
): AndroidViewModel(application) {
val networkAvailable = warnings.networkAvailable
}
}

View file

@ -7,7 +7,7 @@ package at.bitfire.davdroid.ui.account
import android.accounts.Account
import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.content.Context
import android.app.Application
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -28,12 +28,13 @@ import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.syncadapter.SyncWorker
import at.bitfire.davdroid.ui.AppWarningsManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
@ -86,10 +87,14 @@ class AccountActivity: AppCompatActivity() {
tabsAdapter.calDavSvcId = it
})
binding.sync.setOnClickListener {
// enqueue sync worker for all authorities of this account
SyncWorker.enqueueAllAuthorities(this, model.account)
}
// "Sync now" button
model.networkAvailable.observe(this, Observer { networkAvailable ->
binding.sync.setOnClickListener {
if (!networkAvailable)
Snackbar.make(binding.sync, R.string.no_internet_sync_scheduled, Snackbar.LENGTH_LONG).show()
SyncWorker.enqueueAllAuthorities(this, model.account)
}
})
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -241,25 +246,28 @@ class AccountActivity: AppCompatActivity() {
// model
class Model @AssistedInject constructor(
@ApplicationContext val context: Context,
application: Application,
val db: AppDatabase,
@Assisted val account: Account
): ViewModel(), OnAccountsUpdateListener {
@Assisted val account: Account,
warnings: AppWarningsManager
): AndroidViewModel(application), OnAccountsUpdateListener {
@AssistedFactory
interface Factory {
fun create(account: Account): Model
}
val accountManager = AccountManager.get(context)!!
val accountSettings by lazy { AccountSettings(context, account) }
val accountManager: AccountManager = AccountManager.get(application)
val accountSettings by lazy { AccountSettings(application, account) }
val accountExists = MutableLiveData<Boolean>()
val cardDavService = db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV)
val calDavService = db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV)
val showOnlyPersonal = MutableLiveData<Boolean>()
val showOnlyPersonal_writable = MutableLiveData<Boolean>()
val showOnlyPersonalWritable = MutableLiveData<Boolean>()
val networkAvailable = warnings.networkAvailable
init {
@ -267,7 +275,7 @@ class AccountActivity: AppCompatActivity() {
viewModelScope.launch(Dispatchers.IO) {
accountSettings.getShowOnlyPersonal().let { (value, locked) ->
showOnlyPersonal.postValue(value)
showOnlyPersonal_writable.postValue(locked)
showOnlyPersonalWritable.postValue(locked)
}
}
}

View file

@ -143,7 +143,7 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList
accountModel.showOnlyPersonal.value?.let { value ->
showOnlyPersonal.isChecked = value
}
accountModel.showOnlyPersonal_writable.value?.let { writable ->
accountModel.showOnlyPersonalWritable.value?.let { writable ->
showOnlyPersonal.isEnabled = writable
}
}

View file

@ -17,7 +17,7 @@
<string name="help">Help</string>
<string name="manage_accounts">Manage accounts</string>
<string name="share">Share</string>
<string name="no_internet_connection">No internet connection</string>
<string name="no_internet_sync_scheduled">No internet, scheduling sync</string>
<string name="database_destructive_migration_title">Database corrupted</string>
<string name="database_destructive_migration_text">All accounts have been removed locally.</string>