pref: 吧页面顶栏动画
This commit is contained in:
parent
8b358a080a
commit
2c7194cda7
|
|
@ -312,7 +312,7 @@ class ForumActivity : BaseActivity(), View.OnClickListener, OnRefreshedListener,
|
|||
)
|
||||
else
|
||||
ForumFragment.newInstance(forumName, false, getSortType()),
|
||||
getString(R.string.tab_forum_1)
|
||||
getString(R.string.tab_forum_latest)
|
||||
)
|
||||
addFragment(
|
||||
ForumFragment.newInstance(forumName, true, getSortType()),
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ import android.graphics.Typeface
|
|||
import android.net.Uri
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.rememberScrollableState
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -22,28 +20,33 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeContent
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.FractionalThreshold
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Tab
|
||||
import androidx.compose.material.TabRow
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Add
|
||||
|
|
@ -56,8 +59,11 @@ import androidx.compose.material.icons.rounded.VerticalAlignTop
|
|||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -71,16 +77,18 @@ import androidx.compose.ui.graphics.Color
|
|||
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.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import com.google.accompanist.placeholder.PlaceholderHighlight
|
||||
|
|
@ -99,7 +107,6 @@ import com.huanchengfly.tieba.post.dataStore
|
|||
import com.huanchengfly.tieba.post.getInt
|
||||
import com.huanchengfly.tieba.post.goToActivity
|
||||
import com.huanchengfly.tieba.post.models.database.History
|
||||
import com.huanchengfly.tieba.post.pxToDp
|
||||
import com.huanchengfly.tieba.post.toastShort
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
|
|
@ -118,12 +125,16 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.MenuScope
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.PagerTabIndicator
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ScrollableTabRow
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.SwipeableState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Toolbar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.picker.ListSinglePicker
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberSwipeableState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.swipeable
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
||||
import com.huanchengfly.tieba.post.utils.HistoryUtil
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
|
||||
|
|
@ -134,15 +145,19 @@ import com.ramcosta.composedestinations.annotation.DeepLink
|
|||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
private val LoadDistance = 70.dp
|
||||
|
||||
fun getSortType(
|
||||
context: Context,
|
||||
forumName: String
|
||||
forumName: String,
|
||||
): Int {
|
||||
val defaultSortType = context.appPreferences.defaultSortType?.toIntOrNull() ?: 0
|
||||
return context.dataStore.getInt("${forumName}_sort_type", defaultSortType)
|
||||
|
|
@ -151,7 +166,7 @@ fun getSortType(
|
|||
suspend fun setSortType(
|
||||
context: Context,
|
||||
forumName: String,
|
||||
sortType: Int
|
||||
sortType: Int,
|
||||
) {
|
||||
context.dataStore.edit {
|
||||
it[intPreferencesKey("${forumName}_sort_type")] = sortType
|
||||
|
|
@ -352,7 +367,7 @@ private suspend fun sendToDesktop(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Destination(
|
||||
deepLinks = [
|
||||
DeepLink(uriPattern = "tblite://forum/{forumName}")
|
||||
|
|
@ -437,36 +452,29 @@ fun ForumPage(
|
|||
|
||||
val account = LocalAccount.current
|
||||
val pagerState = rememberPagerState { 2 }
|
||||
|
||||
val currentPage by remember {
|
||||
derivedStateOf {
|
||||
pagerState.currentPage
|
||||
}
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val lazyListStates = persistentListOf(rememberLazyListState(), rememberLazyListState())
|
||||
|
||||
val density = LocalDensity.current
|
||||
|
||||
val playDistance = with(density) { 12.dp.toPx() }
|
||||
val isShowTopBarArea by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = ForumUiState::showForumHeader,
|
||||
initial = true
|
||||
var heightOffset by remember { mutableFloatStateOf(0f) }
|
||||
var headerHeight by remember {
|
||||
mutableFloatStateOf(
|
||||
with(density) {
|
||||
(Sizes.Large + 16.dp * 2).toPx()
|
||||
}
|
||||
)
|
||||
|
||||
val nestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
available: Offset,
|
||||
source: NestedScrollSource
|
||||
): Offset {
|
||||
lazyListStates.getOrNull(pagerState.currentPage)?.let { lazyListState ->
|
||||
if (available.y > 0 && lazyListState.firstVisibleItemIndex == 0) {
|
||||
// 一番上の要素が表示されたので表示
|
||||
viewModel.send(ForumUiIntent.ToggleShowHeader(true))
|
||||
} else {
|
||||
if (available.y.absoluteValue > playDistance && available.y < 0) {
|
||||
viewModel.send(ForumUiIntent.ToggleShowHeader(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
val isShowTopBarArea by remember {
|
||||
derivedStateOf {
|
||||
heightOffset.absoluteValue < headerHeight
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -524,12 +532,55 @@ fun ForumPage(
|
|||
LoadingPlaceholder(forumName)
|
||||
}
|
||||
) {
|
||||
|
||||
val loadDistance = with(LocalDensity.current) { LoadDistance.toPx() }
|
||||
var isFakeLoading by remember { mutableStateOf(false) }
|
||||
|
||||
val swipeableState = rememberSwipeableState(false) {
|
||||
if (it && !isFakeLoading) {
|
||||
coroutineScope.launch {
|
||||
emitGlobalEvent(
|
||||
ForumThreadListUiEvent.Refresh(
|
||||
currentPage == 1,
|
||||
getSortType(
|
||||
context,
|
||||
forumName
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
isFakeLoading = true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
val showTip by remember {
|
||||
derivedStateOf { swipeableState.offset.value > -loadDistance / 2 }
|
||||
}
|
||||
|
||||
LaunchedEffect(isFakeLoading) {
|
||||
if (isFakeLoading) {
|
||||
delay(1000)
|
||||
isFakeLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier) {
|
||||
MyScaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
backgroundColor = Color.Transparent,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(nestedScrollConnection),
|
||||
.nestedScroll(swipeableState.LoadPreDownPostUpNestedScrollConnection)
|
||||
.swipeable(
|
||||
state = swipeableState,
|
||||
anchors = mapOf(
|
||||
-loadDistance to false,
|
||||
loadDistance to true,
|
||||
),
|
||||
thresholds = { _, _ -> FractionalThreshold(0.75f) },
|
||||
orientation = Orientation.Vertical,
|
||||
),
|
||||
topBar = {
|
||||
ForumToolbar(
|
||||
forumName = forumName,
|
||||
|
|
@ -598,12 +649,12 @@ fun ForumPage(
|
|||
coroutineScope.launch {
|
||||
emitGlobalEventSuspend(
|
||||
ForumThreadListUiEvent.BackToTop(
|
||||
pagerState.currentPage == 1
|
||||
currentPage == 1
|
||||
)
|
||||
)
|
||||
emitGlobalEventSuspend(
|
||||
ForumThreadListUiEvent.Refresh(
|
||||
pagerState.currentPage == 1,
|
||||
currentPage == 1,
|
||||
getSortType(
|
||||
context,
|
||||
forumName
|
||||
|
|
@ -617,7 +668,7 @@ fun ForumPage(
|
|||
coroutineScope.launch {
|
||||
emitGlobalEvent(
|
||||
ForumThreadListUiEvent.BackToTop(
|
||||
pagerState.currentPage == 1
|
||||
currentPage == 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -644,22 +695,88 @@ fun ForumPage(
|
|||
}
|
||||
}
|
||||
) { contentPadding ->
|
||||
Column(modifier = Modifier.padding(contentPadding)) {
|
||||
AnimatedVisibility(
|
||||
visible = isShowTopBarArea,
|
||||
enter = expandVertically(
|
||||
expandFrom = Alignment.Top
|
||||
),
|
||||
exit = shrinkVertically()
|
||||
val headerNestedScrollConnection = remember {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset {
|
||||
if (available.y < 0) {
|
||||
val prevHeightOffset = heightOffset
|
||||
heightOffset = max(heightOffset + available.y, -headerHeight)
|
||||
if (prevHeightOffset != heightOffset) {
|
||||
return available.copy(x = 0f)
|
||||
}
|
||||
}
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset {
|
||||
if (available.y > 0f) {
|
||||
// Adjust the height offset in case the consumed delta Y is less than what was
|
||||
// recorded as available delta Y in the pre-scroll.
|
||||
val prevHeightOffset = heightOffset
|
||||
heightOffset = min(heightOffset + available.y, 0f)
|
||||
if (prevHeightOffset != heightOffset) {
|
||||
return available.copy(x = 0f)
|
||||
}
|
||||
}
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(contentPadding)
|
||||
.nestedScroll(headerNestedScrollConnection)
|
||||
) {
|
||||
forumInfo?.let {
|
||||
Column(
|
||||
modifier = Modifier.offset {
|
||||
IntOffset(
|
||||
x = 0,
|
||||
y = (swipeableState.offset.value + loadDistance).toInt()
|
||||
)
|
||||
}
|
||||
) {
|
||||
val containerHeight by remember {
|
||||
derivedStateOf {
|
||||
with(density) {
|
||||
(headerHeight + heightOffset).toDp()
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(containerHeight)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight(
|
||||
align = Alignment.Bottom,
|
||||
unbounded = true
|
||||
)
|
||||
.onSizeChanged {
|
||||
headerHeight = it.height.toFloat()
|
||||
}
|
||||
) {
|
||||
forumInfo?.let { holder ->
|
||||
ForumHeader(
|
||||
forumInfoImmutableHolder = it,
|
||||
forumInfoImmutableHolder = holder,
|
||||
onOpenForumInfo = {
|
||||
navigator.navigate(ForumDetailPageDestination(forumId = it.get { this.id }))
|
||||
navigator.navigate(
|
||||
ForumDetailPageDestination(
|
||||
forumId = holder.get { this.id })
|
||||
)
|
||||
},
|
||||
onBtnClick = {
|
||||
val (forum) = it
|
||||
val (forum) = holder
|
||||
when {
|
||||
forum.is_like != 1 -> viewModel.send(
|
||||
ForumUiIntent.Like(
|
||||
|
|
@ -683,32 +800,19 @@ fun ForumPage(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
// enable event when scroll image.
|
||||
.scrollable(
|
||||
orientation = Orientation.Vertical,
|
||||
state = rememberScrollableState { it }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val tabText = stringResource(id = R.string.tab_forum_1)
|
||||
val tabTextStyle = MaterialTheme.typography.button.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp,
|
||||
letterSpacing = 0.sp
|
||||
)
|
||||
val tabWidth = remember {
|
||||
val width = textMeasurer.measure(
|
||||
AnnotatedString(tabText),
|
||||
style = tabTextStyle
|
||||
).size.width.pxToDp()
|
||||
(width + 16 * 2) * 2
|
||||
}
|
||||
|
||||
TabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = currentPage,
|
||||
indicator = { tabPositions ->
|
||||
PagerTabIndicator(
|
||||
pagerState = pagerState,
|
||||
|
|
@ -718,8 +822,9 @@ fun ForumPage(
|
|||
divider = {},
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = ExtendedTheme.colors.primary,
|
||||
edgePadding = 0.dp,
|
||||
modifier = Modifier
|
||||
.width(tabWidth.dp)
|
||||
.wrapContentWidth(align = Alignment.Start)
|
||||
.align(Alignment.Start)
|
||||
) {
|
||||
val menuState = rememberMenuState()
|
||||
|
|
@ -759,7 +864,7 @@ fun ForumPage(
|
|||
coroutineScope.launch {
|
||||
emitGlobalEvent(
|
||||
ForumThreadListUiEvent.Refresh(
|
||||
pagerState.currentPage == 1,
|
||||
currentPage == 1,
|
||||
value
|
||||
)
|
||||
)
|
||||
|
|
@ -772,12 +877,12 @@ fun ForumPage(
|
|||
}
|
||||
) {
|
||||
val rotate by animateFloatAsState(targetValue = if (menuState.expanded) 180f else 0f)
|
||||
val alpha by animateFloatAsState(targetValue = if (pagerState.currentPage == 0) 1f else 0f)
|
||||
val alpha by animateFloatAsState(targetValue = if (currentPage == 0) 1f else 0f)
|
||||
|
||||
Tab(
|
||||
selected = pagerState.currentPage == 0,
|
||||
selected = currentPage == 0,
|
||||
onClick = {
|
||||
if (pagerState.currentPage != 0) {
|
||||
if (currentPage != 0) {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(0)
|
||||
}
|
||||
|
|
@ -796,7 +901,7 @@ fun ForumPage(
|
|||
.padding(start = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tab_forum_1),
|
||||
text = stringResource(id = R.string.tab_forum_latest),
|
||||
style = tabTextStyle
|
||||
)
|
||||
Icon(
|
||||
|
|
@ -811,7 +916,7 @@ fun ForumPage(
|
|||
}
|
||||
}
|
||||
Tab(
|
||||
selected = pagerState.currentPage == 1,
|
||||
selected = currentPage == 1,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(1)
|
||||
|
|
@ -846,13 +951,35 @@ fun ForumPage(
|
|||
forumId = forumInfo!!.get { id },
|
||||
forumName = forumInfo!!.get { name },
|
||||
isGood = it == 1,
|
||||
lazyListState = remember { lazyListStates[it] }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = showTip,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.windowInsetsPadding(WindowInsets.safeContent)
|
||||
) {
|
||||
Text(
|
||||
text = if (isFakeLoading || swipeableState.targetValue)
|
||||
stringResource(id = R.string.release_to_refresh)
|
||||
else stringResource(id = R.string.pull_down_to_refresh),
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
style = MaterialTheme.typography.caption,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -891,7 +1018,10 @@ fun LoadingPlaceholder(
|
|||
.padding(16.dp)
|
||||
)
|
||||
Row(modifier = Modifier.height(48.dp)) {
|
||||
repeat(2) {
|
||||
persistentListOf(
|
||||
stringResource(id = R.string.tab_forum_latest),
|
||||
stringResource(id = R.string.tab_forum_good),
|
||||
).fastForEach {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
|
|
@ -899,7 +1029,7 @@ fun LoadingPlaceholder(
|
|||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tab_forum_1),
|
||||
text = it,
|
||||
modifier = Modifier.placeholder(
|
||||
visible = true,
|
||||
highlight = PlaceholderHighlight.fade(),
|
||||
|
|
@ -1015,3 +1145,52 @@ private fun RowScope.StatCardItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
private val <T> SwipeableState<T>.LoadPreDownPostUpNestedScrollConnection: NestedScrollConnection
|
||||
get() = object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
val delta = available.toFloat()
|
||||
return if (delta < 0 && source == NestedScrollSource.Drag) {
|
||||
performDrag(delta).toOffset()
|
||||
} else {
|
||||
Offset.Zero
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset {
|
||||
return if (source == NestedScrollSource.Drag) {
|
||||
performDrag(available.toFloat()).toOffset()
|
||||
} else {
|
||||
Offset.Zero
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
val toFling = Offset(available.x, available.y).toFloat()
|
||||
return if (toFling > 0) {
|
||||
performFling(velocity = toFling)
|
||||
// since we go to the anchor with tween settling, consume all for the best UX
|
||||
// available
|
||||
Velocity.Zero
|
||||
} else {
|
||||
Velocity.Zero
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onPostFling(
|
||||
consumed: Velocity,
|
||||
available: Velocity,
|
||||
): Velocity {
|
||||
performFling(velocity = Offset(available.x, available.y).toFloat())
|
||||
return Velocity.Zero
|
||||
}
|
||||
|
||||
private fun Float.toOffset(): Offset = Offset(0f, this)
|
||||
|
||||
private fun Offset.toFloat(): Float = this.y
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ import androidx.compose.material.MaterialTheme
|
|||
import androidx.compose.material.SnackbarResult
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -276,12 +275,14 @@ fun ForumThreadListPage(
|
|||
forumId: Long,
|
||||
forumName: String,
|
||||
isGood: Boolean = false,
|
||||
lazyListState: LazyListState = rememberLazyListState(),
|
||||
viewModel: ForumThreadListViewModel = if (isGood) pageViewModel<GoodThreadListViewModel>() else pageViewModel<LatestThreadListViewModel>()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.current
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
viewModel.send(getFirstLoadIntent(context, forumName, isGood))
|
||||
viewModel.initialized = true
|
||||
|
|
@ -356,7 +357,7 @@ fun ForumThreadListPage(
|
|||
refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.send(getRefreshIntent(context, forumName, isGood)) }
|
||||
)
|
||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||
Box {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
onLoadMore = {
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@
|
|||
<string name="button_signed">已签到</string>
|
||||
<string name="button_like">关注</string>
|
||||
<string name="toast_like_success">关注成功,你是第 %s 个关注本吧的</string>
|
||||
<string name="tab_forum_1">查看贴子</string>
|
||||
<string name="tab_forum_good">吧内精华</string>
|
||||
<string name="tab_forum_latest">最新</string>
|
||||
<string name="tab_forum_good">精华</string>
|
||||
<string name="tip_good">精</string>
|
||||
<string name="title_my_message">我的消息</string>
|
||||
<string name="title_messages">消息</string>
|
||||
|
|
@ -745,4 +745,6 @@
|
|||
<string name="title_forum_intro">本吧简介</string>
|
||||
<string name="title_forum_rule">本吧吧规</string>
|
||||
<string name="desc_forum_rule">吧规</string>
|
||||
<string name="pull_down_to_refresh">继续下拉以刷新</string>
|
||||
<string name="release_to_refresh">松手刷新</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue