Skip to content

Commit

Permalink
Merge pull request #3266 from remychantenay/android-library-swipe-act…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
jacksonh authored Dec 22, 2023
2 parents e9f0725 + bf7c79d commit bc99ea1
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package app.omnivore.omnivore.ui.library

import android.content.Intent
import android.util.Log
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.FloatTweenSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
Expand All @@ -10,16 +14,27 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissState
import androidx.compose.material.FractionalThreshold
import androidx.compose.material.DismissValue
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Unarchive
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
Expand All @@ -28,9 +43,9 @@ import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.ui.components.AddLinkSheetContent
import app.omnivore.omnivore.ui.editinfo.EditInfoSheetContent
import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent
import app.omnivore.omnivore.ui.components.LabelsViewModel
import app.omnivore.omnivore.ui.editinfo.EditInfoSheetContent
import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel
import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
Expand All @@ -41,7 +56,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch


@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun LibraryView(
libraryViewModel: LibraryViewModel,
Expand Down Expand Up @@ -110,7 +125,8 @@ fun LibraryView(
fun BottomSheetContent(libraryViewModel: LibraryViewModel,
labelsViewModel: LabelsViewModel,
saveViewModel: SaveViewModel,
editInfoViewModel: EditInfoViewModel) {
editInfoViewModel: EditInfoViewModel
) {
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false)
val showEditInfoSheet: Boolean by libraryViewModel.showEditInfoSheetLiveData.observeAsState(false)
Expand Down Expand Up @@ -231,27 +247,101 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
item {
LibraryFilterBar(libraryViewModel)
}
items(cardsData) { cardDataWithLabels ->
val selected = cardDataWithLabels.savedItem.savedItemId == selectedItem?.savedItem?.savedItemId
SavedItemCard(
selected = selected,
savedItemViewModel = libraryViewModel,
savedItem = cardDataWithLabels,
onClickHandler = {
libraryViewModel.actionsMenuItemLiveData.postValue(null)
val activityClass =
if (cardDataWithLabels.savedItem.contentReader == "PDF") PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java
val intent = Intent(context, activityClass)
intent.putExtra("SAVED_ITEM_SLUG", cardDataWithLabels.savedItem.slug)
context.startActivity(intent)
items(
items = cardsData,
key = { item -> item.savedItem.savedItemId }
) { cardDataWithLabels ->
val swipeThreshold = 0.40f

val currentThresholdFraction = remember { mutableStateOf(0f) }
val currentItem by rememberUpdatedState(cardDataWithLabels.savedItem)
val swipeState = rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToEnd ||
currentThresholdFraction.value < swipeThreshold ||
currentThresholdFraction.value > 1.0f) {
false
}

if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving.
if (currentItem.isArchived) {
libraryViewModel.unarchiveSavedItem(currentItem.savedItemId)
} else {
libraryViewModel.archiveSavedItem(currentItem.savedItemId)
}
} else if (it == DismissValue.DismissedToStart) { // Deleting.
libraryViewModel.deleteSavedItem(currentItem.savedItemId)
}

true
}
)
SwipeToDismiss(
state = swipeState,
modifier = Modifier.padding(vertical = 4.dp),
directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart),
dismissThresholds = { FractionalThreshold(swipeThreshold) },
background = {
val direction = swipeState.dismissDirection ?: return@SwipeToDismiss
val color by animateColorAsState(
when (swipeState.targetValue) {
DismissValue.Default -> Color.LightGray
DismissValue.DismissedToEnd -> Color.Green
DismissValue.DismissedToStart -> Color.Red
}, label = "backgroundColor"
)
val alignment = when (direction) {
DismissDirection.StartToEnd -> Alignment.CenterStart
DismissDirection.EndToStart -> Alignment.CenterEnd
}
val icon = when (direction) {
DismissDirection.StartToEnd -> if (currentItem.isArchived) Icons.Default.Unarchive else Icons.Default.Archive
DismissDirection.EndToStart -> Icons.Default.Delete
}
val scale by animateFloatAsState(
if (swipeState.targetValue == DismissValue.Default) 0.75f else 1f,
label = "scaleAnimation"
)

Box(
Modifier.fillMaxSize().background(color).padding(horizontal = 20.dp),
contentAlignment = alignment
) {
currentThresholdFraction.value = swipeState.progress.fraction
Icon(
icon,
contentDescription = null,
modifier = Modifier.scale(scale)
)
}
},
actionHandler = {
libraryViewModel.handleSavedItemAction(
cardDataWithLabels.savedItem.savedItemId,
it
dismissContent = {
val selected = currentItem.savedItemId == selectedItem?.savedItem?.savedItemId
SavedItemCard(
selected = selected,
savedItemViewModel = libraryViewModel,
savedItem = cardDataWithLabels,
onClickHandler = {
libraryViewModel.actionsMenuItemLiveData.postValue(null)
val activityClass =
if (currentItem.contentReader == "PDF") PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java
val intent = Intent(context, activityClass)
intent.putExtra("SAVED_ITEM_SLUG", currentItem.slug)
context.startActivity(intent)
},
actionHandler = {
libraryViewModel.handleSavedItemAction(
currentItem.savedItemId,
it
)
}
)
}
},
)
when {
swipeState.isDismissed(DismissDirection.EndToStart) -> Reset(state = swipeState)
swipeState.isDismissed(DismissDirection.StartToEnd) -> Reset(state = swipeState)
}
}
}

Expand All @@ -275,6 +365,18 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
}
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun Reset(state: DismissState) {
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = state.dismissDirection) {
scope.launch {
state.reset()
state.animateTo(DismissValue.Default, FloatTweenSpec(duration= 0, delay = 0))
}
}
}

@Composable
private fun BottomSheetUI(content: @Composable () -> Unit) {
Box(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,19 +282,13 @@ class LibraryViewModel @Inject constructor(
override fun handleSavedItemAction(itemID: String, action: SavedItemAction) {
when (action) {
SavedItemAction.Delete -> {
viewModelScope.launch {
dataService.deleteSavedItem(itemID)
}
deleteSavedItem(itemID)
}
SavedItemAction.Archive -> {
viewModelScope.launch {
dataService.archiveSavedItem(itemID)
}
archiveSavedItem(itemID)
}
SavedItemAction.Unarchive -> {
viewModelScope.launch {
dataService.unarchiveSavedItem(itemID)
}
unarchiveSavedItem(itemID)
}
SavedItemAction.EditLabels -> {
currentItemLiveData.value = itemID
Expand All @@ -311,6 +305,24 @@ class LibraryViewModel @Inject constructor(
actionsMenuItemLiveData.postValue(null)
}

fun deleteSavedItem(itemID: String) {
viewModelScope.launch {
dataService.deleteSavedItem(itemID)
}
}

fun archiveSavedItem(itemID: String) {
viewModelScope.launch {
dataService.archiveSavedItem(itemID)
}
}

fun unarchiveSavedItem(itemID: String) {
viewModelScope.launch {
dataService.unarchiveSavedItem(itemID)
}
}

fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
Expand Down

0 comments on commit bc99ea1

Please sign in to comment.