Archiving and Label options

This commit is contained in:
soraefir
2026-06-30 20:40:32 +02:00
parent 88469fa96c
commit 678a90ead9
4 changed files with 159 additions and 35 deletions
@@ -65,6 +65,7 @@ import net.helcel.cowspent.android.statistics.ProjectStatisticsActivity
import net.helcel.cowspent.model.DBBill import net.helcel.cowspent.model.DBBill
import net.helcel.cowspent.model.DBMember import net.helcel.cowspent.model.DBMember
import net.helcel.cowspent.model.DBProject import net.helcel.cowspent.model.DBProject
import net.helcel.cowspent.model.ProjectType
import net.helcel.cowspent.model.SectionItem import net.helcel.cowspent.model.SectionItem
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
import net.helcel.cowspent.util.IRefreshBillsListCallback import net.helcel.cowspent.util.IRefreshBillsListCallback
@@ -137,6 +138,10 @@ fun BillsListScreen(
onProjectAction(projectOptionsProjectId, 3) onProjectAction(projectOptionsProjectId, 3)
viewModel.showProjectOptionsDialogByProjectId = null viewModel.showProjectOptionsDialogByProjectId = null
}, },
onManageLabels = {
onProjectAction(projectOptionsProjectId, 8)
viewModel.showProjectOptionsDialogByProjectId = null
},
onStatistics = { onStatistics = {
onProjectAction(projectOptionsProjectId, 4) onProjectAction(projectOptionsProjectId, 4)
viewModel.showProjectOptionsDialogByProjectId = null viewModel.showProjectOptionsDialogByProjectId = null
@@ -155,6 +160,7 @@ fun BillsListScreen(
}, },
onDismiss = { viewModel.showProjectOptionsDialogByProjectId = null }, onDismiss = { viewModel.showProjectOptionsDialogByProjectId = null },
isArchived = proj?.isArchived == true, isArchived = proj?.isArchived == true,
projectType = proj?.type ?: ProjectType.LOCAL,
accessLevel = proj?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN, accessLevel = proj?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN,
isShareable = proj?.isShareable() ?: true isShareable = proj?.isShareable() ?: true
) )
@@ -45,6 +45,7 @@ import net.helcel.cowspent.model.Category
import net.helcel.cowspent.model.DBBill import net.helcel.cowspent.model.DBBill
import net.helcel.cowspent.model.DBProject import net.helcel.cowspent.model.DBProject
import net.helcel.cowspent.model.GroupedBill import net.helcel.cowspent.model.GroupedBill
import net.helcel.cowspent.model.ProjectType
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
import net.helcel.cowspent.persistence.CowspentServerSyncHelper import net.helcel.cowspent.persistence.CowspentServerSyncHelper
import net.helcel.cowspent.theme.ThemeUtils import net.helcel.cowspent.theme.ThemeUtils
@@ -254,13 +255,21 @@ class BillsListViewActivity :
onProjectAction = { pid, actionIndex -> onProjectAction = { pid, actionIndex ->
when (actionIndex) { when (actionIndex) {
0 -> onEditProjectClick(pid) 0 -> onEditProjectClick(pid)
1 -> onRemoveProjectClick(pid) 1 -> {
val proj = db.getProject(pid)
if (proj?.type == ProjectType.COSPEND) {
onArchiveProjectClick(pid)
} else {
onRemoveProjectClick(pid)
}
}
2 -> onManageMembersClick(pid) 2 -> onManageMembersClick(pid)
3 -> onManageCurrenciesClick(pid) 3 -> onManageCurrenciesClick(pid)
4 -> onProjectStatisticsClick(pid) 4 -> onProjectStatisticsClick(pid)
5 -> onSettleProjectClick(pid) 5 -> onSettleProjectClick(pid)
6 -> onShareProjectClick(pid) 6 -> onShareProjectClick(pid)
7 -> onExportProjectClick(pid) 7 -> onExportProjectClick(pid)
8 -> { /* Manage Labels - Placeholder for now */ }
} }
}, },
onAccountSwitcherClick = { onAccountSwitcherClick = {
@@ -423,6 +432,38 @@ class BillsListViewActivity :
} }
} }
private fun onArchiveProjectClick(projectId: Long) {
if (projectId == 0L) return
lifecycleScope.launch {
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) } ?: return@launch
val isArchiving = !proj.isArchived
val newArchivedTs = if (isArchiving) System.currentTimeMillis() / 1000 else 0L
withContext(Dispatchers.IO) {
db.updateProject(
projId = projectId,
newName = null,
newEmail = null,
newPassword = null,
newLastPayerId = null,
newLastSyncedTimestamp = null,
newCurrencyName = null,
newDeletionDisabled = null,
newMyAccessLevel = null,
newBearerToken = null,
newArchivedTs = newArchivedTs
)
}
setupDrawerProjects()
refreshLists()
val msgRes = if (isArchiving) R.string.action_archive else R.string.action_unarchive
showToast(this@BillsListViewActivity, getString(msgRes))
}
}
fun onManageMembersClick(projectId: Long) { fun onManageMembersClick(projectId: Long) {
if (projectId == 0L) return if (projectId == 0L) return
lifecycleScope.launch { lifecycleScope.launch {
@@ -4,6 +4,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Label
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -15,8 +16,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import net.helcel.cowspent.R import net.helcel.cowspent.R
import net.helcel.cowspent.model.DBProject import net.helcel.cowspent.model.DBProject
import net.helcel.cowspent.model.ProjectType
@Composable @Composable
fun ProjectOptionsDialogContent( fun ProjectOptionsDialogContent(
@@ -24,12 +25,14 @@ fun ProjectOptionsDialogContent(
onRemoveProject: () -> Unit, onRemoveProject: () -> Unit,
onManageMembers: () -> Unit, onManageMembers: () -> Unit,
onManageCurrencies: () -> Unit, onManageCurrencies: () -> Unit,
onManageLabels: () -> Unit,
onStatistics: () -> Unit, onStatistics: () -> Unit,
onSettle: () -> Unit, onSettle: () -> Unit,
onShareProject: () -> Unit, onShareProject: () -> Unit,
onExportProject: () -> Unit, onExportProject: () -> Unit,
onDismiss: () -> Unit, onDismiss: () -> Unit,
isArchived: Boolean = false, isArchived: Boolean = false,
projectType: ProjectType = ProjectType.LOCAL,
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN, accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN,
isShareable: Boolean = true isShareable: Boolean = true
) { ) {
@@ -49,41 +52,63 @@ fun ProjectOptionsDialogContent(
modifier = Modifier.padding(bottom = 8.dp) modifier = Modifier.padding(bottom = 8.dp)
) )
val options = mutableListOf<ProjectOption>()
val isMaintainer = accessLevel >= DBProject.ACCESS_LEVEL_MAINTAINER || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN val isMaintainer = accessLevel >= DBProject.ACCESS_LEVEL_MAINTAINER || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN
val isParticipant = accessLevel >= DBProject.ACCESS_LEVEL_PARTICIPANT || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN val isParticipant = accessLevel >= DBProject.ACCESS_LEVEL_PARTICIPANT || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN
val row1 = mutableListOf<ProjectOption>()
val row2 = mutableListOf<ProjectOption>()
val row3 = mutableListOf<ProjectOption>()
// Row 1: Edit, Share, Remove/Archive
if (!isArchived && isMaintainer) { if (!isArchived && isMaintainer) {
options.add(ProjectOption(stringResource(R.string.action_edit_project), Icons.Default.Edit, onEditProject)) row1.add(ProjectOption(stringResource(R.string.action_edit_project), Icons.Default.Edit, onEditProject))
}
options.add(ProjectOption(stringResource(R.string.fab_rm_project), Icons.Default.Delete, onRemoveProject))
if (!isArchived && isMaintainer) {
options.add(ProjectOption(stringResource(R.string.fab_manage_members), Icons.Default.Group, onManageMembers))
options.add(ProjectOption(stringResource(R.string.fab_manage_currencies), Icons.Default.MonetizationOn, onManageCurrencies))
}
options.add(ProjectOption(stringResource(R.string.fab_statistics), Icons.Default.BarChart, onStatistics))
if (!isArchived && isParticipant) {
options.add(ProjectOption(stringResource(R.string.fab_settle), Icons.Default.Handshake, onSettle))
} }
if (isShareable && isParticipant) { if (isShareable && isParticipant) {
options.add(ProjectOption(stringResource(R.string.action_share_project), Icons.Default.Share, onShareProject)) row1.add(ProjectOption(stringResource(R.string.action_share_project), Icons.Default.Share, onShareProject))
}
if (projectType == ProjectType.COSPEND) {
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
row1.add(ProjectOption(archiveLabel, archiveIcon, onRemoveProject))
} else {
row1.add(ProjectOption(stringResource(R.string.fab_rm_project), Icons.Default.Delete, onRemoveProject))
} }
options.add(ProjectOption(stringResource(R.string.fab_export_project), Icons.Default.Download, onExportProject))
// Simple 2-column grid using Rows // Row 2: Manage Member, Manage Labels, Manage Currencies
for (i in options.indices step 2) { if (!isArchived && isMaintainer) {
Row(modifier = Modifier.fillMaxWidth()) { row2.add(ProjectOption(stringResource(R.string.fab_manage_members), Icons.Default.Group, onManageMembers))
ProjectOptionItem( row2.add(ProjectOption(stringResource(R.string.fab_manage_labels), Icons.AutoMirrored.Filled.Label, onManageLabels))
option = options[i], row2.add(ProjectOption(stringResource(R.string.fab_manage_currencies), Icons.Default.MonetizationOn, onManageCurrencies))
modifier = Modifier.weight(1f) }
)
if (i + 1 < options.size) { // Row 3: Statistics, Settle, Export
row3.add(ProjectOption(stringResource(R.string.fab_statistics), Icons.Default.BarChart, onStatistics))
if (!isArchived && isParticipant) {
row3.add(ProjectOption(stringResource(R.string.fab_settle), Icons.Default.Handshake, onSettle))
}
row3.add(ProjectOption(stringResource(R.string.fab_export_project), Icons.Default.Download, onExportProject))
val allRows = listOf(row1, row2, row3).filter { it.isNotEmpty() }
allRows.forEach { rowOptions ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
val emptySpace = 3 - rowOptions.size
if (emptySpace > 0) {
Spacer(modifier = Modifier.weight(emptySpace / 2f))
}
rowOptions.forEach { option ->
ProjectOptionItem( ProjectOptionItem(
option = options[i + 1], option = option,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
} else { }
Spacer(modifier = Modifier.weight(1f))
if (emptySpace > 0) {
Spacer(modifier = Modifier.weight(emptySpace / 2f))
} }
} }
} }
@@ -145,10 +170,59 @@ fun ProjectOptionsDialogPreview() {
onManageCurrencies = {}, onManageCurrencies = {},
onStatistics = {}, onStatistics = {},
onSettle = {}, onSettle = {},
onManageLabels = {},
onShareProject = {}, onShareProject = {},
onExportProject = {}, onExportProject = {},
onDismiss = {}, onDismiss = {},
isArchived = false, isArchived = false,
projectType = ProjectType.COSPEND,
accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
isShareable = true
)
}
}
@Preview(showBackground = true)
@Composable
fun ProjectOptionsDialogPreview2() {
MaterialTheme {
ProjectOptionsDialogContent(
onEditProject = {},
onRemoveProject = {},
onManageMembers = {},
onManageCurrencies = {},
onStatistics = {},
onSettle = {},
onManageLabels = {},
onShareProject = {},
onExportProject = {},
onDismiss = {},
isArchived = true,
projectType = ProjectType.COSPEND,
accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
isShareable = true
)
}
}
@Preview(showBackground = true)
@Composable
fun ProjectOptionsDialogPreview3() {
MaterialTheme {
ProjectOptionsDialogContent(
onEditProject = {},
onRemoveProject = {},
onManageMembers = {},
onManageCurrencies = {},
onStatistics = {},
onSettle = {},
onManageLabels = {},
onShareProject = {},
onExportProject = {},
onDismiss = {},
isArchived = false,
projectType = ProjectType.LOCAL,
accessLevel = DBProject.ACCESS_LEVEL_ADMIN, accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
isShareable = true isShareable = true
) )
+12 -9
View File
@@ -8,8 +8,8 @@
<string name="action_scan_qrcode">Scan QRCode from Cospend/Cowspent</string> <string name="action_scan_qrcode">Scan QRCode from Cospend/Cowspent</string>
<string name="action_add_project">Add project</string> <string name="action_add_project">Add project</string>
<string name="action_save_bill">Save bill</string> <string name="action_save_bill">Save bill</string>
<string name="action_edit_project">Edit project</string> <string name="action_edit_project">Edit</string>
<string name="action_share_project">Share project</string> <string name="action_share_project">Share</string>
<string name="setting_what">What?</string> <string name="setting_what">What?</string>
<string name="setting_payer">Who paid?</string> <string name="setting_payer">Who paid?</string>
<string name="setting_all">All</string> <string name="setting_all">All</string>
@@ -82,12 +82,15 @@
<string name="simple_share_share">Share Cowspent link</string> <string name="simple_share_share">Share Cowspent link</string>
<string name="menu_delete_project_remote">Delete project on server</string> <string name="menu_delete_project_remote">Delete project on server</string>
<string name="menu_save_project">Save project</string> <string name="menu_save_project">Save project</string>
<string name="fab_add_member">Add a member</string> <string name="fab_add_member">Add member</string>
<string name="fab_rm_project">Remove Project</string> <string name="fab_rm_project">Remove</string>
<string name="remove_project_confirmation">Project %1$s has been removed</string> <string name="remove_project_confirmation">Project %1$s has been removed</string>
<string name="choose_project_management_action">Manage project</string> <string name="choose_project_management_action">Project</string>
<string name="fab_manage_members">Manage members</string> <string name="fab_manage_members">Members</string>
<string name="fab_export_project">Export project</string> <string name="fab_manage_labels">Labels</string>
<string name="action_archive">Archive</string>
<string name="action_unarchive">Unarchive</string>
<string name="fab_export_project">Export</string>
<string name="drawer_last_sync_text">Last sync: %1$02d:%2$02d</string> <string name="drawer_last_sync_text">Last sync: %1$02d:%2$02d</string>
<string name="scan_qrcode">Scan QR code</string> <string name="scan_qrcode">Scan QR code</string>
<string name="new_project_from_nextcloud_tooltip">Choose from Nextcloud account</string> <string name="new_project_from_nextcloud_tooltip">Choose from Nextcloud account</string>
@@ -147,7 +150,7 @@
<string name="stats_paid">Paid</string> <string name="stats_paid">Paid</string>
<string name="stats_spent">Spent</string> <string name="stats_spent">Spent</string>
<string name="stats_balance">Balance</string> <string name="stats_balance">Balance</string>
<string name="fab_statistics">Statistics</string> <string name="fab_statistics">Stats</string>
<string name="fab_settle">Settle</string> <string name="fab_settle">Settle</string>
<string name="project_add_success_message">Project %1$s was successfully added</string> <string name="project_add_success_message">Project %1$s was successfully added</string>
<string name="project_create_success_message">Project %1$s was successfully created</string> <string name="project_create_success_message">Project %1$s was successfully created</string>
@@ -240,7 +243,7 @@
<!-- QR Code Scanner --> <!-- QR Code Scanner -->
<string name="error_scanning_bill_qr_code">QR code could not be scanned.</string> <string name="error_scanning_bill_qr_code">QR code could not be scanned.</string>
<string name="fab_manage_currencies">Manage currencies</string> <string name="fab_manage_currencies">Currencies</string>
<string name="setting_show_archived">Show archived projects</string> <string name="setting_show_archived">Show archived projects</string>