Skip to content

Commit 736662d

Browse files
Merge branch 'develop' of https://github.com/TechbeeAT/jtxBoard into develop
2 parents 23e8376 + 4ea9c27 commit 736662d

File tree

6 files changed

+177
-0
lines changed

6 files changed

+177
-0
lines changed

app/src/main/java/at/techbee/jtx/database/ICalDatabaseDao.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,13 @@ interface ICalDatabaseDao {
475475
@Delete
476476
fun deleteICalCollection(collection: ICalCollection)
477477

478+
/**
479+
* Delete ICalCollections by their Ids
480+
* @param [collectionIds] of the collections to be deleted.
481+
*/
482+
@Query("DELETE FROM $TABLE_NAME_COLLECTION WHERE $COLUMN_COLLECTION_ID in (:collectionIds)")
483+
fun deleteICalCollectionsByIds(collectionIds: List<Long>)
484+
478485
/**
479486
* Delete all collections of an account.
480487
* @param [accountName] and [accountType] of the Account to be deleted.

app/src/main/java/at/techbee/jtx/ui/list/ListScreenTabContainer.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import android.content.Context
1414
import android.content.pm.PackageManager
1515
import android.location.LocationListener
1616
import android.location.LocationManager
17+
import android.os.Build
1718
import android.widget.Toast
1819
import androidx.activity.compose.rememberLauncherForActivityResult
1920
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
@@ -54,9 +55,11 @@ import androidx.compose.material3.rememberDrawerState
5455
import androidx.compose.material3.rememberModalBottomSheetState
5556
import androidx.compose.runtime.Composable
5657
import androidx.compose.runtime.LaunchedEffect
58+
import androidx.compose.runtime.collectAsState
5759
import androidx.compose.runtime.derivedStateOf
5860
import androidx.compose.runtime.getValue
5961
import androidx.compose.runtime.livedata.observeAsState
62+
import androidx.compose.runtime.mutableStateListOf
6063
import androidx.compose.runtime.mutableStateOf
6164
import androidx.compose.runtime.remember
6265
import androidx.compose.runtime.rememberCoroutineScope
@@ -75,9 +78,12 @@ import androidx.compose.ui.text.style.TextAlign
7578
import androidx.compose.ui.text.style.TextOverflow
7679
import androidx.compose.ui.unit.dp
7780
import androidx.core.app.ActivityCompat
81+
import androidx.lifecycle.Lifecycle
82+
import androidx.lifecycle.compose.LocalLifecycleOwner
7883
import androidx.lifecycle.viewmodel.compose.viewModel
7984
import androidx.navigation.NavHostController
8085
import at.techbee.jtx.R
86+
import at.techbee.jtx.database.ICalCollection
8187
import at.techbee.jtx.database.ICalDatabase
8288
import at.techbee.jtx.database.ICalObject
8389
import at.techbee.jtx.database.Module
@@ -92,6 +98,7 @@ import at.techbee.jtx.ui.GlobalStateHolder
9298
import at.techbee.jtx.ui.reusable.appbars.JtxNavigationDrawer
9399
import at.techbee.jtx.ui.reusable.destinations.DetailDestination
94100
import at.techbee.jtx.ui.reusable.dialogs.CollectionSelectorDialog
101+
import at.techbee.jtx.ui.reusable.dialogs.DeleteObsoleteCollectionsDialog
95102
import at.techbee.jtx.ui.reusable.dialogs.DeleteSelectedDialog
96103
import at.techbee.jtx.ui.reusable.dialogs.ErrorOnUpdateDialog
97104
import at.techbee.jtx.ui.reusable.dialogs.UpdateEntriesDialog
@@ -221,6 +228,7 @@ fun ListScreenTabContainer(
221228
var showDeleteSelectedDialog by rememberSaveable { mutableStateOf(false) }
222229
var showUpdateEntriesDialog by rememberSaveable { mutableStateOf(false) }
223230
var showCollectionSelectorDialog by rememberSaveable { mutableStateOf(false) }
231+
val obsoleteCollections = remember { mutableStateListOf<ICalCollection>() }
224232

225233
val availableSyncApps = SyncUtil.availableSyncApps(context)
226234
var isRefreshing by remember { mutableStateOf(false) }
@@ -250,6 +258,18 @@ fun ListScreenTabContainer(
250258
}
251259
}
252260

261+
val lifecycleOwner = LocalLifecycleOwner.current
262+
val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsState()
263+
LaunchedEffect(lifecycleState) {
264+
if (lifecycleState == Lifecycle.State.RESUMED) {
265+
scope.launch(Dispatchers.IO) {
266+
val allCollections = database.getAllCollectionsSync()
267+
obsoleteCollections.clear()
268+
obsoleteCollections.addAll(SyncUtil.getObsoleteCollections(allCollections, context))
269+
}
270+
}
271+
}
272+
253273

254274
val drawerState = rememberDrawerState(DrawerValue.Closed)
255275
val filterSheetState = rememberModalBottomSheetState()
@@ -303,6 +323,17 @@ fun ListScreenTabContainer(
303323
)
304324
}
305325

326+
if (obsoleteCollections.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // don't show it for versions of Android 7 and lower as this was not recognized properly in the past
327+
DeleteObsoleteCollectionsDialog(
328+
collections = obsoleteCollections,
329+
onConfirm = {
330+
listViewModel.deleteCollections(obsoleteCollections.toList()) // adding toList() here, otherwise the list would be cleared before it's processed
331+
obsoleteCollections.clear()
332+
},
333+
onDismiss = { obsoleteCollections.clear() }
334+
)
335+
}
336+
306337
if(getActiveViewModel().sqlConstraintException.value) {
307338
ErrorOnUpdateDialog(onConfirm = { getActiveViewModel().sqlConstraintException.value = false })
308339
}

app/src/main/java/at/techbee/jtx/ui/list/ListViewModel.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.lifecycle.switchMap
2626
import androidx.lifecycle.viewModelScope
2727
import androidx.preference.PreferenceManager
2828
import androidx.sqlite.db.SimpleSQLiteQuery
29+
import at.techbee.jtx.MainActivity2
2930
import at.techbee.jtx.NotificationPublisher
3031
import at.techbee.jtx.R
3132
import at.techbee.jtx.database.Classification
@@ -458,6 +459,17 @@ open class ListViewModel(application: Application, val module: Module) : Android
458459
}
459460
}
460461

462+
/**
463+
* Deletes the selected collections
464+
* @param collections to be deleted
465+
*/
466+
fun deleteCollections(collections: List<ICalCollection>) {
467+
viewModelScope.launch(Dispatchers.IO) {
468+
databaseDao.deleteICalCollectionsByIds(collections.map { it.collectionId })
469+
MainActivity2.restoreNotificationChannels(_application)
470+
}
471+
}
472+
461473
/**
462474
* Notifies the contentObservers
463475
* schedules the notifications
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package at.techbee.jtx.ui.reusable.dialogs
2+
3+
/*
4+
* Copyright (c) Techbee e.U.
5+
* All rights reserved. This program and the accompanying materials
6+
* are made available under the terms of the GNU Public License v3.0
7+
* which accompanies this distribution, and is available at
8+
* http://www.gnu.org/licenses/gpl.html
9+
*/
10+
11+
import androidx.compose.foundation.layout.Arrangement
12+
import androidx.compose.foundation.layout.Column
13+
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.rememberScrollState
15+
import androidx.compose.foundation.verticalScroll
16+
import androidx.compose.material3.AlertDialog
17+
import androidx.compose.material3.MaterialTheme
18+
import androidx.compose.material3.Text
19+
import androidx.compose.material3.TextButton
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.text.font.FontStyle
24+
import androidx.compose.ui.tooling.preview.Preview
25+
import androidx.compose.ui.unit.dp
26+
import at.techbee.jtx.R
27+
import at.techbee.jtx.database.ICalCollection
28+
29+
30+
@Composable
31+
fun DeleteObsoleteCollectionsDialog(
32+
collections: List<ICalCollection>,
33+
onConfirm: () -> Unit,
34+
onDismiss: () -> Unit
35+
) {
36+
37+
AlertDialog(
38+
onDismissRequest = { onDismiss() },
39+
title = { Text(stringResource(id = R.string.dialog_delete_obsolete_collections_title)) },
40+
text = {
41+
Column(
42+
verticalArrangement = Arrangement.spacedBy(8.dp),
43+
modifier = Modifier.verticalScroll(rememberScrollState())
44+
) {
45+
Text(
46+
text = stringResource(R.string.dialog_delete_obsolete_collections_message),
47+
modifier = Modifier.padding(bottom = 8.dp)
48+
)
49+
50+
Text(
51+
text = collections.joinToString(separator = System.lineSeparator()) { "${it.displayName ?: ""} (${it.accountName})" },
52+
fontStyle = FontStyle.Italic
53+
)
54+
}
55+
},
56+
confirmButton = {
57+
TextButton(
58+
onClick = {
59+
onConfirm()
60+
}
61+
) {
62+
Text(stringResource(id = R.string.delete))
63+
}
64+
},
65+
dismissButton = {
66+
TextButton(
67+
onClick = {
68+
onDismiss()
69+
}
70+
) {
71+
Text( stringResource(id = R.string.cancel))
72+
}
73+
}
74+
)
75+
76+
}
77+
78+
@Preview(showBackground = true)
79+
@Composable
80+
fun DeleteObsoleteCollectionsDialog_Preview() {
81+
MaterialTheme {
82+
83+
DeleteObsoleteCollectionsDialog(
84+
collections = listOf(
85+
ICalCollection(
86+
displayName = "Collection 1",
87+
accountName = "Account 1"),
88+
ICalCollection(
89+
displayName = "Collection 2",
90+
accountName = "Account 2"
91+
)
92+
),
93+
onConfirm = { },
94+
onDismiss = { }
95+
)
96+
}
97+
}

app/src/main/java/at/techbee/jtx/util/SyncUtil.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package at.techbee.jtx.util
1010

1111
import android.accounts.Account
12+
import android.accounts.AccountManager
1213
import android.content.ActivityNotFoundException
1314
import android.content.ContentResolver
1415
import android.content.Context
@@ -27,6 +28,8 @@ import at.techbee.jtx.BuildConfig
2728
import at.techbee.jtx.R
2829
import at.techbee.jtx.SYNC_PROVIDER_AUTHORITY
2930
import at.techbee.jtx.contract.JtxContract
31+
import at.techbee.jtx.database.ICalCollection
32+
import at.techbee.jtx.database.ICalCollection.Factory.LOCAL_ACCOUNT_TYPE
3033

3134
const val TAG = "SyncUtil"
3235

@@ -55,6 +58,31 @@ class SyncUtil {
5558
}
5659
}
5760

61+
62+
/**
63+
* Retrieves a list of all collections that have an account that cannot be found in the account manager anymore
64+
*
65+
* @param collections The list of collections to check.
66+
* @param context The application context.
67+
* @return A list of obsolete collections
68+
*/
69+
fun getObsoleteCollections(collections: List<ICalCollection>, context: Context): List<ICalCollection> {
70+
71+
val foundAccounts = mutableSetOf<Account>()
72+
collections.map { it.accountType }.distinct().forEach { accountType ->
73+
if(accountType == LOCAL_ACCOUNT_TYPE)
74+
return@forEach
75+
val account = AccountManager.get(context).getAccountsByType(accountType)
76+
foundAccounts.addAll(account)
77+
}
78+
val obsoleteCollections = mutableListOf<ICalCollection>()
79+
collections.forEach { collection ->
80+
if(!foundAccounts.contains(collection.getAccount()) && collection.accountType != LOCAL_ACCOUNT_TYPE)
81+
obsoleteCollections.add(collection)
82+
}
83+
return obsoleteCollections
84+
}
85+
5886
fun showSyncRequestedToast(context: Context) = Toast.makeText(context, context.getString(R.string.toast_sync_requested), Toast.LENGTH_SHORT).show()
5987

6088
/**

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ Thank you!"</string>
510510
<string name="dialog_edit_stored_resource_title">"Add/edit resource preset"</string>
511511
<string name="dialog_edit_stored_status_title">"Add/edit status preset"</string>
512512
<string name="dialog_add_audio_entry_title">"Add audio entry"</string>
513+
<string name="dialog_delete_obsolete_collections_title">"Delete collections of missing accounts?"</string>
514+
<string name="dialog_delete_obsolete_collections_message">"The accounts of the following collections cannot be found anymore. Would you like to delete them?"</string>
513515
<string name="menu_list_viewmode_compact">"Compact view"</string>
514516
<string name="menu_list_viewmode_kanban">"Kanban view"</string>
515517
<string name="menu_list_viewmode_week">"Week view"</string>

0 commit comments

Comments
 (0)