restore stateFlow
This commit is contained in:
@ -8,6 +8,8 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@ -26,16 +28,16 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Unarchive
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material.rememberDismissState
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@ -52,6 +54,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@ -70,6 +73,7 @@ import app.omnivore.omnivore.feature.save.SaveState
|
||||
import app.omnivore.omnivore.feature.save.SaveViewModel
|
||||
import app.omnivore.omnivore.feature.savedItemViews.SavedItemCard
|
||||
import app.omnivore.omnivore.navigation.Routes
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -141,8 +145,7 @@ internal fun LibraryView(
|
||||
is LibraryUiState.Success -> {
|
||||
LibraryViewContent(
|
||||
viewModel,
|
||||
modifier = Modifier
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
paddingValues = paddingValues,
|
||||
uiState = uiState
|
||||
)
|
||||
}
|
||||
@ -293,152 +296,155 @@ fun EditBottomSheet(
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LibraryViewContent(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
modifier: Modifier,
|
||||
paddingValues: PaddingValues,
|
||||
uiState: LibraryUiState
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = libraryViewModel.isRefreshing,
|
||||
onRefresh = { libraryViewModel.refresh() }
|
||||
)
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
if (pullToRefreshState.isRefreshing) {
|
||||
LaunchedEffect(true) {
|
||||
// fetch something
|
||||
delay(1500)
|
||||
libraryViewModel.refresh()
|
||||
pullToRefreshState.endRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by libraryViewModel.actionsMenuItemLiveData.observeAsState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.fillMaxSize()
|
||||
.pullRefresh(pullRefreshState)
|
||||
.nestedScroll(pullToRefreshState.nestedScrollConnection)
|
||||
) {
|
||||
Column {
|
||||
LibraryFilterBar()
|
||||
HorizontalDivider()
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
items(
|
||||
items = (uiState as LibraryUiState.Success).items,
|
||||
key = { item -> item.savedItem.savedItemId }
|
||||
) { cardDataWithLabels ->
|
||||
val swipeThreshold = 0.45f
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
LibraryFilterBar(libraryViewModel)
|
||||
}
|
||||
items(
|
||||
items = (uiState as LibraryUiState.Success).items,
|
||||
key = { item -> item.savedItem.savedItemId }
|
||||
) { cardDataWithLabels ->
|
||||
val swipeThreshold = 0.45f
|
||||
|
||||
val currentThresholdFraction = remember { mutableStateOf(0f) }
|
||||
val currentItem by rememberUpdatedState(cardDataWithLabels.savedItem)
|
||||
val swipeState = rememberDismissState(
|
||||
confirmStateChange = {
|
||||
when(it) {
|
||||
DismissValue.Default -> {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
DismissValue.DismissedToEnd -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
val currentThresholdFraction = remember { mutableStateOf(0f) }
|
||||
val currentItem by rememberUpdatedState(cardDataWithLabels.savedItem)
|
||||
val swipeState = rememberDismissState(
|
||||
confirmStateChange = {
|
||||
when(it) {
|
||||
DismissValue.Default -> {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
}
|
||||
DismissValue.DismissedToStart -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
DismissValue.DismissedToEnd -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
}
|
||||
DismissValue.DismissedToStart -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving.
|
||||
if (currentItem.isArchived) {
|
||||
libraryViewModel.unarchiveSavedItem(currentItem.savedItemId)
|
||||
} else {
|
||||
libraryViewModel.archiveSavedItem(currentItem.savedItemId)
|
||||
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)
|
||||
}
|
||||
} else if (it == DismissValue.DismissedToStart) { // Deleting.
|
||||
libraryViewModel.deleteSavedItem(currentItem.savedItemId)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
)
|
||||
SwipeToDismiss(
|
||||
state = swipeState,
|
||||
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
|
||||
true
|
||||
}
|
||||
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)
|
||||
)
|
||||
SwipeToDismiss(
|
||||
state = swipeState,
|
||||
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"
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissContent = {
|
||||
val selected =
|
||||
currentItem.savedItemId == selectedItem?.savedItem?.savedItemId
|
||||
val savedItem = SavedItemWithLabelsAndHighlights(
|
||||
savedItem = cardDataWithLabels.savedItem,
|
||||
labels = cardDataWithLabels.labels,
|
||||
highlights = cardDataWithLabels.highlights
|
||||
)
|
||||
SavedItemCard(
|
||||
selected = selected,
|
||||
savedItemViewModel = libraryViewModel,
|
||||
savedItem = savedItem,
|
||||
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
|
||||
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)
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
when {
|
||||
swipeState.isDismissed(DismissDirection.EndToStart) -> Reset(state = swipeState)
|
||||
swipeState.isDismissed(DismissDirection.StartToEnd) -> Reset(state = swipeState)
|
||||
},
|
||||
dismissContent = {
|
||||
val selected =
|
||||
currentItem.savedItemId == selectedItem?.savedItem?.savedItemId
|
||||
val savedItem = SavedItemWithLabelsAndHighlights(
|
||||
savedItem = cardDataWithLabels.savedItem,
|
||||
labels = cardDataWithLabels.labels,
|
||||
highlights = cardDataWithLabels.highlights
|
||||
)
|
||||
SavedItemCard(
|
||||
selected = selected,
|
||||
savedItemViewModel = libraryViewModel,
|
||||
savedItem = savedItem,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -453,10 +459,9 @@ fun LibraryViewContent(
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = libraryViewModel.isRefreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
PullToRefreshContainer(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
state = pullToRefreshState,
|
||||
)
|
||||
|
||||
// LabelsSelectionSheet(viewModel = libraryViewModel)
|
||||
|
||||
@ -32,16 +32,22 @@ import com.apollographql.apollo3.api.Optional
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
class LibraryViewModel @Inject constructor(
|
||||
private val networker: Networker,
|
||||
@ -67,8 +73,7 @@ class LibraryViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
|
||||
// Correct way - but not working
|
||||
/* val uiState: StateFlow<LibraryUiState> = _libraryQuery.flatMapLatest { query ->
|
||||
val uiState: StateFlow<LibraryUiState> = _libraryQuery.flatMapLatest { query ->
|
||||
libraryRepository.getSavedItems(query)
|
||||
}
|
||||
.map(LibraryUiState::Success)
|
||||
@ -76,24 +81,8 @@ class LibraryViewModel @Inject constructor(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = LibraryUiState.Loading
|
||||
)*/
|
||||
)
|
||||
|
||||
// This approach needs to be replaced with the StateFlow above after fixing Room Flow
|
||||
private val _uiState = MutableStateFlow<LibraryUiState>(LibraryUiState.Loading)
|
||||
val uiState: StateFlow<LibraryUiState> = _uiState
|
||||
|
||||
init {
|
||||
loadSavedItems()
|
||||
}
|
||||
|
||||
private fun loadSavedItems() {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.getSavedItems(_libraryQuery.value)
|
||||
.collect { favoriteNews ->
|
||||
_uiState.value = LibraryUiState.Success(favoriteNews)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX)
|
||||
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
|
||||
@ -265,7 +254,6 @@ class LibraryViewModel @Inject constructor(
|
||||
excludedLabels = excludeLabels,
|
||||
allowedContentReaders = allowedContentReaders
|
||||
)
|
||||
loadSavedItems()
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,17 +333,13 @@ class LibraryViewModel @Inject constructor(
|
||||
|
||||
SavedItemAction.MarkRead -> {
|
||||
viewModelScope.launch {
|
||||
_uiState.value = LibraryUiState.Success(emptyList())
|
||||
libraryRepository.updateReadingProgress(itemID, 100.0, 0)
|
||||
loadSavedItems()
|
||||
}
|
||||
}
|
||||
|
||||
SavedItemAction.MarkUnread -> {
|
||||
viewModelScope.launch {
|
||||
_uiState.value = LibraryUiState.Success(emptyList())
|
||||
libraryRepository.updateReadingProgress(itemID, 0.0, 0)
|
||||
loadSavedItems()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user