Merge origin/main into nagisa - keep both versions
This commit is contained in:
@@ -4,9 +4,11 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -14,7 +16,6 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -22,12 +23,11 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -37,12 +37,12 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -52,26 +52,24 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.graphics.nativeCanvas
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.ConstVars
|
import com.aiosman.ravenow.ConstVars
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.MainActivity
|
import com.aiosman.ravenow.MainActivity
|
||||||
@@ -82,14 +80,9 @@ import com.aiosman.ravenow.entity.AgentEntity
|
|||||||
import com.aiosman.ravenow.entity.MomentEntity
|
import com.aiosman.ravenow.entity.MomentEntity
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
|
||||||
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
|
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
|
||||||
import com.aiosman.ravenow.ui.composables.toolbar.CollapsingToolbarScaffold
|
|
||||||
import com.aiosman.ravenow.ui.composables.toolbar.ScrollStrategy
|
|
||||||
import com.aiosman.ravenow.ui.composables.toolbar.rememberCollapsingToolbarScaffoldState
|
|
||||||
import com.aiosman.ravenow.ui.index.IndexViewModel
|
import com.aiosman.ravenow.ui.index.IndexViewModel
|
||||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GalleryGrid
|
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GalleryGrid
|
||||||
import com.aiosman.ravenow.ui.post.MenuActionItem
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GroupChatEmptyContent
|
import com.aiosman.ravenow.ui.index.tabs.profile.composable.GroupChatEmptyContent
|
||||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.OtherProfileAction
|
import com.aiosman.ravenow.ui.index.tabs.profile.composable.OtherProfileAction
|
||||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsList
|
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsList
|
||||||
@@ -97,23 +90,13 @@ import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsRow
|
|||||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserContentPageIndicator
|
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserContentPageIndicator
|
||||||
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserItem
|
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserItem
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import com.aiosman.ravenow.ui.navigateToPost
|
import com.aiosman.ravenow.ui.post.MenuActionItem
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.foundation.Canvas
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import com.aiosman.ravenow.ui.points.PointsBottomSheet
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||||
@@ -233,16 +216,13 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
val pointsBalanceState = PointService.pointsBalance.collectAsState(initial = null)
|
val pointsBalanceState = PointService.pointsBalance.collectAsState(initial = null)
|
||||||
|
|
||||||
// 计算导航栏背景透明度,根据滚动位置从0到1
|
// 计算导航栏背景透明度,根据滚动位置从0到1,在前段就达到完全不透明
|
||||||
val toolbarBackgroundAlpha by remember {
|
val toolbarBackgroundAlpha by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (!isSelf) {
|
val maxScroll = 120f // 大幅减少最大滚动距离,让前段就完全不透明
|
||||||
1f
|
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
|
||||||
} else {
|
// 直接使用线性插值,尽快达到完全不透明
|
||||||
val maxScroll = 600f // 增加最大滚动距离,让渐变更平缓
|
progress
|
||||||
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
|
|
||||||
progress
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +498,7 @@ fun ProfileV3(
|
|||||||
)
|
)
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.height(500.dp) // 固定滚动高度
|
modifier = Modifier.height(650.dp) // 固定滚动高度
|
||||||
) { idx ->
|
) { idx ->
|
||||||
when (idx) {
|
when (idx) {
|
||||||
0 -> GalleryGrid(
|
0 -> GalleryGrid(
|
||||||
@@ -703,24 +683,18 @@ fun TopNavigationBar(
|
|||||||
// 仅本人主页显示积分:收集全局积分
|
// 仅本人主页显示积分:收集全局积分
|
||||||
val pointsBalanceState = if (isSelf) PointService.pointsBalance.collectAsState(initial = null) else null
|
val pointsBalanceState = if (isSelf) PointService.pointsBalance.collectAsState(initial = null) else null
|
||||||
|
|
||||||
// 根据背景透明度和主题决定图标与边框颜色
|
// 根据背景透明度和暗色模式决定图标颜色
|
||||||
val iconColor = if (backgroundAlpha >= 0.7f) appColors.text else Color.White
|
// 暗色模式下:图标始终为白色
|
||||||
val cardBorderColor = if (backgroundAlpha >= 0.7f) appColors.divider else Color.White
|
// 亮色模式下:根据背景透明度决定,透明度为1时变黑,否则为白色
|
||||||
val toolbarSolidColor = remember(backgroundAlpha, appColors) {
|
val iconColor = if (AppState.darkMode) {
|
||||||
appColors.background.copy(alpha = backgroundAlpha.coerceIn(0f, 1f))
|
Color.White // 暗色模式下图标始终为白色
|
||||||
|
} else {
|
||||||
|
if (backgroundAlpha >= 1f) Color.Black else Color.White
|
||||||
}
|
}
|
||||||
val toolbarOverlayBrush = remember(backgroundAlpha) {
|
val cardBorderColor = if (AppState.darkMode) {
|
||||||
val overlayAlpha = (1f - backgroundAlpha).coerceIn(0f, 1f) * 0.25f
|
Color.White // 暗色模式下边框应为白色
|
||||||
if (overlayAlpha > 0f) {
|
} else {
|
||||||
Brush.verticalGradient(
|
if (backgroundAlpha >= 1f) Color.Black else Color.White
|
||||||
colors = listOf(
|
|
||||||
Color.Black.copy(alpha = overlayAlpha),
|
|
||||||
Color.Transparent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -731,25 +705,31 @@ fun TopNavigationBar(
|
|||||||
val statusBarHeight = statusBarPadding.calculateTopPadding()
|
val statusBarHeight = statusBarPadding.calculateTopPadding()
|
||||||
val navigationBarHeight = 56.dp // 增加导航栏高度,包括图标和额外空间
|
val navigationBarHeight = 56.dp // 增加导航栏高度,包括图标和额外空间
|
||||||
|
|
||||||
// 导航栏背景层,包括状态栏区域,根据滚动位置逐渐变白
|
// 导航栏背景层,包括状态栏区域,根据滚动位置逐渐显示实色填充
|
||||||
val totalHeight = statusBarHeight + navigationBarHeight
|
val totalHeight = statusBarHeight + navigationBarHeight
|
||||||
|
|
||||||
|
// 根据滚动位置计算背景颜色,从透明逐渐变为实色填充,尽快完成
|
||||||
|
val toolbarBackgroundColor = remember(backgroundAlpha) {
|
||||||
|
val progress = backgroundAlpha.coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
if (AppState.darkMode) {
|
||||||
|
// 暗色模式下:从透明逐渐变为黑色实色填充
|
||||||
|
Color.Black.copy(alpha = progress)
|
||||||
|
} else {
|
||||||
|
// 亮色模式下:从透明逐渐变为白色实色填充
|
||||||
|
Color.White.copy(alpha = progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(totalHeight) // 状态栏高度 + 导航栏高度
|
.height(totalHeight) // 状态栏高度 + 导航栏高度
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.background(toolbarSolidColor)
|
.background(
|
||||||
|
color = toolbarBackgroundColor // 实色填充,不使用渐变
|
||||||
|
)
|
||||||
)
|
)
|
||||||
toolbarOverlayBrush?.let { brush ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(totalHeight)
|
|
||||||
.align(Alignment.TopCenter)
|
|
||||||
.background(brush)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 功能按钮区域,图标和文字根据背景透明度改变颜色
|
// 功能按钮区域,图标和文字根据背景透明度改变颜色
|
||||||
Row(
|
Row(
|
||||||
@@ -761,13 +741,25 @@ fun TopNavigationBar(
|
|||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// 左侧:互动数据卡片(仅本人主页显示)
|
// 左侧:互动数据卡片(仅自己的界面显示)
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
|
// 根据 toolbar 背景透明度动态调整卡片背景
|
||||||
|
val cardBackgroundColor = remember(backgroundAlpha) {
|
||||||
|
val smoothProgress = backgroundAlpha.coerceIn(0f, 1f)
|
||||||
|
if (AppState.darkMode) {
|
||||||
|
// 暗色模式:从半透明白色逐渐变为更不透明的白色
|
||||||
|
Color.White.copy(alpha = 0.52f + (0.48f * smoothProgress))
|
||||||
|
} else {
|
||||||
|
// 亮色模式:从半透明白色逐渐变为完全不透明的白色
|
||||||
|
Color.White.copy(alpha = 0.52f + (0.48f * smoothProgress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(24.dp)
|
.height(24.dp)
|
||||||
.background(
|
.background(
|
||||||
color = Color.White.copy(alpha = 0.52f),
|
color = cardBackgroundColor,
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
)
|
)
|
||||||
.border(
|
.border(
|
||||||
@@ -797,25 +789,25 @@ fun TopNavigationBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
// 中间:分享图标(仅自己的界面显示)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.menu_icon),
|
||||||
|
contentDescription = "分享",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
onShareClick()
|
||||||
|
},
|
||||||
|
colorFilter = ColorFilter.tint(iconColor) // 根据背景透明度改变颜色
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 中间:分享图标
|
// 右侧:菜单图标(三点图标)
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.mipmap.menu_icon),
|
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
||||||
contentDescription = "分享",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
onShareClick()
|
|
||||||
},
|
|
||||||
colorFilter = ColorFilter.tint(iconColor) // 根据背景透明度改变颜色
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
|
|
||||||
// 右侧:菜单图标
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.menu_ico),
|
|
||||||
contentDescription = "菜单",
|
contentDescription = "菜单",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -38,8 +40,16 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
import androidx.compose.ui.unit.Velocity
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
@@ -111,7 +121,7 @@ fun PointsBottomSheet(
|
|||||||
containerColor = AppColors.background,
|
containerColor = AppColors.background,
|
||||||
dragHandle = null // 移除拖动手柄
|
dragHandle = null // 移除拖动手柄
|
||||||
) {
|
) {
|
||||||
Column(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight(0.95f)
|
.fillMaxHeight(0.95f)
|
||||||
@@ -122,6 +132,11 @@ fun PointsBottomSheet(
|
|||||||
bottom = 8.dp
|
bottom = 8.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
) {
|
||||||
// 头部 - 使用 Box 实现绝对居中布局
|
// 头部 - 使用 Box 实现绝对居中布局
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -329,6 +344,7 @@ fun PointsBottomSheet(
|
|||||||
} else {
|
} else {
|
||||||
HowToEarnList(onRecharge = onRecharge)
|
HowToEarnList(onRecharge = onRecharge)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,22 +459,72 @@ private fun PointsHistoryList(
|
|||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val loading = PointsViewModel.loading
|
||||||
|
var lastLoadTriggeredIndex by remember { mutableStateOf(-1) }
|
||||||
|
var previousItemsSize by remember { mutableStateOf(items.size) }
|
||||||
|
|
||||||
// 创建 NestedScrollConnection 来阻止滚动事件传播到弹窗
|
// 创建 NestedScrollConnection 来阻止滚动事件向上传播到 ModalBottomSheet
|
||||||
|
// 使用 onPostScroll 来消费 LazyColumn 处理后的剩余滚动事件
|
||||||
val nestedScrollConnection = remember {
|
val nestedScrollConnection = remember {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
override fun onPostScroll(
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
consumed: Offset,
|
// 不消费任何事件,让 LazyColumn 先处理
|
||||||
available: Offset,
|
return Offset.Zero
|
||||||
source: NestedScrollSource
|
}
|
||||||
): Offset {
|
|
||||||
// 消费剩余的滚动事件,防止传播到 ModalBottomSheet 导致弹窗关闭
|
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
// 消费 LazyColumn 处理后的剩余滚动事件,防止传递到 ModalBottomSheet
|
||||||
|
return available
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||||
|
// 不消费惯性滚动,让 LazyColumn 先处理
|
||||||
|
return Velocity.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||||
|
// 消费 LazyColumn 处理后的剩余惯性滚动,防止传递到 ModalBottomSheet
|
||||||
return available
|
return available
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当数据加载完成后,重置触发索引,以便可以继续加载
|
||||||
|
LaunchedEffect(items.size, loading) {
|
||||||
|
if (items.size > previousItemsSize && !loading) {
|
||||||
|
// 数据已加载完成,重置触发索引
|
||||||
|
lastLoadTriggeredIndex = -1
|
||||||
|
previousItemsSize = items.size
|
||||||
|
} else if (items.size != previousItemsSize) {
|
||||||
|
previousItemsSize = items.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听滚动位置,接近底部时自动加载更多
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
snapshotFlow {
|
||||||
|
listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
|
||||||
|
}
|
||||||
|
.debounce(500) // 防抖500ms,等待滚动停止后再触发,避免快速滚动时频繁触发
|
||||||
|
.distinctUntilChanged() // 只在值变化时触发
|
||||||
|
.collect { lastVisibleIndex ->
|
||||||
|
if (lastVisibleIndex >= 0 && hasNext && !loading) {
|
||||||
|
val totalItems = items.size
|
||||||
|
// 当滚动到倒数第3个item时,触发加载更多
|
||||||
|
// 并且确保不会重复触发(检查上次触发的索引)
|
||||||
|
if (totalItems > 0 &&
|
||||||
|
lastVisibleIndex >= totalItems - 3 &&
|
||||||
|
lastVisibleIndex != lastLoadTriggeredIndex) {
|
||||||
|
lastLoadTriggeredIndex = lastVisibleIndex
|
||||||
|
onLoadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.nestedScroll(nestedScrollConnection)
|
.nestedScroll(nestedScrollConnection)
|
||||||
@@ -499,12 +565,37 @@ private fun PointsHistoryList(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasNext) {
|
// 显示加载状态
|
||||||
|
if (loading && items.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
Button(onClick = onLoadMore, modifier = Modifier
|
Box(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)) {
|
.fillMaxWidth()
|
||||||
Text(stringResource(R.string.load_more))
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.load_more),
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 显示已经到底提示
|
||||||
|
if (!hasNext && items.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "已经到底",
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -581,15 +672,26 @@ private fun HowToEarnList(onRecharge: () -> Unit) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 创建 NestedScrollConnection 来阻止滚动事件传播到弹窗
|
// 创建 NestedScrollConnection 来阻止滚动事件向上传播到 ModalBottomSheet
|
||||||
val nestedScrollConnection = remember {
|
val nestedScrollConnection = remember {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
override fun onPostScroll(
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
consumed: Offset,
|
// 不消费任何事件,让 LazyColumn 先处理
|
||||||
available: Offset,
|
return Offset.Zero
|
||||||
source: NestedScrollSource
|
}
|
||||||
): Offset {
|
|
||||||
// 消费剩余的滚动事件,防止传播到 ModalBottomSheet 导致弹窗关闭
|
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
// 消费 LazyColumn 处理后的剩余滚动事件,防止传递到 ModalBottomSheet
|
||||||
|
return available
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||||
|
// 不消费惯性滚动,让 LazyColumn 先处理
|
||||||
|
return Velocity.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||||
|
// 消费 LazyColumn 处理后的剩余惯性滚动,防止传递到 ModalBottomSheet
|
||||||
return available
|
return available
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user