Add local delete of archived projects

This commit is contained in:
2026-09-10 00:34:02 +02:00
parent d6ee50bc34
commit 5bc83e22ba
5 changed files with 91 additions and 67 deletions
@@ -132,6 +132,10 @@ fun BillsListScreen(
onProjectAction(projectOptionsProjectId, 1) onProjectAction(projectOptionsProjectId, 1)
viewModel.showProjectOptionsDialogByProjectId = null viewModel.showProjectOptionsDialogByProjectId = null
}, },
onForgetProject = {
onProjectAction(projectOptionsProjectId, 9)
viewModel.showProjectOptionsDialogByProjectId = null
},
onManageMembers = { onManageMembers = {
onProjectAction(projectOptionsProjectId, 2) onProjectAction(projectOptionsProjectId, 2)
viewModel.showProjectOptionsDialogByProjectId = null viewModel.showProjectOptionsDialogByProjectId = null
@@ -408,11 +412,9 @@ fun BillsListScreen(
.pullRefresh(pullRefreshState)) { .pullRefresh(pullRefreshState)) {
when { when {
viewModel.showNoProjects -> EmptyProjectsState(onAccountSwitcherClick, onAddProjectClick) viewModel.showNoProjects -> EmptyProjectsState(onAccountSwitcherClick, onAddProjectClick)
(viewModel.isRefreshing || viewModel.isLoadingBills) && viewModel.bills.isEmpty() ->
LoadingBillsState()
viewModel.showNoMembers -> EmptyMembersState() viewModel.showNoMembers -> EmptyMembersState()
// A large project takes a long time on its first sync. Until that finishes there
// is nothing stored for it yet, and reporting that as "no bills" tells the user
// the project is empty when it is still downloading.
viewModel.isRefreshing && viewModel.bills.isEmpty() -> LoadingBillsState()
viewModel.showNoBills -> EmptyBillsState() viewModel.showNoBills -> EmptyBillsState()
viewModel.bills.isEmpty() -> EmptyState() viewModel.bills.isEmpty() -> EmptyState()
else -> { else -> {
@@ -270,6 +270,7 @@ class BillsListViewActivity :
8 -> { 8 -> {
startActivity(net.helcel.cowspent.android.label.LabelManagementActivity.createIntent(this@BillsListViewActivity, pid)) startActivity(net.helcel.cowspent.android.label.LabelManagementActivity.createIntent(this@BillsListViewActivity, pid))
} }
9 -> onRemoveProjectClick(pid)
} }
}, },
onAccountSwitcherClick = { onAccountSwitcherClick = {
@@ -377,6 +378,8 @@ class BillsListViewActivity :
fun onProjectClick(projectId: Long) { fun onProjectClick(projectId: Long) {
if (viewModel.selectedProjectId != projectId) { if (viewModel.selectedProjectId != projectId) {
viewModel.selectedMemberId = null viewModel.selectedMemberId = null
viewModel.bills = emptyList()
viewModel.isLoadingBills = true
} }
setSelectedProject(projectId) setSelectedProject(projectId)
navigationSelection = Category(null, null) navigationSelection = Category(null, null)
@@ -410,6 +413,9 @@ class BillsListViewActivity :
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.deleteProject(projectId) db.deleteProject(projectId)
// Otherwise the next account sync sees a project the account offers
// with no local row, and creates it again.
CowspentServerSyncHelper.forgetAccountProject(applicationContext, proj)
PreferenceManager.getDefaultSharedPreferences(applicationContext) PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit { remove(lastProjectSyncKey(projectId)) } .edit { remove(lastProjectSyncKey(projectId)) }
val dbProjects = db.projects val dbProjects = db.projects
@@ -621,76 +627,80 @@ class BillsListViewActivity :
val selectedProjectId = PreferenceManager.getDefaultSharedPreferences(applicationContext).getLong("selected_project", 0) val selectedProjectId = PreferenceManager.getDefaultSharedPreferences(applicationContext).getLong("selected_project", 0)
lifecycleScope.launch { lifecycleScope.launch {
val (projId, projName) = withContext(Dispatchers.IO) { try {
if (selectedProjectId != 0L) { val (projId, projName) = withContext(Dispatchers.IO) {
db.getProject(selectedProjectId)?.let { if (selectedProjectId != 0L) {
it.id to (if (it.name == "null" || it.name.isEmpty()) it.remoteId else it.name) db.getProject(selectedProjectId)?.let {
} ?: (0L to "") it.id to (if (it.name == "null" || it.name.isEmpty()) it.remoteId else it.name)
} else { } ?: (0L to "")
0L to "" } else {
} 0L to ""
} }
val title = if (selectedProjectId != 0L) projName else getString(R.string.app_name)
setSelectedProject(selectedProjectId)
viewModel.title = title
val query = viewModel.searchQuery.ifEmpty { null }
val (ljItems, memberCount) = withContext(Dispatchers.IO) {
val db = CowspentSQLiteOpenHelper.getInstance(applicationContext)
val billList: List<DBBill> = if (projId != 0L) {
db.searchBills(query, projId)
} else {
ArrayList()
} }
val bills = billList.filter { val title = if (selectedProjectId != 0L) projName else getString(R.string.app_name)
val mid = viewModel.selectedMemberId
mid == null || mid == it.payerId || it.billOwersIds.contains(mid) setSelectedProject(selectedProjectId)
viewModel.title = title
val query = viewModel.searchQuery.ifEmpty { null }
val (ljItems, memberCount) = withContext(Dispatchers.IO) {
val db = CowspentSQLiteOpenHelper.getInstance(applicationContext)
val billList: List<DBBill> = if (projId != 0L) {
db.searchBills(query, projId)
} else {
ArrayList()
}
val bills = billList.filter {
val mid = viewModel.selectedMemberId
mid == null || mid == it.payerId || it.billOwersIds.contains(mid)
}
viewModel.hasUnlabeledBills = bills.any { it.categoryId == 0L && it.state != DBBill.STATE_DELETED }
val projectMembers = db.getMembersOfProject(projId, null)
val memberMap = projectMembers.associateBy { it.id }
val projectPaymentModes = db.getPaymentModes(projId).associateBy { it.id }
val projectCategories = db.getCategories(projId).associateBy { it.id }
BillFormatter.formatBills(
bills,
memberMap,
projectCategories,
projectPaymentModes
)
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT)
val itemList = BillsListUtils.groupAndSectionBills(
bills,
memberMap,
sdf,
applicationContext
)
itemList to projectMembers.size
} }
viewModel.hasUnlabeledBills = bills.any { it.categoryId == 0L && it.state != DBBill.STATE_DELETED } viewModel.showNoProjects = false
viewModel.showNoMembers = false
viewModel.showNoBills = false
val projectMembers = db.getMembersOfProject(projId, null) when {
val memberMap = projectMembers.associateBy { it.id } memberCount == 0 -> {
viewModel.showNoMembers = true
val projectPaymentModes = db.getPaymentModes(projId).associateBy { it.id } }
val projectCategories = db.getCategories(projId).associateBy { it.id } ljItems.isEmpty() -> {
viewModel.showNoBills = true
BillFormatter.formatBills( viewModel.bills = emptyList()
bills, }
memberMap, else -> {
projectCategories, viewModel.bills = ljItems
projectPaymentModes }
)
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.ROOT)
val itemList = BillsListUtils.groupAndSectionBills(
bills,
memberMap,
sdf,
applicationContext
)
itemList to projectMembers.size
}
viewModel.showNoProjects = false
viewModel.showNoMembers = false
viewModel.showNoBills = false
when {
memberCount == 0 -> {
viewModel.showNoMembers = true
}
ljItems.isEmpty() -> {
viewModel.showNoBills = true
viewModel.bills = emptyList()
}
else -> {
viewModel.bills = ljItems
} }
} finally {
viewModel.isLoadingBills = false
} }
} }
} }
@@ -20,6 +20,7 @@ class BillsListViewModel : ViewModel() {
var selectedMemberId by mutableStateOf<Long?>(null) var selectedMemberId by mutableStateOf<Long?>(null)
var bills by mutableStateOf<List<Item>>(emptyList()) var bills by mutableStateOf<List<Item>>(emptyList())
var isRefreshing by mutableStateOf(false) var isRefreshing by mutableStateOf(false)
var isLoadingBills by mutableStateOf(false)
var searchQuery by mutableStateOf("") var searchQuery by mutableStateOf("")
var title by mutableStateOf("") var title by mutableStateOf("")
var accountName by mutableStateOf("") var accountName by mutableStateOf("")
@@ -24,6 +24,7 @@ import net.helcel.cowspent.model.ProjectType
fun ProjectOptionsDialogContent( fun ProjectOptionsDialogContent(
onEditProject: () -> Unit, onEditProject: () -> Unit,
onRemoveProject: () -> Unit, onRemoveProject: () -> Unit,
onForgetProject: () -> Unit = {},
onManageMembers: () -> Unit, onManageMembers: () -> Unit,
onManageCurrencies: () -> Unit, onManageCurrencies: () -> Unit,
onManageLabels: () -> Unit, onManageLabels: () -> Unit,
@@ -73,6 +74,15 @@ fun ProjectOptionsDialogContent(
val archiveLabel = if (isArchived) stringResource(R.string.action_unarchive) else stringResource(R.string.action_archive) val archiveLabel = if (isArchived) stringResource(R.string.action_unarchive) else stringResource(R.string.action_archive)
val archiveIcon = if (isArchived) Icons.Default.Unarchive else Icons.Default.Archive val archiveIcon = if (isArchived) Icons.Default.Unarchive else Icons.Default.Archive
row1.add(ProjectOption(archiveLabel, archiveIcon, onRemoveProject)) row1.add(ProjectOption(archiveLabel, archiveIcon, onRemoveProject))
if (isArchived) {
row1.add(
ProjectOption(
stringResource(R.string.action_forget),
Icons.Default.Delete,
onForgetProject
)
)
}
} else { } else {
row1.add(ProjectOption(stringResource(R.string.action_delete), Icons.Default.Delete, onRemoveProject)) row1.add(ProjectOption(stringResource(R.string.action_delete), Icons.Default.Delete, onRemoveProject))
} }
+1
View File
@@ -17,6 +17,7 @@
<string name="simple_back">Back</string> <string name="simple_back">Back</string>
<string name="action_archive">Archive</string> <string name="action_archive">Archive</string>
<string name="action_unarchive">Unarchive</string> <string name="action_unarchive">Unarchive</string>
<string name="action_forget">Delete locally</string>
<string name="action_export">Export</string> <string name="action_export">Export</string>
<string name="action_stats">Stats</string> <string name="action_stats">Stats</string>
<string name="action_settle">Settle</string> <string name="action_settle">Settle</string>