Skip to content

Commit

Permalink
Added a way to remove archived group. Closes #133
Browse files Browse the repository at this point in the history
  • Loading branch information
Dima-Android committed Jun 27, 2024
1 parent 3abcc2c commit 4ded7bd
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.zotero.android.architecture.ui.CustomLayoutSize
import org.zotero.android.screens.dashboard.ChangedItemsDeletedAlert
Expand All @@ -18,8 +19,11 @@ import org.zotero.android.screens.dashboard.CrashLoggingDialogs
import org.zotero.android.screens.dashboard.DashboardViewModel
import org.zotero.android.screens.dashboard.DashboardViewState
import org.zotero.android.screens.dashboard.DebugLoggingDialogs
import org.zotero.android.uicomponents.Strings
import org.zotero.android.uicomponents.bottomsheet.LongPressBottomSheet
import org.zotero.android.uicomponents.foundation.safeClickable
import org.zotero.android.uicomponents.modal.CustomAlertDialog
import org.zotero.android.uicomponents.theme.CustomPalette
import org.zotero.android.uicomponents.theme.CustomTheme

@Composable
Expand Down Expand Up @@ -71,6 +75,26 @@ fun BoxScope.DashboardTopLevelDialogs(
)
}

val deleteGroupDialogData = viewState.deleteGroupDialogData
if (deleteGroupDialogData != null) {
CustomAlertDialog(
title = stringResource(id = Strings.delete),
description = stringResource(
id = Strings.libraries_delete_question, deleteGroupDialogData.name
),
primaryAction = CustomAlertDialog.ActionConfig(
text = stringResource(id = Strings.no),
),
secondaryAction = CustomAlertDialog.ActionConfig(
text = stringResource(id = Strings.yes),
textColor = CustomPalette.ErrorRed,
onClick = { viewModel.deleteNonLocalGroup(deleteGroupDialogData.id) }
),
dismissOnClickOutside = false,
onDismiss = viewModel::onDismissDeleteGroupDialog
)
}

LongPressBottomSheet(
layoutType = layoutType,
longPressOptionsHolder = viewState.longPressOptionsHolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.zotero.android.architecture.BaseViewModel2
import org.zotero.android.architecture.ScreenArguments
import org.zotero.android.architecture.ViewEffect
import org.zotero.android.architecture.ViewState
import org.zotero.android.architecture.ifFailure
import org.zotero.android.architecture.logging.crash.CrashReportIdDialogData
import org.zotero.android.architecture.logging.crash.CrashShareDataEventStream
import org.zotero.android.architecture.logging.debug.DebugLogging
Expand All @@ -23,6 +24,7 @@ import org.zotero.android.architecture.logging.debug.DebugLoggingDialogDataEvent
import org.zotero.android.architecture.logging.debug.DebugLoggingInterface
import org.zotero.android.database.DbWrapper
import org.zotero.android.database.objects.RCustomLibraryType
import org.zotero.android.database.requests.DeleteGroupDbRequest
import org.zotero.android.database.requests.ReadCollectionDbRequest
import org.zotero.android.database.requests.ReadLibraryDbRequest
import org.zotero.android.database.requests.ReadSearchDbRequest
Expand All @@ -31,6 +33,7 @@ import org.zotero.android.screens.allitems.data.AllItemsArgs
import org.zotero.android.screens.allitems.data.InitialLoadData
import org.zotero.android.screens.collections.data.CollectionsArgs
import org.zotero.android.screens.dashboard.data.ShowDashboardLongPressBottomSheet
import org.zotero.android.screens.libraries.data.DeleteGroupDialogData
import org.zotero.android.sync.Collection
import org.zotero.android.sync.CollectionIdentifier
import org.zotero.android.sync.Library
Expand Down Expand Up @@ -60,6 +63,13 @@ class DashboardViewModel @Inject constructor(
) : BaseViewModel2<DashboardViewState, DashboardViewEffect>(DashboardViewState()),
DebugLoggingInterface {

@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(deleteGroupDialogData: DeleteGroupDialogData) {
updateState {
copy(deleteGroupDialogData = deleteGroupDialogData)
}
}

@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event: AskUserToResolveChangedDeletedItem) {
updateState {
Expand Down Expand Up @@ -291,6 +301,14 @@ class DashboardViewModel @Inject constructor(
}
}

fun onDismissDeleteGroupDialog() {
updateState {
copy(
deleteGroupDialogData = null,
)
}
}

fun dismissBottomSheet() {
updateState {
copy(longPressOptionsHolder = null)
Expand Down Expand Up @@ -336,13 +354,25 @@ class DashboardViewModel @Inject constructor(
debugLogging.stop()
}

fun deleteNonLocalGroup(groupId: Int) {
viewModelScope.launch {
perform(
dbWrapper = dbWrapper,
DeleteGroupDbRequest(groupId = groupId)
).ifFailure {
Timber.e(it, "DashboardViewModel: can't delete group")
return@launch
}
}
}
}

data class DashboardViewState(
val snackbarMessage: SnackbarMessage? = null,
val conflictDialog: ConflictDialogData? = null,
val debugLoggingDialogData: DebugLoggingDialogData? = null,
val crashReportIdDialogData: CrashReportIdDialogData? = null,
val deleteGroupDialogData: DeleteGroupDialogData? = null,
val changedItemsDeletedAlertQueue: List<ConflictDialogData.changedItemsDeletedAlert> = emptyList(),
val longPressOptionsHolder: LongPressOptionsHolder? = null,
val showDebugWindow: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.zotero.android.screens.libraries

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import androidx.compose.ui.window.PopupProperties
import org.zotero.android.uicomponents.CustomScaffold
import org.zotero.android.uicomponents.Drawables
import org.zotero.android.uicomponents.Strings
import org.zotero.android.uicomponents.theme.CustomTheme

@Composable
internal fun DeleteGroupPopup(
onDeleteGroup: () -> Unit,
dismissDeleteGroupPopup: () -> Unit,
) {
val backgroundColor = CustomTheme.colors.topBarBackgroundColor
Popup(
properties = PopupProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
),
onDismissRequest = dismissDeleteGroupPopup,
popupPositionProvider = createDeleteGroupPopupPositionProvider(),
) {
CustomScaffold(
modifier = Modifier
.width(250.dp)
.height(48.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(16.dp),
),
backgroundColor = backgroundColor,
) {
Row(
modifier = Modifier
.fillMaxHeight()
.background(color = CustomTheme.colors.cardBackground)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = true),
onClick = { onDeleteGroup() }
),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.width(16.dp))
Text(
modifier = Modifier.weight(1f),
text = stringResource(id = Strings.remove),
maxLines = 1,
style = CustomTheme.typography.newBody,
)
Icon(
painter = painterResource(id = Drawables.delete_24px),
contentDescription = null,
tint = CustomTheme.colors.error
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
}

@Composable
private fun createDeleteGroupPopupPositionProvider() = object : PopupPositionProvider {
val localDensity = LocalDensity.current
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
val extraXOffset = with(localDensity) {
14.dp.toPx()
}.toInt()
val extraYOffset = with(localDensity) {
6.dp.toPx()
}.toInt()

val xOffset = anchorBounds.left + extraXOffset
val yOffset = anchorBounds.bottom + extraYOffset

return IntOffset(
x = xOffset,
y = yOffset
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ internal fun LibrariesTable(
LibrariesItem(
item = item,
isLastItem = index == viewState.customLibraries.size - 1,
onItemTapped = { viewModel.onCustomLibraryTapped(index) }
onItemTapped = { viewModel.onCustomLibraryTapped(index) },
onItemLongTapped = {
//no-op
}
)
}
item {
Expand All @@ -71,11 +74,21 @@ internal fun LibrariesTable(
NewDivider()
}
itemsIndexed(viewState.groupLibraries) { index, item ->
LibrariesItem(
item = item,
isLastItem = index == viewState.groupLibraries.size - 1,
onItemTapped = { viewModel.onGroupLibraryTapped(index) }
)
Box {
if (viewState.groupIdForDeletePopup == item.id) {
DeleteGroupPopup(
onDeleteGroup = { viewModel.showDeleteGroupQuestion(item.id, item.name) },
dismissDeleteGroupPopup = { viewModel.dismissDeleteGroupPopup() },
)
}
LibrariesItem(
item = item,
isLastItem = index == viewState.groupLibraries.size - 1,
onItemTapped = { viewModel.onGroupLibraryTapped(index) },
onItemLongTapped = { viewModel.showDeleteGroupPopup(item) }
)
}

}
item {
NewDivider()
Expand All @@ -88,7 +101,8 @@ internal fun LibrariesTable(
private fun LibrariesItem(
item: LibraryRowData,
isLastItem: Boolean,
onItemTapped: () -> Unit
onItemTapped: () -> Unit,
onItemLongTapped: () -> Unit,
) {
Box {
Row(
Expand All @@ -100,7 +114,8 @@ private fun LibrariesItem(
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = { onItemTapped() },
onClick = onItemTapped,
onLongClick = onItemLongTapped,
)
) {
Spacer(modifier = Modifier.width(16.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmResults
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.zotero.android.architecture.BaseViewModel2
import org.zotero.android.architecture.LCE2
import org.zotero.android.architecture.ScreenArguments
Expand All @@ -18,6 +19,7 @@ import org.zotero.android.database.requests.ReadAllCustomLibrariesDbRequest
import org.zotero.android.database.requests.ReadAllGroupsDbRequest
import org.zotero.android.files.FileStore
import org.zotero.android.screens.collections.data.CollectionsArgs
import org.zotero.android.screens.libraries.data.DeleteGroupDialogData
import org.zotero.android.screens.libraries.data.LibraryRowData
import org.zotero.android.screens.libraries.data.LibraryState
import org.zotero.android.sync.CollectionIdentifier
Expand Down Expand Up @@ -56,8 +58,8 @@ internal class LibrariesViewModel @Inject constructor(

OrderedCollectionChangeSet.State.UPDATE -> {
val deletions = changeSet.deletions
if (!deletions.isEmpty()) {
//TODO process group deletion
if (deletions.isNotEmpty()) {
showDefaultLibraryIfNeeded()
}
generateLibraryRows()
}
Expand All @@ -76,6 +78,10 @@ internal class LibrariesViewModel @Inject constructor(
}
}

private fun showDefaultLibraryIfNeeded() {
showCollections(LibraryIdentifier.custom(RCustomLibraryType.myLibrary))
}

private fun generateLibraryRows() {
updateState {
copy(
Expand All @@ -92,6 +98,7 @@ internal class LibrariesViewModel @Inject constructor(

private fun createCustomLibraryRowData(library: RCustomLibrary): LibraryRowData {
return LibraryRowData(
id = -1,
name = RCustomLibraryType.valueOf(library.type).libraryName,
state = LibraryState.normal
)
Expand All @@ -105,7 +112,7 @@ internal class LibrariesViewModel @Inject constructor(
} else {
state = LibraryState.normal
}
return LibraryRowData(name = library.name, state = state)
return LibraryRowData(id = library.identifier ,name = library.name, state = state)
}

private fun libraryForCustomLibrary(index: Int): Library? {
Expand Down Expand Up @@ -163,11 +170,37 @@ internal class LibrariesViewModel @Inject constructor(

}

fun showDeleteGroupQuestion(id: Int, name: String) {
dismissDeleteGroupPopup()
EventBus.getDefault().post(DeleteGroupDialogData(id = id, name = name))
}

fun dismissDeleteGroupPopup() {
updateState {
copy(
groupIdForDeletePopup = null,
)
}
}

fun showDeleteGroupPopup(item: LibraryRowData) {
val group = this.groupLibraries!!.find { it.identifier == item.id } ?: return
if (!group.isLocalOnly) {
return
}
updateState {
copy(
groupIdForDeletePopup = group.identifier,
)
}
}

}

internal data class LibrariesViewState(
val str: String = "",
val lce: LCE2 = LCE2.Loading,
val groupIdForDeletePopup: Int? = null,
val customLibraries: List<LibraryRowData> = emptyList(),
val groupLibraries: List<LibraryRowData> = emptyList(),
) : ViewState {
Expand Down
Loading

0 comments on commit 4ded7bd

Please sign in to comment.