优化个人主页并实现列表位置记忆
- **实现列表滚动位置记忆**: 在`MyProfileViewModel`中添加状态变量,用于记录动态、智能体和群聊列表的滚动位置,以及当前所在的标签页。当用户离开并返回个人主页时,能够恢复到上次浏览的位置,提升用户体验。 - **添加智能体列表分页加载**: 新增`loadMoreAgent`方法,并结合`derivedStateOf`在列表滚动到底部时自动加载更多智能体数据。 - **统一列表滚动处理**: 引入`NestedScrollConnection`,以协调处理个人主页头部区域与下方标签页中各列表(动态、智能体、群聊)之间的嵌套滚动,解决了滚动冲突问题。 - **列表加载与空状态优化**: 为智能体列表添加了加载中(loading)和“没有更多”的提示,并优化了空状态下的布局显示。 - **导航栏样式优化**: 调整了个人主页顶部导航栏的背景和图标颜色过渡效果,使其在滚动时更加平滑和美观。
This commit is contained in:
@@ -422,13 +422,6 @@ fun Profile() {
|
|||||||
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
|
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面退出时清理个人资料相关资源
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
ResourceCleanupManager.cleanupPageResources("profile")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
|
|||||||
@@ -52,15 +52,24 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
private val roomsPageSize = 20
|
private val roomsPageSize = 20
|
||||||
private val apiClient: RaveNowAPI = ApiClient.api
|
private val apiClient: RaveNowAPI = ApiClient.api
|
||||||
|
|
||||||
|
var profileScrollOffset by mutableStateOf(0)
|
||||||
|
var profileGridFirstVisibleItemIndex by mutableStateOf(0)
|
||||||
|
var profileGridFirstVisibleItemOffset by mutableStateOf(0)
|
||||||
|
var profileAgentListFirstVisibleItemIndex by mutableStateOf(0)
|
||||||
|
var profileAgentListFirstVisibleItemOffset by mutableStateOf(0)
|
||||||
|
var profileGroupChatFirstVisibleItemIndex by mutableStateOf(0)
|
||||||
|
var profileGroupChatFirstVisibleItemOffset by mutableStateOf(0)
|
||||||
|
var profileCurrentPage by mutableStateOf(0)
|
||||||
|
|
||||||
val momentLoader: MomentLoader = MomentLoader().apply {
|
val momentLoader: MomentLoader = MomentLoader().apply {
|
||||||
pageSize = 20 // 设置与后端一致的页面大小
|
pageSize = 20 // 设置与后端一致的页面大小
|
||||||
onListChanged = {
|
onListChanged = { updated ->
|
||||||
moments = it
|
moments = updated.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val agentLoader: AgentLoader = AgentLoader().apply {
|
val agentLoader: AgentLoader = AgentLoader().apply {
|
||||||
onListChanged = {
|
onListChanged = { updated ->
|
||||||
agents = it
|
agents = updated.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var refreshing by mutableStateOf(false)
|
var refreshing by mutableStateOf(false)
|
||||||
@@ -137,6 +146,26 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadMoreAgent() {
|
||||||
|
if (AppStore.isGuest) {
|
||||||
|
Log.d("MyProfileViewModel", "loadMoreAgent: 游客模式下跳过加载更多智能体")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
Log.d("MyProfileViewModel", "loadMoreAgent: hasNext = ${agentLoader.hasNext}")
|
||||||
|
if (profile == null) {
|
||||||
|
agentLoader.loadMore(extra = AgentLoaderExtraArgs(authorId = null))
|
||||||
|
} else {
|
||||||
|
agentLoader.loadMore(extra = AgentLoaderExtraArgs(authorId = profile?.id))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MyProfileViewModel", "loadMoreAgent: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun logout(context: Context) {
|
fun logout(context: Context) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// 只有非游客用户才需要取消注册推送设备
|
// 只有非游客用户才需要取消注册推送设备
|
||||||
@@ -230,6 +259,14 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
momentLoader.clear()
|
momentLoader.clear()
|
||||||
agentLoader.clear()
|
agentLoader.clear()
|
||||||
firstLoad = true
|
firstLoad = true
|
||||||
|
profileScrollOffset = 0
|
||||||
|
profileGridFirstVisibleItemIndex = 0
|
||||||
|
profileGridFirstVisibleItemOffset = 0
|
||||||
|
profileAgentListFirstVisibleItemIndex = 0
|
||||||
|
profileAgentListFirstVisibleItemOffset = 0
|
||||||
|
profileGroupChatFirstVisibleItemIndex = 0
|
||||||
|
profileGroupChatFirstVisibleItemOffset = 0
|
||||||
|
profileCurrentPage = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ 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.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -106,11 +107,14 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.ui.geometry.Offset
|
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.text.style.TextAlign
|
||||||
import androidx.compose.ui.graphics.Brush
|
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 com.aiosman.ravenow.ui.points.PointsBottomSheet
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -133,7 +137,10 @@ fun ProfileV3(
|
|||||||
) {
|
) {
|
||||||
val model = MyProfileViewModel
|
val model = MyProfileViewModel
|
||||||
// Tabs: 动态、(可选)智能体、群聊
|
// Tabs: 动态、(可选)智能体、群聊
|
||||||
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 2 else 3 })
|
val pagerState = rememberPagerState(
|
||||||
|
initialPage = model.profileCurrentPage,
|
||||||
|
pageCount = { if (isAiAccount) 2 else 3 }
|
||||||
|
)
|
||||||
val enabled by remember { mutableStateOf(true) }
|
val enabled by remember { mutableStateOf(true) }
|
||||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
@@ -165,9 +172,65 @@ fun ProfileV3(
|
|||||||
val appTheme = LocalAppTheme.current
|
val appTheme = LocalAppTheme.current
|
||||||
val AppColors = appTheme
|
val AppColors = appTheme
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState(
|
||||||
val gridState = rememberLazyGridState()
|
initialFirstVisibleItemIndex = model.profileAgentListFirstVisibleItemIndex,
|
||||||
val scrollState = rememberScrollState()
|
initialFirstVisibleItemScrollOffset = model.profileAgentListFirstVisibleItemOffset
|
||||||
|
)
|
||||||
|
val groupChatListState = rememberLazyListState(
|
||||||
|
initialFirstVisibleItemIndex = model.profileGroupChatFirstVisibleItemIndex,
|
||||||
|
initialFirstVisibleItemScrollOffset = model.profileGroupChatFirstVisibleItemOffset
|
||||||
|
)
|
||||||
|
val gridState = rememberLazyGridState(
|
||||||
|
initialFirstVisibleItemIndex = model.profileGridFirstVisibleItemIndex,
|
||||||
|
initialFirstVisibleItemScrollOffset = model.profileGridFirstVisibleItemOffset
|
||||||
|
)
|
||||||
|
val scrollState = rememberScrollState(model.profileScrollOffset)
|
||||||
|
val nestedScrollConnection = remember(scrollState, pagerState, gridState, listState, groupChatListState, isAiAccount) {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
if (available.y >= 0f) return Offset.Zero
|
||||||
|
val consumed = scrollState.dispatchRawDelta(-available.y)
|
||||||
|
return if (consumed != 0f) Offset(0f, -consumed) else Offset.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
if (available.y <= 0f) return Offset.Zero
|
||||||
|
|
||||||
|
val childCanScrollUp = when (pagerState.currentPage) {
|
||||||
|
0 -> gridState.firstVisibleItemIndex > 0 || gridState.firstVisibleItemScrollOffset > 0
|
||||||
|
1 -> if (!isAiAccount) {
|
||||||
|
listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0
|
||||||
|
} else {
|
||||||
|
groupChatListState.firstVisibleItemIndex > 0 || groupChatListState.firstVisibleItemScrollOffset > 0
|
||||||
|
}
|
||||||
|
2 -> if (!isAiAccount) {
|
||||||
|
groupChatListState.firstVisibleItemIndex > 0 || groupChatListState.firstVisibleItemScrollOffset > 0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
if (childCanScrollUp) return Offset.Zero
|
||||||
|
|
||||||
|
val consumedByParent = scrollState.dispatchRawDelta(-available.y)
|
||||||
|
return if (consumedByParent != 0f) Offset(0f, -consumedByParent) else Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
model.profileCurrentPage = pagerState.currentPage
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
model.profileScrollOffset = scrollState.value
|
||||||
|
model.profileGridFirstVisibleItemIndex = gridState.firstVisibleItemIndex
|
||||||
|
model.profileGridFirstVisibleItemOffset = gridState.firstVisibleItemScrollOffset
|
||||||
|
model.profileAgentListFirstVisibleItemIndex = listState.firstVisibleItemIndex
|
||||||
|
model.profileAgentListFirstVisibleItemOffset = listState.firstVisibleItemScrollOffset
|
||||||
|
model.profileGroupChatFirstVisibleItemIndex = groupChatListState.firstVisibleItemIndex
|
||||||
|
model.profileGroupChatFirstVisibleItemOffset = groupChatListState.firstVisibleItemScrollOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
val pointsBalanceState = PointService.pointsBalance.collectAsState(initial = null)
|
val pointsBalanceState = PointService.pointsBalance.collectAsState(initial = null)
|
||||||
|
|
||||||
// 计算导航栏背景透明度,根据滚动位置从0到1
|
// 计算导航栏背景透明度,根据滚动位置从0到1
|
||||||
@@ -184,10 +247,21 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// observe list scrolling
|
// observe list scrolling
|
||||||
val reachedListBottom by remember {
|
val reachedAgentsBottom by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
|
if (isAiAccount || pagerState.currentPage != 1) {
|
||||||
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2
|
false
|
||||||
|
} else {
|
||||||
|
val layoutInfo = listState.layoutInfo
|
||||||
|
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
||||||
|
val totalItems = layoutInfo.totalItemsCount
|
||||||
|
if (lastVisibleItem == null || totalItems == 0) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val triggerIndex = max(0, totalItems - 2)
|
||||||
|
lastVisibleItem.index >= triggerIndex && model.agentLoader.hasNext && !model.agentLoader.isLoading
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +291,9 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load more if scrolled to bottom of list
|
// load more if scrolled to bottom of list
|
||||||
LaunchedEffect(reachedListBottom) {
|
LaunchedEffect(reachedAgentsBottom) {
|
||||||
if (reachedListBottom) {
|
if (reachedAgentsBottom) {
|
||||||
onLoadMore()
|
model.loadMoreAgent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +436,6 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 壁纸下方间距
|
// 壁纸下方间距
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
Box(
|
Box(
|
||||||
@@ -460,7 +533,12 @@ fun ProfileV3(
|
|||||||
modifier = Modifier.height(500.dp) // 固定滚动高度
|
modifier = Modifier.height(500.dp) // 固定滚动高度
|
||||||
) { idx ->
|
) { idx ->
|
||||||
when (idx) {
|
when (idx) {
|
||||||
0 -> GalleryGrid(moments = moments)
|
0 -> GalleryGrid(
|
||||||
|
moments = moments,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
state = gridState,
|
||||||
|
nestedScrollConnection = nestedScrollConnection
|
||||||
|
)
|
||||||
1 -> {
|
1 -> {
|
||||||
if (!isAiAccount) {
|
if (!isAiAccount) {
|
||||||
UserAgentsList(
|
UserAgentsList(
|
||||||
@@ -482,15 +560,28 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxSize()
|
hasMore = model.agentLoader.hasNext,
|
||||||
|
isLoading = model.agentLoader.isLoading,
|
||||||
|
showNoMoreText = isSelf,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
state = listState,
|
||||||
|
nestedScrollConnection = nestedScrollConnection
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GroupChatPlaceholder()
|
GroupChatPlaceholder(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
listState = groupChatListState,
|
||||||
|
nestedScrollConnection = nestedScrollConnection
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
if (!isAiAccount) {
|
if (!isAiAccount) {
|
||||||
GroupChatPlaceholder()
|
GroupChatPlaceholder(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
listState = groupChatListState,
|
||||||
|
nestedScrollConnection = nestedScrollConnection
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,7 +589,7 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 底部间距,增加滚动距离
|
// 底部间距,增加滚动距离
|
||||||
Spacer(modifier = Modifier.height(100.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 顶部导航栏
|
// 顶部导航栏
|
||||||
@@ -594,8 +685,16 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GroupChatPlaceholder() {
|
private fun GroupChatPlaceholder(
|
||||||
GroupChatEmptyContent()
|
modifier: Modifier = Modifier,
|
||||||
|
listState: androidx.compose.foundation.lazy.LazyListState,
|
||||||
|
nestedScrollConnection: NestedScrollConnection? = null
|
||||||
|
) {
|
||||||
|
GroupChatEmptyContent(
|
||||||
|
modifier = modifier,
|
||||||
|
listState = listState,
|
||||||
|
nestedScrollConnection = nestedScrollConnection
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//顶部导航栏组件
|
//顶部导航栏组件
|
||||||
@@ -616,18 +715,24 @@ 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
|
||||||
// 亮色模式下:根据背景透明度决定,透明度为1时变黑,否则为白色
|
val cardBorderColor = if (backgroundAlpha >= 0.7f) appColors.divider else Color.White
|
||||||
val iconColor = if (AppState.darkMode) {
|
val toolbarSolidColor = remember(backgroundAlpha, appColors) {
|
||||||
Color.White // 暗色模式下图标始终为白色
|
appColors.background.copy(alpha = backgroundAlpha.coerceIn(0f, 1f))
|
||||||
} else {
|
|
||||||
if (backgroundAlpha >= 1f) Color.Black else Color.White
|
|
||||||
}
|
}
|
||||||
val cardBorderColor = if (AppState.darkMode) {
|
val toolbarOverlayBrush = remember(backgroundAlpha) {
|
||||||
Color.White // 暗色模式下边框应为白色
|
val overlayAlpha = (1f - backgroundAlpha).coerceIn(0f, 1f) * 0.25f
|
||||||
|
if (overlayAlpha > 0f) {
|
||||||
|
Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color.Black.copy(alpha = overlayAlpha),
|
||||||
|
Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (backgroundAlpha >= 1f) Color.Black else Color.White
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -640,58 +745,23 @@ fun TopNavigationBar(
|
|||||||
|
|
||||||
// 导航栏背景层,包括状态栏区域,根据滚动位置逐渐变白
|
// 导航栏背景层,包括状态栏区域,根据滚动位置逐渐变白
|
||||||
val totalHeight = statusBarHeight + navigationBarHeight
|
val totalHeight = statusBarHeight + navigationBarHeight
|
||||||
val density = LocalDensity.current
|
|
||||||
val totalHeightPx = with(density) { totalHeight.toPx() }
|
|
||||||
|
|
||||||
// 根据滚动位置计算基础颜色,从深色平滑过渡到白色,透明度从初始值逐渐减到0
|
|
||||||
val baseColor = remember(backgroundAlpha) {
|
|
||||||
val smoothProgress = backgroundAlpha.coerceIn(0f, 1f)
|
|
||||||
|
|
||||||
if (AppState.darkMode) {
|
|
||||||
// 暗色模式下:从黑色(透明度0)逐渐变为黑色(透明度1)
|
|
||||||
val alpha = smoothProgress.coerceIn(0f, 1f)
|
|
||||||
Color.Black.copy(alpha = alpha)
|
|
||||||
} else {
|
|
||||||
// 亮色模式:初始状态:半透明深色,让白色图标清晰可见
|
|
||||||
val initialDarkAlpha = 0.12f
|
|
||||||
|
|
||||||
// 使用平滑的插值函数,让整个过渡更自然
|
|
||||||
val easedProgress = smoothProgress * smoothProgress * (3f - 2f * smoothProgress) // smoothstep
|
|
||||||
|
|
||||||
// 颜色值:从黑色(0)平滑过渡到白色(1)
|
|
||||||
val colorValue = easedProgress
|
|
||||||
|
|
||||||
// 透明度:从初始值(0.12f)逐渐减少到0
|
|
||||||
// 当smoothProgress从0到1时,alpha从initialDarkAlpha减少到0
|
|
||||||
val alpha = initialDarkAlpha * (1f - easedProgress)
|
|
||||||
|
|
||||||
Color(
|
|
||||||
red = colorValue,
|
|
||||||
green = colorValue,
|
|
||||||
blue = colorValue,
|
|
||||||
alpha = alpha.coerceIn(0f, initialDarkAlpha)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(totalHeight) // 状态栏高度 + 导航栏高度
|
.height(totalHeight) // 状态栏高度 + 导航栏高度
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.background(
|
.background(toolbarSolidColor)
|
||||||
brush = Brush.verticalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
baseColor, // 顶部保持基础颜色
|
|
||||||
baseColor, // 中间保持基础颜色
|
|
||||||
baseColor.copy(alpha = baseColor.alpha * 0.5f), // 底部过渡,逐渐变透明
|
|
||||||
Color.Transparent // 最底部完全透明
|
|
||||||
),
|
|
||||||
startY = 0f,
|
|
||||||
endY = totalHeightPx
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
toolbarOverlayBrush?.let { brush ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(totalHeight)
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.background(brush)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 功能按钮区域,图标和文字根据背景透明度改变颜色
|
// 功能按钮区域,图标和文字根据背景透明度改变颜色
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import com.aiosman.ravenow.LocalAppTheme
|
|||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -49,6 +49,8 @@ import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
|||||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||||
import com.aiosman.ravenow.ui.network.ReloadButton
|
import com.aiosman.ravenow.ui.network.ReloadButton
|
||||||
import com.aiosman.ravenow.utils.NetworkUtils
|
import com.aiosman.ravenow.utils.NetworkUtils
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
@Composable
|
@Composable
|
||||||
fun GalleryItem(
|
fun GalleryItem(
|
||||||
moment: MomentEntity,
|
moment: MomentEntity,
|
||||||
@@ -132,18 +134,25 @@ fun GalleryItem(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GalleryGrid(
|
fun GalleryGrid(
|
||||||
moments: List<MomentEntity>
|
moments: List<MomentEntity>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
state: LazyGridState,
|
||||||
|
nestedScrollConnection: NestedScrollConnection? = null
|
||||||
) {
|
) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val gridState = rememberLazyGridState()
|
|
||||||
val debouncer = rememberDebouncer()
|
val debouncer = rememberDebouncer()
|
||||||
var refreshKey by remember { mutableStateOf(0) }
|
var refreshKey by remember { mutableStateOf(0) }
|
||||||
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||||
|
val baseModifier = if (nestedScrollConnection != null) {
|
||||||
|
modifier.nestedScroll(nestedScrollConnection)
|
||||||
|
} else {
|
||||||
|
modifier
|
||||||
|
}
|
||||||
|
|
||||||
if (!isNetworkAvailable) {
|
if (!isNetworkAvailable) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = baseModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(vertical = 60.dp),
|
.padding(vertical = 60.dp),
|
||||||
@@ -183,7 +192,7 @@ fun GalleryGrid(
|
|||||||
}
|
}
|
||||||
} else if (moments.isEmpty()) {
|
} else if (moments.isEmpty()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = baseModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(vertical = 60.dp),
|
.padding(vertical = 60.dp),
|
||||||
@@ -227,8 +236,10 @@ fun GalleryGrid(
|
|||||||
} else {
|
} else {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(3),
|
columns = GridCells.Fixed(3),
|
||||||
state = gridState,
|
state = state,
|
||||||
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp),
|
modifier = baseModifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(moments) { idx, moment ->
|
itemsIndexed(moments) { idx, moment ->
|
||||||
if (moment != null && moment.images.isNotEmpty()) {
|
if (moment != null && moment.images.isNotEmpty()) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
@@ -43,6 +44,8 @@ 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 androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import com.aiosman.ravenow.ConstVars
|
import com.aiosman.ravenow.ConstVars
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
@@ -59,7 +62,11 @@ import android.util.Base64
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GroupChatEmptyContent() {
|
fun GroupChatEmptyContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
listState: LazyListState,
|
||||||
|
nestedScrollConnection: NestedScrollConnection? = null
|
||||||
|
) {
|
||||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -86,8 +93,14 @@ fun GroupChatEmptyContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nestedScrollModifier = if (nestedScrollConnection != null) {
|
||||||
|
Modifier.nestedScroll(nestedScrollConnection)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -106,14 +119,14 @@ fun GroupChatEmptyContent() {
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = nestedScrollModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.pullRefresh(state)
|
.pullRefresh(state)
|
||||||
) {
|
) {
|
||||||
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
|
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
|
||||||
// 空状态内容(居中)
|
// 空状态内容(居中)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = nestedScrollModifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
// 空状态插图
|
// 空状态插图
|
||||||
@@ -135,7 +148,8 @@ fun GroupChatEmptyContent() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize()
|
state = listState,
|
||||||
|
modifier = nestedScrollModifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = viewModel.rooms,
|
items = viewModel.rooms,
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ 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.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -38,6 +40,8 @@ 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 androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||||
@@ -57,16 +61,31 @@ fun UserAgentsList(
|
|||||||
agents: List<AgentEntity>,
|
agents: List<AgentEntity>,
|
||||||
onAgentClick: (AgentEntity) -> Unit = {},
|
onAgentClick: (AgentEntity) -> Unit = {},
|
||||||
onAvatarClick: (AgentEntity) -> Unit = {},
|
onAvatarClick: (AgentEntity) -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
hasMore: Boolean,
|
||||||
|
isLoading: Boolean,
|
||||||
|
showNoMoreText: Boolean = false,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
state: LazyListState,
|
||||||
|
nestedScrollConnection: NestedScrollConnection? = null
|
||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
val listModifier = if (nestedScrollConnection != null) {
|
||||||
|
modifier.nestedScroll(nestedScrollConnection)
|
||||||
|
} else {
|
||||||
|
modifier
|
||||||
|
}
|
||||||
|
|
||||||
if (agents.isEmpty()) {
|
if (agents.isEmpty()) {
|
||||||
// 使用带分段控制器的空状态布局
|
// 使用带分段控制器的空状态布局
|
||||||
|
Box(
|
||||||
|
modifier = listModifier.fillMaxSize()
|
||||||
|
) {
|
||||||
AgentEmptyContentWithSegments()
|
AgentEmptyContentWithSegments()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier.fillMaxSize(),
|
state = state,
|
||||||
|
modifier = listModifier.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
items(agents) { agent ->
|
items(agents) { agent ->
|
||||||
@@ -77,9 +96,39 @@ fun UserAgentsList(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 底部间距
|
if (isLoading) {
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(120.dp))
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 24.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = AppColors.main
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!hasMore && showNoMoreText) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 24.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "没有更多的滋养",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = AppColors.secondaryText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(80.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user