From a57b1d5614ec60fbcbd2017c61ff5bf610fabda5 Mon Sep 17 00:00:00 2001 From: tom5079 Date: Sat, 18 Dec 2021 13:32:07 +0900 Subject: [PATCH] [WIP] Pageturn --- .idea/deploymentTargetDropDown.xml | 17 ---- .../java/xyz/quaver/pupil/ui/MainActivity.kt | 98 +++++++++++++++---- .../pupil/ui/viewmodel/MainViewModel.kt | 12 ++- 3 files changed, 87 insertions(+), 40 deletions(-) delete mode 100644 .idea/deploymentTargetDropDown.xml diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml deleted file mode 100644 index 1342638b..00000000 --- a/.idea/deploymentTargetDropDown.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 26254e90..a16f26ab 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -44,11 +44,13 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.pointer.* import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastFirstOrNull import com.google.accompanist.drawablepainter.rememberDrawablePainter import kotlinx.coroutines.* import org.kodein.di.DIAware @@ -58,14 +60,10 @@ import org.kodein.log.newLogger import xyz.quaver.pupil.* import xyz.quaver.pupil.R import xyz.quaver.pupil.sources.SearchResultEvent -import xyz.quaver.pupil.ui.composable.FloatingActionButtonState -import xyz.quaver.pupil.ui.composable.FloatingSearchBar -import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton -import xyz.quaver.pupil.ui.composable.SubFabItem +import xyz.quaver.pupil.ui.composable.* +import xyz.quaver.pupil.ui.dialog.OpenWithItemIDDialog import xyz.quaver.pupil.ui.dialog.SourceSelectDialog import xyz.quaver.pupil.ui.theme.PupilTheme -import xyz.quaver.pupil.ui.composable.ProgressCard -import xyz.quaver.pupil.ui.dialog.OpenWithItemIDDialog import xyz.quaver.pupil.ui.viewmodel.MainViewModel import xyz.quaver.pupil.util.* import kotlin.math.* @@ -90,6 +88,20 @@ class MainActivity : ComponentActivity(), DIAware { PupilTheme { val focusManager = LocalFocusManager.current + val maxPage by model.maxPage.collectAsState(0) + + val pageTurnIndicatorHeight = LocalDensity.current.run { 64.dp.toPx() } + + val prevPageAvailable by derivedStateOf { + model.currentPage > 1 + } + + val nextPageAvailable by derivedStateOf { + model.currentPage <= maxPage + } + + var overscroll: Float? by remember { mutableStateOf(null) } + var isFabExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) } var isFabVisible by remember { mutableStateOf(true) } @@ -185,24 +197,40 @@ class MainActivity : ComponentActivity(), DIAware { } ) { Box(Modifier.fillMaxSize()) { - LazyColumn( Modifier .fillMaxSize() + .offset(0.dp, overscroll?.let { overscroll -> LocalDensity.current.run { overscroll.toDp() } } ?: 0.dp) .nestedScroll(object : NestedScrollConnection { override fun onPreScroll( available: Offset, source: NestedScrollSource ): Offset { - searchBarOffset = - (searchBarOffset + available.y.roundToInt()).coerceIn( - -searchBarHeight, - 0 - ) + val overscrollSnapshot = overscroll - isFabVisible = available.y > 0f + if (overscrollSnapshot == null || overscrollSnapshot == 0f) { + searchBarOffset = + (searchBarOffset + available.y.roundToInt()).coerceIn( + -searchBarHeight, + 0 + ) - return Offset.Zero + isFabVisible = available.y > 0f + + return Offset.Zero + } else { + val newOverscroll = + if (overscrollSnapshot > 0f && available.y < 0f) + max(overscrollSnapshot + available.y, 0f) + else if (overscrollSnapshot < 0f && available.y > 0f) + min(overscrollSnapshot + available.y, 0f) + else + overscrollSnapshot + + return Offset(0f, newOverscroll - overscrollSnapshot).also { + overscroll = newOverscroll + } + } } override fun onPostScroll( @@ -210,11 +238,41 @@ class MainActivity : ComponentActivity(), DIAware { available: Offset, source: NestedScrollSource ): Offset { + if (available.y == 0f || source == NestedScrollSource.Fling) return Offset.Zero + return overscroll?.let { + val newOverscroll = (it + available.y).coerceIn(-pageTurnIndicatorHeight, pageTurnIndicatorHeight) - return super.onPostScroll(consumed, available, source) + Offset(0f, newOverscroll - it).also { + overscroll = newOverscroll + } + } ?: Offset.Zero } - }), + }).pointerInput(Unit) { + forEachGesture { + awaitPointerEventScope { + val down = awaitFirstDown(requireUnconsumed = false) + var pointer = down.id + overscroll = 0f + + while (true) { + val event = awaitPointerEvent() + val dragEvent = event.changes.fastFirstOrNull { it.id == pointer }!! + + if (dragEvent.changedToUpIgnoreConsumed()) { + val otherDown = event.changes.fastFirstOrNull { it.pressed } + if (otherDown == null) { + dragEvent.consumePositionChange() + overscroll = null + break + } + else + pointer = otherDown.id + } + } + } + } + }, contentPadding = PaddingValues(0.dp, 56.dp, 0.dp, 0.dp) ) { items(model.searchResults, key = { it.itemID }) { itemInfo -> @@ -251,9 +309,11 @@ class MainActivity : ComponentActivity(), DIAware { Image( painterResource(model.source.iconResID), contentDescription = null, - modifier = Modifier.size(24.dp).clickable { - sourceSelectDialog = true - } + modifier = Modifier + .size(24.dp) + .clickable { + sourceSelectDialog = true + } ) Icon( Icons.Default.Sort, diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt index dca337e6..2d9afb69 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt @@ -19,10 +19,7 @@ package xyz.quaver.pupil.ui.viewmodel import android.app.Application -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope @@ -40,6 +37,7 @@ import xyz.quaver.pupil.sources.History import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.util.source +import kotlin.math.ceil import kotlin.random.Random @Suppress("UNCHECKED_CAST") @@ -78,6 +76,12 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { var totalItems by mutableStateOf(0) private set + val maxPage by derivedStateOf { + resultsPerPage.map { + ceil(totalItems / it.toDouble()).toInt() + } + } + fun setSourceAndReset(sourceName: String) { source = sourceFactory(sourceName) sortModeIndex = 0