再次优化加载性能

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 state = rememberPullToRefreshState()
val pagerSate = info.loadState.append val pagerSate = info.loadState.append
val refreshState = info.loadState.refresh
LaunchedEffect(pagerSate) { LaunchedEffect(pagerSate) {
if (pagerSate is LoadState.NotLoading) { if (pagerSate is LoadState.NotLoading) {
onFinishRefresh() onFinishRefresh()
@@ -272,30 +273,37 @@ fun <T : Any> MyRefreshTableForCard(
} }
} }
) { ) {
LazyVerticalGrid( Box(modifier = M.fillMaxSize()) {
modifier = M.fillMaxSize(), LazyVerticalGrid(
columns = GridCells.Fixed(columns), modifier = M.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(10.dp), columns = GridCells.Fixed(columns),
verticalArrangement = Arrangement.spacedBy(10.dp) horizontalArrangement = Arrangement.spacedBy(10.dp),
) { verticalArrangement = Arrangement.spacedBy(10.dp)
items( ) {
count = info.itemCount, items(
key = info.itemKey { key(it) }, count = info.itemCount,
contentType = info.itemContentType { "paging-card" } key = info.itemKey { key(it) },
) { index -> contentType = info.itemContentType { "paging-card" }
info[index]?.let { lineItem -> ) { index ->
Box { info[index]?.let { lineItem ->
item(lineItem, index) 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") { if (refreshState is LoadState.Loading && info.itemCount == 0) {
when (pagerSate) { Box(modifier = M.fillMaxSize(), contentAlignment = Alignment.Center) {
is LoadState.NotLoading -> if (pagerSate.endOfPaginationReached) ListLoading()
ListNoMore()
is LoadState.Loading -> ListLoading()
is LoadState.Error -> ListError(message = pagerSate.error.message.toString())
} }
} }
} }
@@ -512,6 +520,7 @@ fun <T : Any> MyRefreshTable(
val listState: LazyListState = rememberLazyListState() val listState: LazyListState = rememberLazyListState()
val state = rememberPullToRefreshState() val state = rememberPullToRefreshState()
val pagerSate = info.loadState.append val pagerSate = info.loadState.append
val refreshState = info.loadState.refresh
LaunchedEffect(pagerSate) { LaunchedEffect(pagerSate) {
if (pagerSate is LoadState.NotLoading) { if (pagerSate is LoadState.NotLoading) {
onFinishRefresh() onFinishRefresh()
@@ -553,8 +562,9 @@ fun <T : Any> MyRefreshTable(
} }
} }
) { ) {
var selectIndex by rememberSaveable { mutableStateOf(-1) } Box(modifier = M.fillMaxSize()) {
LazyColumn(state = listState) { var selectIndex by rememberSaveable { mutableStateOf(-1) }
LazyColumn(state = listState) {
items( items(
count = info.itemCount, count = info.itemCount,
key = info.itemKey { key(it) }, key = info.itemKey { key(it) },
@@ -647,6 +657,12 @@ fun <T : Any> MyRefreshTable(
is LoadState.Error -> ListError(message = pagerSate.error.message.toString()) 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, onUI: (T) -> Unit,
) { ) {
viewModelScope.launch { viewModelScope.launch {
if (showDialog) {
Toasty.showLoadingDialog(loadingTips)
}
val result = kotlin.runCatching { val result = kotlin.runCatching {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if (showDialog) {
Toasty.showLoadingDialog(loadingTips)
}
onIO() onIO()
} }
} }
@@ -67,33 +68,31 @@ open class BaseViewModel : ViewModel() {
hideLoadingDialog() hideLoadingDialog()
} }
withContext(Dispatchers.Main) { result.onSuccess { data ->
result.onSuccess { data -> onUI(data)
onUI(data) }.onFailure { exception ->
}.onFailure { exception -> // ✅ 如果是协程取消,不处理,只记录日志
// ✅ 如果是协程取消,不处理,只记录日志 if (exception is CancellationException || exception.cause is SocketException && exception.cause?.message?.contains(
if (exception is CancellationException || exception.cause is SocketException && exception.cause?.message?.contains( "Socket closed"
"Socket closed" ) == true
) == true ) {
) { MyLog.test("协程被取消:${exception.javaClass.simpleName}message=${exception.message}")
MyLog.test("协程被取消:${exception.javaClass.simpleName}message=${exception.message}") return@onFailure
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()
} }
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.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment 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.geometry.Rect
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.bbitcn.f8.pad.M 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.screen.view.Toasty
import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel import com.bbitcn.f8.pad.ui.viewmodel.UpdateViewModel
import com.cyberecho.ui.screen.ChatDialog import com.cyberecho.ui.screen.ChatDialog
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.xutils.db.annotation.Column
@Preview(showBackground = true, widthDp = 1280) @Preview(showBackground = true, widthDp = 1280)
@Composable @Composable
@@ -74,9 +75,43 @@ fun MainScreen(
} }
Box(modifier = M.fillMaxWidth(), contentAlignment = Alignment.BottomStart) { Box(modifier = M.fillMaxWidth(), contentAlignment = Alignment.BottomStart) {
var selectedPage by rememberSaveable { mutableStateOf(0) } 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 coroutineScope = rememberCoroutineScope()
val menu by mainViewModel.menu.collectAsState() 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()) { Row(modifier = M.fillMaxWidth()) {
NavigationRail( NavigationRail(
containerColor = MyColors.Black, containerColor = MyColors.Black,
@@ -99,35 +134,37 @@ fun MainScreen(
) )
}, },
selected = selectedPage == index, selected = selectedPage == index,
onClick = { selectedPage = index } onClick = {
) if (selectedPage != index) {
} selectedPage = index
} isSwitchingPage = index !in cachedPages
Box { if (index in cachedPages) {
var index = -1 contentPage = index
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()
} }
} }
} }
} )
}
0 -> HomeScreen(navController) }
1 -> UserScreen(navController) Box(modifier = M.weight(1f).fillMaxSize()) {
2 -> PurchaseScreen(navController, drawerViewModel = drawerViewModel) val selectedMenu = menu.getOrNull(selectedPage)
3 -> FundsScreen(navController, drawerViewModel = drawerViewModel) if (isSwitchingPage && selectedMenu != null && selectedPage !in cachedPages) {
4 -> StatisticsScreen(drawerViewModel = drawerViewModel) MainPageLoading(selectedMenu.title)
5 -> DryCoonScreen(navController, drawerViewModel) } else {
6 -> SettingScreen(navController, updateViewModel = updateViewModel, topInfoViewmodel = topInfoViewmodel){ cachedPages.forEach { page ->
mainViewModel.refreshMenuPermission() 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() val chatDialogData by mainViewModel.chatDialogData.collectAsState()
ChatDialog(chatDialogData) 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.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf 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.common.DatePickerRange
import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
import com.bbitcn.f8.pad.ui.theme.MyColors import com.bbitcn.f8.pad.ui.theme.MyColors
import kotlinx.coroutines.delay
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.LocalDate import java.time.LocalDate
import java.util.Locale import java.util.Locale
@@ -69,8 +71,18 @@ fun StatisticsScreen(
val ticketMoreDialogData by remember { mutableStateOf(TicketMoreDialogData()) } val ticketMoreDialogData by remember { mutableStateOf(TicketMoreDialogData()) }
var curType by remember { mutableStateOf(StatisticsResponse.Data()) } var curType by remember { mutableStateOf(StatisticsResponse.Data()) }
var queryInput by rememberSaveable { mutableStateOf("") } var queryInput by rememberSaveable { mutableStateOf("") }
var queryInputInitialized by rememberSaveable { mutableStateOf(false) }
val pager = statisticsViewModel.statisticsPager.collectAsLazyPagingItems() val pager = statisticsViewModel.statisticsPager.collectAsLazyPagingItems()
val dateRangeSelectDialogData by statisticsViewModel.dateRangeSelectDialogData.collectAsState() val dateRangeSelectDialogData by statisticsViewModel.dateRangeSelectDialogData.collectAsState()
LaunchedEffect(queryInput) {
if (!queryInputInitialized) {
queryInputInitialized = true
return@LaunchedEffect
}
delay(350)
statisticsViewModel.updateParamsLike(queryInput)
pager.refresh()
}
MainFuncFrame { MainFuncFrame {
MyCard(colors = MyColors.LightLightBlueGreen) { MyCard(colors = MyColors.LightLightBlueGreen) {
Row( Row(
@@ -79,9 +91,11 @@ fun StatisticsScreen(
.padding(10.dp) .padding(10.dp)
) { ) {
StatisticsLeft(modifier = M.weight(2f), statisticsViewModel, curType, pager) { StatisticsLeft(modifier = M.weight(2f), statisticsViewModel, curType, pager) {
curType = it if (curType.sgtypesysid != it.sgtypesysid) {
statisticsViewModel.updateParamsCocoonType(it.sgtypesysid) curType = it
pager.refresh() statisticsViewModel.updateParamsCocoonType(it.sgtypesysid)
pager.refresh()
}
} }
StatisticsMain( StatisticsMain(
modifier = M.weight(8f), modifier = M.weight(8f),
@@ -90,8 +104,6 @@ fun StatisticsScreen(
queryInput, queryInput,
{ {
queryInput = it queryInput = it
statisticsViewModel.updateParamsLike(queryInput)
pager.refresh()
}, },
curType.name, curType.name,
{ {
@@ -119,6 +131,7 @@ fun StatisticsLeft(
) { ) {
val daysInfo by statisticsViewModel.daysInfo.collectAsState() val daysInfo by statisticsViewModel.daysInfo.collectAsState()
val statistics by statisticsViewModel.statistics.collectAsState() val statistics by statisticsViewModel.statistics.collectAsState()
val dayFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }
Column( Column(
modifier = modifier modifier = modifier
@@ -131,10 +144,12 @@ fun StatisticsLeft(
statisticsViewModel.calDate = it statisticsViewModel.calDate = it
statisticsViewModel.getDaysInfo(it.year.toString(), it.monthValue.toString()) statisticsViewModel.getDaysInfo(it.year.toString(), it.monthValue.toString())
}) { start, end -> }) { start, end ->
val dateStart = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(start) val dateStart = dayFormat.parse(start)
val dateEnd = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(end) val dateEnd = dayFormat.parse(end)
statisticsViewModel.refreshDateRange(dateStart to dateEnd) if (dateStart != null && dateEnd != null) {
pager.refresh() statisticsViewModel.refreshDateRange(dateStart to dateEnd)
pager.refresh()
}
} }
val dateRange by statisticsViewModel.dateRange.collectAsState() val dateRange by statisticsViewModel.dateRange.collectAsState()
DateRangePickTextFiled(M.fillMaxWidth(), dateRange = dateRange) { DateRangePickTextFiled(M.fillMaxWidth(), dateRange = dateRange) {
@@ -67,6 +67,7 @@ fun DatePickerRange(
// 处理拖动选择日期范围 // 处理拖动选择日期范围
var selectedStartDay by remember { mutableStateOf(-1) } var selectedStartDay by remember { mutableStateOf(-1) }
var selectedEndDay by remember { mutableStateOf(-1) } var selectedEndDay by remember { mutableStateOf(-1) }
val importantDaySet = remember(importantDateInfoList) { importantDateInfoList.toSet() }
val onClickChangeDate: (date: LocalDate) -> Unit = { date -> val onClickChangeDate: (date: LocalDate) -> Unit = { date ->
currentDate = date currentDate = date
@@ -158,7 +159,7 @@ fun DatePickerRange(
items(currentDate.lengthOfMonth() + 1) { day -> items(currentDate.lengthOfMonth() + 1) { day ->
CalendarDay( CalendarDay(
day, day,
importantDateInfoList.contains(day), importantDaySet.contains(day),
rangeType = if (day == selectedStartDay) rangeType = if (day == selectedStartDay)
if (selectedEndDay == -1 if (selectedEndDay == -1
|| selectedEndDay == selectedStartDay || selectedEndDay == selectedStartDay