diff --git a/app/src/main/java/net/helcel/cowspent/android/main/BillsListScreen.kt b/app/src/main/java/net/helcel/cowspent/android/main/BillsListScreen.kt index 9180582..6c962fc 100644 --- a/app/src/main/java/net/helcel/cowspent/android/main/BillsListScreen.kt +++ b/app/src/main/java/net/helcel/cowspent/android/main/BillsListScreen.kt @@ -132,6 +132,10 @@ fun BillsListScreen( onProjectAction(projectOptionsProjectId, 1) viewModel.showProjectOptionsDialogByProjectId = null }, + onForgetProject = { + onProjectAction(projectOptionsProjectId, 9) + viewModel.showProjectOptionsDialogByProjectId = null + }, onManageMembers = { onProjectAction(projectOptionsProjectId, 2) viewModel.showProjectOptionsDialogByProjectId = null @@ -408,11 +412,9 @@ fun BillsListScreen( .pullRefresh(pullRefreshState)) { when { viewModel.showNoProjects -> EmptyProjectsState(onAccountSwitcherClick, onAddProjectClick) + (viewModel.isRefreshing || viewModel.isLoadingBills) && viewModel.bills.isEmpty() -> + LoadingBillsState() 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.bills.isEmpty() -> EmptyState() else -> { diff --git a/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewActivity.kt b/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewActivity.kt index e624507..c6408af 100644 --- a/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewActivity.kt +++ b/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewActivity.kt @@ -270,6 +270,7 @@ class BillsListViewActivity : 8 -> { startActivity(net.helcel.cowspent.android.label.LabelManagementActivity.createIntent(this@BillsListViewActivity, pid)) } + 9 -> onRemoveProjectClick(pid) } }, onAccountSwitcherClick = { @@ -377,6 +378,8 @@ class BillsListViewActivity : fun onProjectClick(projectId: Long) { if (viewModel.selectedProjectId != projectId) { viewModel.selectedMemberId = null + viewModel.bills = emptyList() + viewModel.isLoadingBills = true } setSelectedProject(projectId) navigationSelection = Category(null, null) @@ -410,6 +413,9 @@ class BillsListViewActivity : lifecycleScope.launch { withContext(Dispatchers.IO) { 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) .edit { remove(lastProjectSyncKey(projectId)) } val dbProjects = db.projects @@ -621,76 +627,80 @@ class BillsListViewActivity : val selectedProjectId = PreferenceManager.getDefaultSharedPreferences(applicationContext).getLong("selected_project", 0) lifecycleScope.launch { - val (projId, projName) = withContext(Dispatchers.IO) { - if (selectedProjectId != 0L) { - db.getProject(selectedProjectId)?.let { - it.id to (if (it.name == "null" || it.name.isEmpty()) it.remoteId else it.name) - } ?: (0L to "") - } else { - 0L to "" + try { + val (projId, projName) = withContext(Dispatchers.IO) { + if (selectedProjectId != 0L) { + db.getProject(selectedProjectId)?.let { + it.id to (if (it.name == "null" || it.name.isEmpty()) it.remoteId else it.name) + } ?: (0L to "") + } else { + 0L to "" + } } - } - val title = if (selectedProjectId != 0L) projName else getString(R.string.app_name) + val title = if (selectedProjectId != 0L) projName else getString(R.string.app_name) - setSelectedProject(selectedProjectId) - viewModel.title = title - val query = viewModel.searchQuery.ifEmpty { null } + 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 = if (projId != 0L) { - db.searchBills(query, projId) - } else { - ArrayList() + val (ljItems, memberCount) = withContext(Dispatchers.IO) { + val db = CowspentSQLiteOpenHelper.getInstance(applicationContext) + val billList: List = 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 } - 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.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 + 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 } } } diff --git a/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewModel.kt b/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewModel.kt index caafc3f..13c7e0d 100644 --- a/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewModel.kt +++ b/app/src/main/java/net/helcel/cowspent/android/main/BillsListViewModel.kt @@ -20,6 +20,7 @@ class BillsListViewModel : ViewModel() { var selectedMemberId by mutableStateOf(null) var bills by mutableStateOf>(emptyList()) var isRefreshing by mutableStateOf(false) + var isLoadingBills by mutableStateOf(false) var searchQuery by mutableStateOf("") var title by mutableStateOf("") var accountName by mutableStateOf("") diff --git a/app/src/main/java/net/helcel/cowspent/android/project/ProjectOptionsDialog.kt b/app/src/main/java/net/helcel/cowspent/android/project/ProjectOptionsDialog.kt index 96d86d1..8b86a0c 100644 --- a/app/src/main/java/net/helcel/cowspent/android/project/ProjectOptionsDialog.kt +++ b/app/src/main/java/net/helcel/cowspent/android/project/ProjectOptionsDialog.kt @@ -24,6 +24,7 @@ import net.helcel.cowspent.model.ProjectType fun ProjectOptionsDialogContent( onEditProject: () -> Unit, onRemoveProject: () -> Unit, + onForgetProject: () -> Unit = {}, onManageMembers: () -> Unit, onManageCurrencies: () -> 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 archiveIcon = if (isArchived) Icons.Default.Unarchive else Icons.Default.Archive row1.add(ProjectOption(archiveLabel, archiveIcon, onRemoveProject)) + if (isArchived) { + row1.add( + ProjectOption( + stringResource(R.string.action_forget), + Icons.Default.Delete, + onForgetProject + ) + ) + } } else { row1.add(ProjectOption(stringResource(R.string.action_delete), Icons.Default.Delete, onRemoveProject)) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1512578..3fa089c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ Back Archive Unarchive + Delete locally Export Stats Settle