diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt index f81929f7..98074e9c 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt @@ -23,10 +23,8 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState @@ -39,10 +37,8 @@ import androidx.compose.material.IconButton import androidx.compose.material.IconToggleButton import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProgressIndicatorDefaults import androidx.compose.material.Scaffold import androidx.compose.material.ScaffoldState -import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState import androidx.compose.material.Text import androidx.compose.material.TopAppBar @@ -67,6 +63,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -87,6 +84,7 @@ import at.bitfire.davdroid.syncadapter.BaseSyncWorker import at.bitfire.davdroid.syncadapter.OneTimeSyncWorker import at.bitfire.davdroid.syncadapter.SyncUtils import at.bitfire.davdroid.ui.account.AccountActivity +import at.bitfire.davdroid.ui.account.progressAlpha import at.bitfire.davdroid.ui.composable.ActionCard import at.bitfire.davdroid.ui.intro.IntroActivity import at.bitfire.davdroid.ui.setup.LoginActivity @@ -220,7 +218,7 @@ class AccountsActivity: AppCompatActivity() { // account list AccountList( - accounts = accounts ?: emptyList(), + accounts = accounts ?: emptyMap(), onClickAccount = { account -> val activity = this@AccountsActivity val intent = Intent(activity, AccountActivity::class.java) @@ -255,28 +253,6 @@ class AccountsActivity: AppCompatActivity() { model.syncAllAccounts() } - @Composable - private fun snackbarHost( - snackbarHostState: SnackbarHostState, - scope: CoroutineScope - ): @Composable (SnackbarHostState) -> Unit = { - SnackbarHost(snackbarHostState) - model.syncEnqueued.observeAsState().value?.let { enqueued -> - if (enqueued) - scope.launch { - val msg = getString( - if (warnings.networkAvailable.value == true) - R.string.sync_started - else - R.string.no_internet_sync_scheduled - ) - snackbarHostState.showSnackbar(msg) - } - // reset feedback - model.syncEnqueued.value = null - } - } - @Composable private fun floatingActionButton(): @Composable (() -> Unit) = { val show by model.showAddAccount.observeAsState() @@ -330,28 +306,20 @@ class AccountsActivity: AppCompatActivity() { } - data class AccountInfo( - val account: Account, - val isRefreshing: Boolean, - val isSyncing: Boolean - ) - @HiltViewModel class Model @Inject constructor( application: Application, val db: AppDatabase ): AndroidViewModel(application), OnAccountsUpdateListener { - val syncEnqueued = MutableLiveData() - val accountManager = AccountManager.get(application) private val accountType = application.getString(R.string.account_type) val workManager = WorkManager.getInstance(application) - val runningWorkers = workManager.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING)) + val runningWorkers = workManager.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.ENQUEUED, WorkInfo.State.RUNNING)) val accounts = MutableLiveData>() - val accountInfos = object: MediatorLiveData>() { + val accountInfos = object: MediatorLiveData>() { var myAccounts: Set = emptySet() var workInfos: List = emptyList() init { @@ -368,23 +336,28 @@ class AccountsActivity: AppCompatActivity() { val authorities = SyncUtils.syncAuthorities(application) val collator = Collator.getInstance() postValue(myAccounts - .toList() .sortedWith { a, b -> collator.compare(a.name, b.name) } - .map { account -> + .associateWith { account -> val services = db.serviceDao().getIdsByAccount(account.name) - AccountInfo( - account = account, - isRefreshing = workInfos.any { info -> - services.any { serviceId -> - info.tags.contains(RefreshCollectionsWorker.workerName(serviceId)) + when { + workInfos.any { info -> + info.state == WorkInfo.State.RUNNING && ( + services.any { serviceId -> + info.tags.contains(RefreshCollectionsWorker.workerName(serviceId)) + } || authorities.any { authority -> + info.tags.contains(BaseSyncWorker.commonTag(account, authority)) + } + ) + } -> AccountActivity.Progress.Active + + workInfos.any { info -> + info.state == WorkInfo.State.ENQUEUED && authorities.any { authority -> + info.tags.contains(OneTimeSyncWorker.workerName(account, authority)) } - }, - isSyncing = workInfos.any { info -> - authorities.any { authority -> - info.tags.contains(BaseSyncWorker.commonTag(account, authority)) - } - } - ) + } -> AccountActivity.Progress.Pending + + else -> AccountActivity.Progress.Idle + } }) } } @@ -409,8 +382,6 @@ class AccountsActivity: AppCompatActivity() { if (Build.VERSION.SDK_INT >= 25) context.getSystemService()?.reportShortcutUsed(UiUtils.SHORTCUT_SYNC_ALL) - syncEnqueued.value = true - // Enqueue sync worker for all accounts and authorities. Will sync once internet is available for (account in allAccounts()) OneTimeSyncWorker.enqueueAllAuthorities(context, account, manual = true) @@ -429,7 +400,7 @@ class AccountsActivity: AppCompatActivity() { @Composable fun AccountList( - accounts: List, + accounts: Map, modifier: Modifier = Modifier, onClickAccount: (Account) -> Unit = {} ) { @@ -449,23 +420,35 @@ fun AccountList( ) } else - for (account in accounts) + for ((account, progress) in accounts) Card( backgroundColor = MaterialTheme.colors.secondaryVariant, contentColor = MaterialTheme.colors.onSecondary, modifier = Modifier - .clickable { onClickAccount(account.account) } + .clickable { onClickAccount(account) } .fillMaxWidth() .padding(8.dp) ) { Column { - if (account.isRefreshing || account.isSyncing) - LinearProgressIndicator( - color = MaterialTheme.colors.onSecondary, - modifier = Modifier.fillMaxWidth() - ) - else - Spacer(Modifier.height(ProgressIndicatorDefaults.StrokeWidth)) + val progressAlpha = progressAlpha(progress) + when (progress) { + AccountActivity.Progress.Active -> + LinearProgressIndicator( + color = MaterialTheme.colors.onSecondary, + modifier = Modifier + .alpha(progressAlpha) + .fillMaxWidth() + ) + AccountActivity.Progress.Pending, + AccountActivity.Progress.Idle -> + LinearProgressIndicator( + progress = 1f, + color = MaterialTheme.colors.onSecondary, + modifier = Modifier + .alpha(progressAlpha) + .fillMaxWidth() + ) + } Column(Modifier.padding(8.dp)) { Icon( @@ -477,7 +460,7 @@ fun AccountList( ) Text( - text = account.account.name, + text = account.name, style = MaterialTheme.typography.h5, textAlign = TextAlign.Center, modifier = Modifier @@ -493,31 +476,31 @@ fun AccountList( @Composable @Preview fun AccountList_Preview_Idle() { - AccountList(listOf( - AccountsActivity.AccountInfo( - Account("Account Name", "test"), - isRefreshing = false, - isSyncing = false - ) + AccountList(mapOf( + Account("Account Name", "test") to AccountActivity.Progress.Idle )) } @Composable @Preview -fun AccountList_Preview_IsSyncing() { - AccountList(listOf( - AccountsActivity.AccountInfo( - Account("Account Name", "test"), - isRefreshing = false, - isSyncing = true - ) +fun AccountList_Preview_SyncPending() { + AccountList(mapOf( + Account("Account Name", "test") to AccountActivity.Progress.Pending + )) +} + +@Composable +@Preview +fun AccountList_Preview_Syncing() { + AccountList(mapOf( + Account("Account Name", "test") to AccountActivity.Progress.Active )) } @Composable @Preview fun AccountList_Preview_Empty() { - AccountList(listOf()) + AccountList(emptyMap()) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt index 17752d8c..18981290 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt @@ -10,12 +10,11 @@ import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState @@ -29,7 +28,6 @@ import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProgressIndicatorDefaults import androidx.compose.material.Scaffold import androidx.compose.material.SnackbarDuration import androidx.compose.material.SnackbarHost @@ -63,7 +61,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -75,13 +73,13 @@ import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import at.bitfire.davdroid.R import at.bitfire.davdroid.db.Collection -import at.bitfire.davdroid.util.TaskUtils import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.syncadapter.OneTimeSyncWorker import at.bitfire.davdroid.ui.AppTheme import at.bitfire.davdroid.ui.PermissionsActivity import at.bitfire.davdroid.ui.composable.ActionCard +import at.bitfire.davdroid.util.TaskUtils import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState import dagger.hilt.android.AndroidEntryPoint @@ -703,29 +701,22 @@ fun ServiceTab( Column { // progress indicator - val progressAlpha by animateFloatAsState( - when (progress) { - AccountActivity.Progress.Active -> 1f - AccountActivity.Progress.Pending -> 0.5f - AccountActivity.Progress.Idle -> 0f - }, - label = "progressAlpha" - ) + val progressAlpha = progressAlpha(progress) when (progress) { AccountActivity.Progress.Active -> LinearProgressIndicator( color = MaterialTheme.colors.secondary, modifier = Modifier - .graphicsLayer(alpha = progressAlpha) + .alpha(progressAlpha) .fillMaxWidth() ) - AccountActivity.Progress.Pending -> LinearProgressIndicator( + AccountActivity.Progress.Pending, + AccountActivity.Progress.Idle -> LinearProgressIndicator( color = MaterialTheme.colors.secondary, progress = 1f, modifier = Modifier - .graphicsLayer(alpha = progressAlpha) + .alpha(progressAlpha) .fillMaxWidth() ) - AccountActivity.Progress.Idle -> Spacer(Modifier.height(ProgressIndicatorDefaults.StrokeWidth)) } // permissions warning @@ -752,4 +743,18 @@ fun ServiceTab( modifier = Modifier.weight(1f) ) } +} + +@Composable +fun progressAlpha(progress: AccountActivity.Progress): Float { + val progressAlpha by animateFloatAsState( + when (progress) { + AccountActivity.Progress.Active -> 1f + AccountActivity.Progress.Pending -> 0.5f + AccountActivity.Progress.Idle -> 0f + }, + label = "progressAlpha", + animationSpec = tween(500) + ) + return progressAlpha } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f64019e4..81fef1c4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,11 +15,10 @@ Help Manage accounts Navigate up - No Internet, scheduling sync * optional Options menu Share - Synchronization started + Synchronization started/enqueued Database corrupted All accounts have been removed locally.