再次优化加载性能

This commit is contained in:
BBIT-Kai
2026-05-28 17:15:17 +08:00
parent 4eeeaaa3bc
commit c7fd095c98
5 changed files with 224 additions and 90 deletions
@@ -240,6 +240,7 @@ fun <T : Any> MyRefreshTableForCard(
) {
val state = rememberPullToRefreshState()
val pagerSate = info.loadState.append
val refreshState = info.loadState.refresh
LaunchedEffect(pagerSate) {
if (pagerSate is LoadState.NotLoading) {
onFinishRefresh()
@@ -272,30 +273,37 @@ fun <T : Any> MyRefreshTableForCard(
}
}
) {
LazyVerticalGrid(
modifier = M.fillMaxSize(),
columns = GridCells.Fixed(columns),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(
count = info.itemCount,
key = info.itemKey { key(it) },
contentType = info.itemContentType { "paging-card" }
) { index ->
info[index]?.let { lineItem ->
Box {
item(lineItem, index)
Box(modifier = M.fillMaxSize()) {
LazyVerticalGrid(
modifier = M.fillMaxSize(),
columns = GridCells.Fixed(columns),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(
count = info.itemCount,
key = info.itemKey { key(it) },
contentType = info.itemContentType { "paging-card" }
) { index ->
info[index]?.let { lineItem ->
Box {
item(lineItem, index)
}
}
}
item(contentType = "paging-footer") {
when (pagerSate) {
is LoadState.NotLoading -> if (pagerSate.endOfPaginationReached)
ListNoMore()
is LoadState.Loading -> ListLoading()
is LoadState.Error -> ListError(message = pagerSate.error.message.toString())
}
}
}
item(contentType = "paging-footer") {
when (pagerSate) {
is LoadState.NotLoading -> if (pagerSate.endOfPaginationReached)
ListNoMore()
is LoadState.Loading -> ListLoading()
is LoadState.Error -> ListError(message = pagerSate.error.message.toString())
if (refreshState is LoadState.Loading && info.itemCount == 0) {
Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
ListLoading()
}
}
}
@@ -512,6 +520,7 @@ fun <T : Any> MyRefreshTable(
val listState: LazyListState = rememberLazyListState()
val state = rememberPullToRefreshState()
val pagerSate = info.loadState.append
val refreshState = info.loadState.refresh
LaunchedEffect(pagerSate) {
if (pagerSate is LoadState.NotLoading) {
onFinishRefresh()
@@ -553,8 +562,9 @@ fun <T : Any> MyRefreshTable(
}
}
) {
var selectIndex by rememberSaveable { mutableStateOf(-1) }
LazyColumn(state = listState) {
Box(modifier = M.fillMaxSize()) {
var selectIndex by rememberSaveable { mutableStateOf(-1) }
LazyColumn(state = listState) {
items(
count = info.itemCount,
key = info.itemKey { key(it) },
@@ -647,6 +657,12 @@ fun <T : Any> MyRefreshTable(
is LoadState.Error -> ListError(message = pagerSate.error.message.toString())
}
}
}
if (refreshState is LoadState.Loading && info.itemCount == 0) {
Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
ListLoading()
}
}
}
}
@@ -54,11 +54,12 @@ open class BaseViewModel : ViewModel() {
onUI: (T) -> Unit,
) {
viewModelScope.launch {
if (showDialog) {
Toasty.showLoadingDialog(loadingTips)
}
val result = kotlin.runCatching {
withContext(Dispatchers.IO) {
if (showDialog) {
Toasty.showLoadingDialog(loadingTips)
}
onIO()
}
}
@@ -67,33 +68,31 @@ open class BaseViewModel : ViewModel() {
hideLoadingDialog()
}
withContext(Dispatchers.Main) {
result.onSuccess { data ->
onUI(data)
}.onFailure { exception ->
// ✅ 如果是协程取消,不处理,只记录日志
if (exception is CancellationException || exception.cause is SocketException && exception.cause?.message?.contains(
"Socket closed"
) == true
) {
MyLog.test("协程被取消:${exception.javaClass.simpleName}message=${exception.message}")
return@onFailure
}
if (exception is HttpException && exception.code() == 401) {
Toasty.loginExpired()
Toasty.error("登录已过期")
return@onFailure
}
// 其他异常继续处理
exception.printStackTrace()
onError(exception)
exception.message?.let {
Toasty.error(it)
}
}.also {
// ✅ 最终执行的操作
onFinish()
result.onSuccess { data ->
onUI(data)
}.onFailure { exception ->
// ✅ 如果是协程取消,不处理,只记录日志
if (exception is CancellationException || exception.cause is SocketException && exception.cause?.message?.contains(
"Socket closed"
) == true
) {
MyLog.test("协程被取消:${exception.javaClass.simpleName}message=${exception.message}")
return@onFailure
}
if (exception is HttpException && exception.code() == 401) {
Toasty.loginExpired()
Toasty.error("登录已过期")
return@onFailure
}
// 其他异常继续处理
exception.printStackTrace()
onError(exception)
exception.message?.let {
Toasty.error(it)
}
}.also {
// ✅ 最终执行的操作
onFinish()
}
}
}
@@ -20,12 +20,13 @@ import androidx.navigation.compose.rememberNavController
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.bbitcn.f8.pad.M
@@ -47,8 +48,8 @@ import com.bbitcn.f8.pad.ui.screen.mainFunc.UserScreen
import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel
import com.cyberecho.ui.screen.ChatDialog
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.xutils.db.annotation.Column
@Preview(showBackground = true, widthDp = 1280)
@Composable
@@ -74,9 +75,43 @@ fun MainScreen(
}
Box(modifier = M.fillMaxWidth(), contentAlignment = Alignment.BottomStart) {
var selectedPage by rememberSaveable { mutableStateOf(0) }
var contentPage by rememberSaveable { mutableStateOf(0) }
var isSwitchingPage by remember { mutableStateOf(false) }
val cachedPages = remember { mutableStateListOf<Int>() }
val coroutineScope = rememberCoroutineScope()
val menu by mainViewModel.menu.collectAsState()
LaunchedEffect(menu, selectedPage) {
if (menu.isEmpty()) {
contentPage = 0
isSwitchingPage = false
cachedPages.clear()
return@LaunchedEffect
}
val safeSelectedPage = selectedPage.coerceIn(0, menu.lastIndex)
if (safeSelectedPage != selectedPage) {
selectedPage = safeSelectedPage
return@LaunchedEffect
}
cachedPages.removeAll { it !in menu.indices }
if (cachedPages.isEmpty()) {
cachedPages.add(safeSelectedPage)
}
if (contentPage != safeSelectedPage && safeSelectedPage !in cachedPages) {
isSwitchingPage = true
withFrameNanos { }
delay(180)
cachedPages.add(safeSelectedPage)
contentPage = safeSelectedPage
isSwitchingPage = false
} else if (contentPage != safeSelectedPage) {
contentPage = safeSelectedPage
isSwitchingPage = false
}
}
Row(modifier = M.fillMaxWidth()) {
NavigationRail(
containerColor = MyColors.Black,
@@ -99,35 +134,37 @@ fun MainScreen(
)
},
selected = selectedPage == index,
onClick = { selectedPage = index }
)
}
}
Box {
var index = -1
if (menu.isNotEmpty()) {
index = menu[selectedPage].index
}
when (index) {
-1 -> {
Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("正在加载菜单,如长时间未响应请点击重新加载")
MyButton(text = "刷新菜单") {
mainViewModel.refreshMenuPermission()
onClick = {
if (selectedPage != index) {
selectedPage = index
isSwitchingPage = index !in cachedPages
if (index in cachedPages) {
contentPage = index
}
}
}
}
0 -> HomeScreen(navController)
1 -> UserScreen(navController)
2 -> PurchaseScreen(navController, drawerViewModel = drawerViewModel)
3 -> FundsScreen(navController, drawerViewModel = drawerViewModel)
4 -> StatisticsScreen(drawerViewModel = drawerViewModel)
5 -> DryCoonScreen(navController, drawerViewModel)
6 -> SettingScreen(navController, updateViewModel = updateViewModel, topInfoViewmodel = topInfoViewmodel){
mainViewModel.refreshMenuPermission()
)
}
}
Box(modifier = M.weight(1f).fillMaxSize()) {
val selectedMenu = menu.getOrNull(selectedPage)
if (isSwitchingPage && selectedMenu != null && selectedPage !in cachedPages) {
MainPageLoading(selectedMenu.title)
} else {
cachedPages.forEach { page ->
key(page) {
val pageIndex = menu.getOrNull(page)?.index ?: -1
MainPageHost(visible = page == contentPage) {
MainPageContent(
index = pageIndex,
navController = navController,
drawerViewModel = drawerViewModel,
updateViewModel = updateViewModel,
topInfoViewmodel = topInfoViewmodel,
mainViewModel = mainViewModel
)
}
}
}
}
}
@@ -208,3 +245,69 @@ fun MainScreen(
val chatDialogData by mainViewModel.chatDialogData.collectAsState()
ChatDialog(chatDialogData)
}
@Composable
private fun MainPageLoading(title: String) {
Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
text = "正在加载$title...",
color = MyColors.Gray,
fontSize = MaterialTheme.typography.bodyLarge.fontSize
)
}
}
@Composable
private fun MainPageHost(
visible: Boolean,
content: @Composable () -> Unit
) {
Box(
modifier = if (visible) {
M
.fillMaxSize()
.alpha(1f)
.zIndex(1f)
} else {
M
.size(0.dp)
.alpha(0f)
.zIndex(0f)
}
) {
content()
}
}
@Composable
private fun MainPageContent(
index: Int,
navController: NavHostController,
drawerViewModel: DrawerViewModel,
updateViewModel: UpdateViewModel,
topInfoViewmodel: TopInfoViewModel,
mainViewModel: MainViewModel
) {
when (index) {
-1 -> {
Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("正在加载菜单,如长时间未响应请点击重新加载")
MyButton(text = "刷新菜单") {
mainViewModel.refreshMenuPermission()
}
}
}
}
0 -> HomeScreen(navController)
1 -> UserScreen(navController)
2 -> PurchaseScreen(navController, drawerViewModel = drawerViewModel)
3 -> FundsScreen(navController, drawerViewModel = drawerViewModel)
4 -> StatisticsScreen(drawerViewModel = drawerViewModel)
5 -> DryCoonScreen(navController, drawerViewModel)
6 -> SettingScreen(navController, updateViewModel = updateViewModel, topInfoViewmodel = topInfoViewmodel) {
mainViewModel.refreshMenuPermission()
}
}
}
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -45,6 +46,7 @@ import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.ui.screen.view.common.DatePickerRange
import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
import com.bbitcn.f8.pad.ui.theme.MyColors
import kotlinx.coroutines.delay
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.util.Locale
@@ -69,8 +71,18 @@ fun StatisticsScreen(
val ticketMoreDialogData by remember { mutableStateOf(TicketMoreDialogData()) }
var curType by remember { mutableStateOf(StatisticsResponse.Data()) }
var queryInput by rememberSaveable { mutableStateOf("") }
var queryInputInitialized by rememberSaveable { mutableStateOf(false) }
val pager = statisticsViewModel.statisticsPager.collectAsLazyPagingItems()
val dateRangeSelectDialogData by statisticsViewModel.dateRangeSelectDialogData.collectAsState()
LaunchedEffect(queryInput) {
if (!queryInputInitialized) {
queryInputInitialized = true
return@LaunchedEffect
}
delay(350)
statisticsViewModel.updateParamsLike(queryInput)
pager.refresh()
}
MainFuncFrame {
MyCard(colors = MyColors.LightLightBlueGreen) {
Row(
@@ -79,9 +91,11 @@ fun StatisticsScreen(
.padding(10.dp)
) {
StatisticsLeft(modifier = M.weight(2f), statisticsViewModel, curType, pager) {
curType = it
statisticsViewModel.updateParamsCocoonType(it.sgtypesysid)
pager.refresh()
if (curType.sgtypesysid != it.sgtypesysid) {
curType = it
statisticsViewModel.updateParamsCocoonType(it.sgtypesysid)
pager.refresh()
}
}
StatisticsMain(
modifier = M.weight(8f),
@@ -90,8 +104,6 @@ fun StatisticsScreen(
queryInput,
{
queryInput = it
statisticsViewModel.updateParamsLike(queryInput)
pager.refresh()
},
curType.name,
{
@@ -119,6 +131,7 @@ fun StatisticsLeft(
) {
val daysInfo by statisticsViewModel.daysInfo.collectAsState()
val statistics by statisticsViewModel.statistics.collectAsState()
val dayFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }
Column(
modifier = modifier
@@ -131,10 +144,12 @@ fun StatisticsLeft(
statisticsViewModel.calDate = it
statisticsViewModel.getDaysInfo(it.year.toString(), it.monthValue.toString())
}) { start, end ->
val dateStart = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(start)
val dateEnd = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(end)
statisticsViewModel.refreshDateRange(dateStart to dateEnd)
pager.refresh()
val dateStart = dayFormat.parse(start)
val dateEnd = dayFormat.parse(end)
if (dateStart != null && dateEnd != null) {
statisticsViewModel.refreshDateRange(dateStart to dateEnd)
pager.refresh()
}
}
val dateRange by statisticsViewModel.dateRange.collectAsState()
DateRangePickTextFiled(M.fillMaxWidth(), dateRange = dateRange) {
@@ -67,6 +67,7 @@ fun DatePickerRange(
// 处理拖动选择日期范围
var selectedStartDay by remember { mutableStateOf(-1) }
var selectedEndDay by remember { mutableStateOf(-1) }
val importantDaySet = remember(importantDateInfoList) { importantDateInfoList.toSet() }
val onClickChangeDate: (date: LocalDate) -> Unit = { date ->
currentDate = date
@@ -158,7 +159,7 @@ fun DatePickerRange(
items(currentDate.lengthOfMonth() + 1) { day ->
CalendarDay(
day,
importantDateInfoList.contains(day),
importantDaySet.contains(day),
rangeType = if (day == selectedStartDay)
if (selectedEndDay == -1
|| selectedEndDay == selectedStartDay