性能优化

This commit is contained in:
BBIT-Kai
2026-05-27 15:10:34 +08:00
parent 2017535f0f
commit 22eeac6c62
26 changed files with 204 additions and 157 deletions
+1
View File
@@ -16,3 +16,4 @@ local.properties
.idea/ .idea/
app/release/ app/release/
app/debug/ app/debug/
.kotlin/
@@ -210,8 +210,7 @@ fun MyTable(
TableContent( TableContent(
verticalPadding = verticalPadding, verticalPadding = verticalPadding,
modifier = M modifier = M
.fillMaxWidth() .fillMaxWidth(),
.animateItem(),
backgroundDeepColor = itIndex % 2 == 0, backgroundDeepColor = itIndex % 2 == 0,
list = items[itIndex].map { it.toString() }.zip(ratio.map { it.toInt() }), list = items[itIndex].map { it.toString() }.zip(ratio.map { it.toInt() }),
) )
@@ -274,9 +273,10 @@ fun <T : Any> MyRefreshTableForCard(
verticalArrangement = Arrangement.spacedBy(10.dp) verticalArrangement = Arrangement.spacedBy(10.dp)
) { ) {
items(count = info.itemCount, key = info.itemKey { key(it) }) { index -> items(count = info.itemCount, key = info.itemKey { key(it) }) { index ->
val lineItem = info[index]!! info[index]?.let { lineItem ->
Box(modifier = M.animateItem()) { Box {
item(lineItem, index) item(lineItem, index)
}
} }
} }
item { item {
@@ -318,7 +318,6 @@ fun <T : Any> MyAnyTable(
val lineItem = info[index] val lineItem = info[index]
Card( Card(
modifier = M modifier = M
.animateItem()
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onLongPress = { onLongPress = {
@@ -422,7 +421,6 @@ fun <T : Any> MyTable2(
Card( Card(
modifier = MD modifier = MD
.animateItem()
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onLongPress = { onLongPress = {
@@ -549,10 +547,9 @@ fun <T : Any> MyRefreshTable(
LazyColumn(state = listState) { LazyColumn(state = listState) {
items(count = info.itemCount, key = info.itemKey { key(it) }) { index -> items(count = info.itemCount, key = info.itemKey { key(it) }) { index ->
// // 每行 // // 每行
val lineItem = info[index]!! val lineItem = info[index] ?: return@items
Card( Card(
modifier = MD modifier = MD
.animateItem()
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onLongPress = { onLongPress = {
@@ -11,19 +11,18 @@ abstract class BasePagingSource<T : Any, R : Any>(
val apiService = RetrofitClient.apiInterface() val apiService = RetrofitClient.apiInterface()
abstract suspend fun fetchData(pageInfoJsonStr: String, requestData: R): List<T> // 返回 T 类型的列表 abstract suspend fun fetchData(pageInfoJsonStr: String, requestData: R): List<T>
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
return try { return try {
val page = params.key ?: 1 val page = params.key ?: 1
val pageSize = params.loadSize val pageSize = params.loadSize
val response = fetchData(PageInfo(page, pageSize).toJson(), requestData) val response = fetchData(PageInfo(page, pageSize).toJson(), requestData)
// 计算上一页和下一页的 key
val prevKey = if (page > 1) page - 1 else null val prevKey = if (page > 1) page - 1 else null
val nextKey = if (response.size < pageSize) null else page + 1 val nextKey = if (response.size < pageSize) null else page + 1
LoadResult.Page( LoadResult.Page(
data = response, // 直接返回数据列表 data = response,
prevKey = prevKey, prevKey = prevKey,
nextKey = nextKey nextKey = nextKey
) )
@@ -32,10 +31,10 @@ abstract class BasePagingSource<T : Any, R : Any>(
} }
} }
/** override fun getRefreshKey(state: PagingState<Int, T>): Int? {
* 这里可以根据当前的分页位置来返回刷新key return state.anchorPosition?.let { anchorPosition ->
*/ state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
override fun getRefreshKey(state: PagingState<Int, T>): Int { ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
return 1 // 默认返回1,具体实现可根据需求调整 }
} }
} }
@@ -246,10 +246,8 @@ fun MainFuncFrame(
.fillMaxSize() .fillMaxSize()
.background(Color.White) .background(Color.White)
) { ) {
AsyncImage( Image(
model = ImageRequest.Builder(LocalContext.current) painter = painterResource(R.drawable.bg),
.data(R.drawable.bg)
.build(),
contentDescription = "Background", contentDescription = "Background",
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
@@ -568,7 +566,6 @@ fun VerticalTabs(
tabs.forEachIndexed() { index, tab -> tabs.forEachIndexed() { index, tab ->
Row( Row(
modifier = M modifier = M
.animateItem()
.clickable { .clickable {
onTabSelected(index) onTabSelected(index)
} }
@@ -37,7 +37,9 @@ class TopInfoViewModel : BaseViewModel() {
refreshBatteryInfo() refreshBatteryInfo()
refreshTitle() refreshTitle()
//循环更新TopBar UI //循环更新TopBar UI
pollingTask.startPollingTaskOnIOThread(RxTag.BAR_UI, 5) { val dateFormat = SimpleDateFormat("yyyy/MM/dd \n")
val timeFormat = SimpleDateFormat("HH:mm")
pollingTask.startPollingTaskOnIOThread(RxTag.BAR_UI, 60) {
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
val lunarCalender: LunarCalendar = LunarCalendar.obtainCalendar( val lunarCalender: LunarCalendar = LunarCalendar.obtainCalendar(
calendar[Calendar.YEAR], calendar[Calendar.YEAR],
@@ -45,11 +47,11 @@ class TopInfoViewModel : BaseViewModel() {
calendar[Calendar.DAY_OF_MONTH] calendar[Calendar.DAY_OF_MONTH]
) )
_date.update { _date.update {
SimpleDateFormat("yyyy/MM/dd \n").format(Date()) + dateFormat.format(Date()) +
(lunarCalender.getLunarMonth() + "" + lunarCalender.getLunarDay()) (lunarCalender.getLunarMonth() + "" + lunarCalender.getLunarDay())
} }
_time.update { _time.update {
SimpleDateFormat("HH:mm").format(Date()) timeFormat.format(Date())
} }
} }
} }
@@ -107,7 +107,7 @@ fun AddEditUserDialog(info: AddEditUserDialogData) {
LazyColumn(modifier = M.fillMaxSize()) { LazyColumn(modifier = M.fillMaxSize()) {
item { item {
Row( Row(
modifier = M.fillMaxWidth().animateItem(), modifier = M.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(10.dp) horizontalArrangement = Arrangement.spacedBy(10.dp)
) { ) {
Column(modifier = M.weight(1f)) { Column(modifier = M.weight(1f)) {
@@ -99,7 +99,7 @@ fun AddWeightDialog(info: AddWeightDialogData) {
LazyColumn { LazyColumn {
item { item {
Column( Column(
modifier = M.fillMaxSize().animateItem(), modifier = M.fillMaxSize(),
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp) verticalArrangement = Arrangement.spacedBy(10.dp)
) { ) {
@@ -203,7 +203,7 @@ fun Messages(
) { ) {
items(chatRecords) { items(chatRecords) {
Message( Message(
modifier = M.animateItem(), modifier = M,
msg = it, msg = it,
isEndMessageByAuthor = true, isEndMessageByAuthor = true,
isNewMessageByAuthor = true isNewMessageByAuthor = true
@@ -133,7 +133,7 @@ fun PriceDialog(info: PriceDialogData) {
var priceT by rememberSaveable { mutableStateOf(item.price.toString()) } var priceT by rememberSaveable { mutableStateOf(item.price.toString()) }
val moneySum = ((priceT.toDoubleOrNull() ?: 0.0) * item.jweightSum) val moneySum = ((priceT.toDoubleOrNull() ?: 0.0) * item.jweightSum)
PriceTableContent( PriceTableContent(
M.animateItem(), M,
index % 2 == 0, index % 2 == 0,
item.sgTypeName, item.sgTypeName,
item.weightCount.toString(), item.weightCount.toString(),
@@ -113,7 +113,7 @@ fun WaterCutRecordDialog(info: WaterCutRecordDialogData) {
LazyColumn { LazyColumn {
items(count = items.size) { it -> items(count = items.size) { it ->
TableContent( TableContent(
modifier = M.fillMaxWidth().animateItem(), modifier = M.fillMaxWidth(),
backgroundDeepColor = it % 2 == 0, backgroundDeepColor = it % 2 == 0,
list = items[it].map { it.toString() }.zip(ratio.map { it.toInt() }), list = items[it].map { it.toString() }.zip(ratio.map { it.toInt() }),
verticalPadding = 15.dp, verticalPadding = 15.dp,
@@ -94,7 +94,7 @@ fun DryCocoonFilterDialog(
} }
} }
items(tagIds.toList()) { items(tagIds.toList()) {
PackageLossInfo(M.animateItem(), it) PackageLossInfo(M, it)
} }
} }
VerticalDivider(modifier = M.padding(horizontal = 5.dp)) VerticalDivider(modifier = M.padding(horizontal = 5.dp))
@@ -123,7 +123,7 @@ fun DryCocoonFilterDialog(
HorizontalDivider(modifier = M.padding(vertical = 5.dp)) HorizontalDivider(modifier = M.padding(vertical = 5.dp))
} }
items(forceItems.toList()) { items(forceItems.toList()) {
PackageLossInfo(M.animateItem(), it) PackageLossInfo(M, it)
} }
} }
} }
@@ -138,7 +138,7 @@ fun DryCocoonLossDialog(
} }
} }
items(tagIds, key = { it }) { tag -> items(tagIds, key = { it }) { tag ->
PackageLossInfo(M.animateItem(),tag) PackageLossInfo(M,tag)
} }
} }
} }
@@ -120,7 +120,7 @@ fun DryCocoonLossDialogInOut(
} }
} }
items(tagIds, key = { it }) { tag -> items(tagIds, key = { it }) { tag ->
PackageLossInfo(M.animateItem(), tag) PackageLossInfo(M, tag)
} }
} }
} }
@@ -16,7 +16,6 @@ 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.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.DatePickerDefaults
@@ -32,9 +31,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDateRangePickerState import androidx.compose.material3.rememberDateRangePickerState
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
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -51,6 +52,7 @@ import com.bbitcn.f8.pad.base.MainFuncFrame
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.bbitcn.f8.pad.R import com.bbitcn.f8.pad.R
import com.bbitcn.f8.pad.base.DateRangePickTextFiled import com.bbitcn.f8.pad.base.DateRangePickTextFiled
@@ -75,6 +77,7 @@ import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
import com.bbitcn.f8.pad.ui.screen.view.drawer.StateChartsOnclick import com.bbitcn.f8.pad.ui.screen.view.drawer.StateChartsOnclick
import com.bbitcn.f8.pad.ui.theme.MyColors import com.bbitcn.f8.pad.ui.theme.MyColors
import com.bbitcn.f8.pad.utils.TimeUtils import com.bbitcn.f8.pad.utils.TimeUtils
import kotlinx.coroutines.delay
import java.util.Date import java.util.Date
/** /**
@@ -127,6 +130,10 @@ fun PurchaseScreen(
) )
info.refresh() info.refresh()
} }
LaunchedEffect(queryInput) {
delay(350)
updateParams()
}
MainFuncFrame { MainFuncFrame {
MyCard { MyCard {
Column(modifier = M.padding(15.dp)) { Column(modifier = M.padding(15.dp)) {
@@ -170,8 +177,14 @@ fun PurchaseScreen(
Spacer(modifier = M.weight(1f)) Spacer(modifier = M.weight(1f))
} }
DateRangePickTextFiled(dateRange = queryDateRange) { DateRangePickTextFiled(dateRange = queryDateRange) {
purchaseViewModel.showDateRangeSelectDialog{ purchaseViewModel.showDateRangeSelectDialog { range ->
updateParams() purchaseViewModel.updateParams(
range.first,
range.second,
queryInput,
queryType
)
info.refresh()
} }
} }
QueryTextField( QueryTextField(
@@ -181,7 +194,6 @@ fun PurchaseScreen(
text = queryInput text = queryInput
) { ) {
queryInput = it queryInput = it
updateParams()
} }
} }
if (!isLandscape()) { if (!isLandscape()) {
@@ -216,7 +228,7 @@ fun PurchaseScreen(
} }
} }
} }
InfoList(purchaseViewModel) { InfoList(purchaseViewModel, info) {
drawerViewModel.openPurchaseDetailDrawer( drawerViewModel.openPurchaseDetailDrawer(
info = it, info = it,
StateChartsOnclick(pricing = { StateChartsOnclick(pricing = {
@@ -311,6 +323,7 @@ fun DateRangePickerModal(
@Composable @Composable
fun InfoList( fun InfoList(
purchaseViewModel: PurchaseViewModel, purchaseViewModel: PurchaseViewModel,
info: LazyPagingItems<PurchaseDataResponse.Data>,
onClick: (info: PurchaseDataResponse.Data) -> Unit onClick: (info: PurchaseDataResponse.Data) -> Unit
) { ) {
// 获取当前设备的屏幕配置,比如方向 // 获取当前设备的屏幕配置,比如方向
@@ -322,10 +335,9 @@ fun InfoList(
} else { } else {
4 // 横屏显示四列 4 // 横屏显示四列
} }
val info = purchaseViewModel.infoPager.collectAsLazyPagingItems()
val myPager = purchaseViewModel.infoMyPager val myPager = purchaseViewModel.infoMyPager
val isRefreshing by myPager.listIsRefreshing.collectAsState() val isRefreshing by myPager.listIsRefreshing.collectAsState()
var selectIndex by rememberSaveable { mutableStateOf(-1) } var selectId by rememberSaveable { mutableStateOf("") }
MyRefreshTableForCard( MyRefreshTableForCard(
modifier = M modifier = M
.fillMaxWidth() .fillMaxWidth()
@@ -338,9 +350,9 @@ fun InfoList(
}, },
columns = columns, columns = columns,
item = { data, index -> item = { data, index ->
InfoItem(isSelect = index == selectIndex, data = data) { InfoItem(isSelect = data.czSysid == selectId, data = data) {
onClick(data) onClick(data)
selectIndex = index selectId = data.czSysid
} }
} }
) )
@@ -352,6 +364,10 @@ fun InfoItem(
data: PurchaseDataResponse.Data, data: PurchaseDataResponse.Data,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val visibleItems = remember(data.chengZhongItemSumList) {
data.chengZhongItemSumList.take(2)
}
val hiddenCount = data.chengZhongItemSumList.size - visibleItems.size
MyCard( MyCard(
radius = 7.dp, radius = 7.dp,
elevation = 0.dp, elevation = 0.dp,
@@ -401,11 +417,10 @@ fun InfoItem(
Pair("净重", 1), Pair("单价", 1) Pair("净重", 1), Pair("单价", 1)
), isSelect ), isSelect
) )
LazyColumn(modifier = M.height(60.dp)) { Column(modifier = M.height(60.dp)) {
items(count = data.chengZhongItemSumList.size) { index -> visibleItems.forEach { item ->
val item = data.chengZhongItemSumList[index]
TableContent( TableContent(
M.fillMaxWidth().animateItem(), isSelect, M.fillMaxWidth(), isSelect,
listOf( listOf(
Pair(item.sgTypeName, 1), Pair(item.sgTypeName, 1),
Pair(item.mweightSum.toString(), 1), Pair(item.mweightSum.toString(), 1),
@@ -416,6 +431,16 @@ fun InfoItem(
), 3.5.dp ), 3.5.dp
) )
} }
if (hiddenCount > 0) {
Text(
text = "+$hiddenCount",
modifier = M
.fillMaxWidth()
.padding(top = 2.dp, end = 8.dp),
color = if (isSelect) MyColors.BlueGreen else MyColors.Gray,
fontSize = MaterialTheme.typography.labelSmall.fontSize
)
}
} }
StateList( StateList(
data.ispPicing, data.isKouPiing, data.ispPicing, data.isKouPiing,
@@ -78,7 +78,7 @@ fun StatisticsScreen(
.fillMaxSize() .fillMaxSize()
.padding(10.dp) .padding(10.dp)
) { ) {
StatisticsLeft(modifier = M.weight(2f), statisticsViewModel, curType) { StatisticsLeft(modifier = M.weight(2f), statisticsViewModel, curType, pager) {
curType = it curType = it
statisticsViewModel.updateParamsCocoonType(it.sgtypesysid) statisticsViewModel.updateParamsCocoonType(it.sgtypesysid)
pager.refresh() pager.refresh()
@@ -114,11 +114,11 @@ fun StatisticsLeft(
modifier: Modifier, modifier: Modifier,
statisticsViewModel: StatisticsViewModel, statisticsViewModel: StatisticsViewModel,
selectedTabIndex: StatisticsResponse.Data, selectedTabIndex: StatisticsResponse.Data,
pager: LazyPagingItems<StatisticsListResponse.Data>,
onFilterSelectedTabChanged: (StatisticsResponse.Data) -> Unit onFilterSelectedTabChanged: (StatisticsResponse.Data) -> Unit
) { ) {
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 pager = statisticsViewModel.statisticsPager.collectAsLazyPagingItems()
Column( Column(
modifier = modifier modifier = modifier
@@ -47,6 +47,7 @@ import com.bbitcn.f8.pad.base.MainFuncFrame
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.bbitcn.f8.pad.R import com.bbitcn.f8.pad.R
import com.bbitcn.f8.pad.base.AssistChipFilter import com.bbitcn.f8.pad.base.AssistChipFilter
@@ -58,6 +59,7 @@ import com.bbitcn.f8.pad.base.MyRefreshTable
import com.bbitcn.f8.pad.base.MyTableData import com.bbitcn.f8.pad.base.MyTableData
import com.bbitcn.f8.pad.base.QueryTextField import com.bbitcn.f8.pad.base.QueryTextField
import com.bbitcn.f8.pad.base.isLandscape import com.bbitcn.f8.pad.base.isLandscape
import com.bbitcn.f8.pad.model.net.response.UserDataResponse
import com.bbitcn.f8.pad.ui.theme.MyColors import com.bbitcn.f8.pad.ui.theme.MyColors
import com.bbitcn.f8.pad.utils.MyUtil import com.bbitcn.f8.pad.utils.MyUtil
import com.bbitcn.f8.pad.utils.TimeUtils import com.bbitcn.f8.pad.utils.TimeUtils
@@ -82,11 +84,12 @@ fun UserScreen(
userViewModel: UserViewModel = viewModel() userViewModel: UserViewModel = viewModel()
) { ) {
val treeData by userViewModel.treeData.collectAsState() val treeData by userViewModel.treeData.collectAsState()
val userData = userViewModel.usersInfoPager.collectAsLazyPagingItems()
MainFuncFrame { MainFuncFrame {
if (isLandscape()) { if (isLandscape()) {
UserScreenInLandscape(navController, userViewModel, treeData) UserScreenInLandscape(navController, userViewModel, treeData, userData)
} else { } else {
UserScreenInPortrait(navController, userViewModel, treeData) UserScreenInPortrait(navController, userViewModel, treeData, userData)
} }
} }
} }
@@ -95,7 +98,8 @@ fun UserScreen(
fun UserScreenInPortrait( fun UserScreenInPortrait(
navController: NavController, navController: NavController,
userViewModel: UserViewModel, userViewModel: UserViewModel,
treeData: List<Pair<Pair<String, Int>, Any>> treeData: List<Pair<Pair<String, Int>, Any>>,
userData: LazyPagingItems<UserDataResponse.Data>
) { ) {
Column( Column(
modifier = M.fillMaxSize() modifier = M.fillMaxSize()
@@ -105,12 +109,12 @@ fun UserScreenInPortrait(
.weight(2f) .weight(2f)
.padding(bottom = 15.dp) .padding(bottom = 15.dp)
) { ) {
CollapsibleList(userViewModel, treeData) CollapsibleList(userViewModel, treeData, userData)
} }
MyInfoCard( MyInfoCard(
modifier = M.weight(8f) modifier = M.weight(8f)
) { ) {
UserManageList(navController, userViewModel) UserManageList(navController, userViewModel, userData)
} }
} }
} }
@@ -119,7 +123,8 @@ fun UserScreenInPortrait(
fun UserScreenInLandscape( fun UserScreenInLandscape(
navController: NavController, navController: NavController,
userViewModel: UserViewModel, userViewModel: UserViewModel,
treeData: List<Pair<Pair<String, Int>, Any>> treeData: List<Pair<Pair<String, Int>, Any>>,
userData: LazyPagingItems<UserDataResponse.Data>
) { ) {
var leftWeight by rememberSaveable { mutableStateOf(2.5f) } var leftWeight by rememberSaveable { mutableStateOf(2.5f) }
var rightWeight by rememberSaveable { mutableStateOf(7.5f) } var rightWeight by rememberSaveable { mutableStateOf(7.5f) }
@@ -131,7 +136,7 @@ fun UserScreenInLandscape(
.weight(leftWeight) // 使用动态权重 .weight(leftWeight) // 使用动态权重
.fillMaxHeight() .fillMaxHeight()
) { ) {
CollapsibleList(userViewModel, treeData) CollapsibleList(userViewModel, treeData, userData)
} }
Box( Box(
modifier = M modifier = M
@@ -160,15 +165,18 @@ fun UserScreenInLandscape(
.weight(rightWeight) // 使用动态权重 .weight(rightWeight) // 使用动态权重
.fillMaxHeight() .fillMaxHeight()
) { ) {
UserManageList(navController, userViewModel) UserManageList(navController, userViewModel, userData)
} }
} }
} }
@Composable @Composable
fun CollapsibleList(userViewModel: UserViewModel, listData: List<Pair<Pair<String, Int>, Any>>) { fun CollapsibleList(
userViewModel: UserViewModel,
listData: List<Pair<Pair<String, Int>, Any>>,
pager: LazyPagingItems<UserDataResponse.Data>
) {
val queryInput by userViewModel.areaLike.collectAsState() val queryInput by userViewModel.areaLike.collectAsState()
val pager = userViewModel.usersInfoPager.collectAsLazyPagingItems()
Column { Column {
Row( Row(
modifier = M modifier = M
@@ -194,6 +202,7 @@ fun CollapsibleList(userViewModel: UserViewModel, listData: List<Pair<Pair<Strin
items(listData) { item -> items(listData) { item ->
CollapsibleItem( CollapsibleItem(
userViewModel, userViewModel,
pager,
item = item, item = item,
currentLevel = 1, currentLevel = 1,
needExpand = queryInput != "" needExpand = queryInput != ""
@@ -206,6 +215,7 @@ fun CollapsibleList(userViewModel: UserViewModel, listData: List<Pair<Pair<Strin
@Composable @Composable
fun CollapsibleItem( fun CollapsibleItem(
userViewModel: UserViewModel, userViewModel: UserViewModel,
pager: LazyPagingItems<UserDataResponse.Data>,
item: Pair<Pair<String, Int>, Any>, item: Pair<Pair<String, Int>, Any>,
currentLevel: Int, currentLevel: Int,
xian: String = "", xian: String = "",
@@ -225,7 +235,6 @@ fun CollapsibleItem(
expandedIndex = if (needExpand) 0 else -1 expandedIndex = if (needExpand) 0 else -1
} }
} }
val pager = userViewModel.usersInfoPager.collectAsLazyPagingItems()
// 被选中的项 // 被选中的项
val xianTmp = if (currentLevel == 1) titleAndCount.first else xian val xianTmp = if (currentLevel == 1) titleAndCount.first else xian
val xiangTmp = if (currentLevel == 2) titleAndCount.first else xiang val xiangTmp = if (currentLevel == 2) titleAndCount.first else xiang
@@ -327,6 +336,7 @@ fun CollapsibleItem(
val subPair = subItem.key to subItem.value val subPair = subItem.key to subItem.value
CollapsibleItem( CollapsibleItem(
userViewModel, userViewModel,
pager,
subPair as Pair<Pair<String, Int>, Any>, subPair as Pair<Pair<String, Int>, Any>,
currentLevel + 1, currentLevel + 1,
xianTmp, xianTmp,
@@ -407,10 +417,13 @@ fun CollapsibleItem(
} }
@Composable @Composable
fun UserManageList(navController: NavController, userViewModel: UserViewModel) { fun UserManageList(
navController: NavController,
userViewModel: UserViewModel,
userData: LazyPagingItems<UserDataResponse.Data>
) {
var queryInput by rememberSaveable { mutableStateOf("") } var queryInput by rememberSaveable { mutableStateOf("") }
val areaFilter by userViewModel.areaFilter.collectAsState() val areaFilter by userViewModel.areaFilter.collectAsState()
val userData = userViewModel.usersInfoPager.collectAsLazyPagingItems()
val onFilterLikeChanged: (String) -> Unit = { val onFilterLikeChanged: (String) -> Unit = {
queryInput = it queryInput = it
userViewModel.updateParamsLike(it) userViewModel.updateParamsLike(it)
@@ -176,7 +176,7 @@ fun AddUserBaseInfo(
LazyColumn(modifier = M.padding(10.dp)) { LazyColumn(modifier = M.padding(10.dp)) {
item { item {
BigButton( BigButton(
modifier = M.padding(top = 15.dp).animateItem(), modifier = M.padding(top = 15.dp),
text = "保存", text = "保存",
isLight = true isLight = true
) { ) {
@@ -213,7 +213,7 @@ fun AddUSerExtendInfo(addUserViewModel: AddUserViewModel) {
val userExtendList by addUserViewModel.userExtendList.collectAsState() val userExtendList by addUserViewModel.userExtendList.collectAsState()
LazyColumn { LazyColumn {
item { item {
Column(modifier = M.padding(top = 5.dp).animateItem()) { Column(modifier = M.padding(top = 5.dp)) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(5.dp), horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -260,8 +260,7 @@ fun WeightList(weightViewModel: WeightViewModel) {
val item = detailList[it] val item = detailList[it]
TableContent( TableContent(
modifier = M modifier = M
.fillMaxWidth() .fillMaxWidth(),
.animateItem(),
backgroundDeepColor = it % 2 == 0, backgroundDeepColor = it % 2 == 0,
list = listOf( list = listOf(
item.weighingTimes.toString() to 1, item.weighingTimes.toString() to 1,
@@ -457,7 +456,7 @@ fun WeightRight(navController: NavController, weightViewModel: WeightViewModel)
verticalArrangement = Arrangement.spacedBy(10.dp) verticalArrangement = Arrangement.spacedBy(10.dp)
) { ) {
items(kindsInfo) { items(kindsInfo) {
AddKindsButton(M.animateItem(), curWeight, weightViewModel, it, errMsg) AddKindsButton(M, curWeight, weightViewModel, it, errMsg)
} }
} }
Row( Row(
@@ -389,7 +389,6 @@ fun ReaderSerialPort(
items(labelList) { tag -> items(labelList) { tag ->
Text( Text(
modifier = M modifier = M
.animateItem()
.fillMaxWidth() .fillMaxWidth()
.padding(10.dp), .padding(10.dp),
text = tag, text = tag,
@@ -565,7 +564,6 @@ fun ConnectBluetooth(bluetooth: MyBlueTooth) {
println("bluetooth.device.value?.address: ${bluetooth.device.value?.address}") println("bluetooth.device.value?.address: ${bluetooth.device.value?.address}")
Row( Row(
modifier = M modifier = M
.animateItem()
.fillMaxWidth() .fillMaxWidth()
.clickable { bluetooth.connect(device.address) }, .clickable { bluetooth.connect(device.address) },
) { ) {
@@ -141,7 +141,7 @@ fun TicketForPurchase(
) )
LazyColumn(modifier = M.weight(1f)) { LazyColumn(modifier = M.weight(1f)) {
items(info.chengZhongItemSumList) { items(info.chengZhongItemSumList) {
SilkKindCard(M.animateItem(),it) SilkKindCard(M,it)
} }
} }
Column { Column {
@@ -1,11 +1,8 @@
package com.bbitcn.f8.pad.utils.network package com.bbitcn.f8.pad.utils.network
import com.bbitcn.f8.pad.R
import com.bbitcn.f8.pad.model.net.response.CommonResponse import com.bbitcn.f8.pad.model.net.response.CommonResponse
import com.bbitcn.f8.pad.ui.screen.view.Toasty import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.utils.AudioPlayer
import com.bbitcn.f8.pad.utils.MMKVUtil import com.bbitcn.f8.pad.utils.MMKVUtil
import com.bbitcn.f8.pad.utils.TTSManager
import com.bbitcn.f8.pad.utils.global.RxTag import com.bbitcn.f8.pad.utils.global.RxTag
import com.bbitcn.f8.pad.utils.log.MyLog import com.bbitcn.f8.pad.utils.log.MyLog
import com.google.gson.Gson import com.google.gson.Gson
@@ -14,7 +11,6 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Protocol import okhttp3.Protocol
@@ -23,7 +19,6 @@ import okhttp3.ResponseBody
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.io.IOException
import java.net.ConnectException import java.net.ConnectException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
@@ -34,50 +29,43 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object RetrofitClient { object RetrofitClient {
const val BASE_URL = "http://f8.api.app.bbitcn.cn/" // 生产环境 const val BASE_URL = "http://f8.api.app.bbitcn.cn/"
// const val BASE_URL = "http://172.20.50.11:5291/" //何 工厂 private const val ERROR_BODY_PEEK_BYTES = 64 * 1024L
// const val BASE_URL = "http://10.0.4.30:5291/" //何 private const val ENABLE_BODY_LOG = false
// const val BASE_URL = "http://10.0.4.50:5291/" //罗
// const val BASE_URL = "http://10.0.4.68:5291/" //孔
val loggingInterceptor = HttpLoggingInterceptor().apply { val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY level = if (ENABLE_BODY_LOG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
} }
val errorInterceptor = Interceptor { chain -> val errorInterceptor = Interceptor { chain ->
// 获取原始的响应
try { try {
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
// 先读取响应体的内容,但不消耗流 if (response.code !in listOf(200, 401)) {
val responseBody = response.body val content = response.peekBody(ERROR_BODY_PEEK_BYTES).string()
val content = responseBody?.string() // 这里消耗了响应体,但我们要重新创建响应体 runCatching {
Gson().fromJson(content, CommonResponse::class.java)
// 将响应体内容保存到新的 ResponseBody }.onSuccess { commonResponse ->
val newResponseBody = ResponseBody.create(responseBody?.contentType(), content ?: "") Toasty.showTipsDialog("网络请求错误:${commonResponse.msg}")
if (commonResponse.code == 401 && MMKVUtil.get(RxTag.REFRESH_TOKEN).isEmpty()) {
// 重新构造响应,避免影响后续的流操作 Toasty.loginExpired()
val newResponse = response.newBuilder() }
.body(newResponseBody) // 替换成新的响应体 }.onFailure {
.build() Toasty.showTipsDialog(response.message)
// 如果响应码不是 200 或 401,显示错误消息
if (newResponse.code !in listOf(200, 401)) {
// todo 如果断网 这里会出现 com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
// 暂定是服务器的断点导致的
val commonResponse = Gson().fromJson(content, CommonResponse::class.java)
Toasty.showTipsDialog("网络请求错误:${commonResponse.msg}")
if (commonResponse.code == 401 && MMKVUtil.get(RxTag.REFRESH_TOKEN).isEmpty()) {
Toasty.loginExpired()
} }
} }
// 返回新的响应
return@Interceptor newResponse return@Interceptor response
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
MyLog.networkError("请求网络时发生异常:${e.message}") MyLog.networkError("请求网络时发生异常:${e.message}")
MyLog.networkError("错误类型:${e.javaClass.simpleName},错误信息:${e.message}") MyLog.networkError("错误类型:${e.javaClass.simpleName},错误信息:${e.message}")
val responseBody = val responseBody =
ResponseBody.create("application/json".toMediaTypeOrNull(), "{}") // 空的 JSON ResponseBody.create("application/json".toMediaTypeOrNull(), "{}")
val fakeResponse: Response = val fakeResponse: Response =
if (e is UnknownHostException || e is SocketTimeoutException || e is ConnectException) { if (e is UnknownHostException || e is SocketTimeoutException || e is ConnectException) {
Response.Builder() Response.Builder()
@@ -100,13 +88,18 @@ object RetrofitClient {
} }
} }
private val retrofit: Retrofit by lazy { createRetrofit() }
private val apiService: ApiService by lazy { retrofit.create(ApiService::class.java) }
@Provides @Provides
@Singleton @Singleton
fun myRetrofit(): Retrofit { fun myRetrofit(): Retrofit = retrofit
private fun createRetrofit(): Retrofit {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(BASE_URL) .baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(// 设置 OkHttpClient .client(
OkHttpClient.Builder() OkHttpClient.Builder()
.addInterceptor { chain -> .addInterceptor { chain ->
val request = chain.request().newBuilder() val request = chain.request().newBuilder()
@@ -114,10 +107,10 @@ object RetrofitClient {
.build() .build()
chain.proceed(request) chain.proceed(request)
} }
.connectTimeout(20, TimeUnit.SECONDS) // 设置连接超时时间 .connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS) // 设置读取超时时间 .readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS) // 设置写入超时时间 .writeTimeout(20, TimeUnit.SECONDS)
.authenticator(TokenAuthenticator()) // 添加401拦截器 .authenticator(TokenAuthenticator())
.addInterceptor(loggingInterceptor) .addInterceptor(loggingInterceptor)
.addInterceptor(errorInterceptor) .addInterceptor(errorInterceptor)
.build() .build()
@@ -127,7 +120,5 @@ object RetrofitClient {
@Provides @Provides
@Singleton @Singleton
fun apiInterface(): ApiService = fun apiInterface(): ApiService = apiService
RetrofitClient_MyRetrofitFactory.myRetrofit().create(ApiService::class.java)
} }
@@ -16,10 +16,14 @@ import javax.inject.Singleton
object RetrofitClientAI { object RetrofitClientAI {
const val AI_BASE_URL = "http://171.212.101.199:13010/" const val AI_BASE_URL = "http://171.212.101.199:13010/"
private val retrofit: Retrofit by lazy { createRetrofit() }
private val apiService: AIApiService by lazy { retrofit.create(AIApiService::class.java) }
@Provides @Provides
@Singleton @Singleton
fun myAIRetrofit(): Retrofit { fun myAIRetrofit(): Retrofit = retrofit
private fun createRetrofit(): Retrofit {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(AI_BASE_URL) .baseUrl(AI_BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
@@ -41,6 +45,6 @@ object RetrofitClientAI {
@Provides @Provides
@Singleton @Singleton
fun aiApiInterface(): AIApiService = myAIRetrofit().create(AIApiService::class.java) fun aiApiInterface(): AIApiService = apiService
} }
@@ -16,10 +16,14 @@ import javax.inject.Singleton
object RetrofitClientFace { object RetrofitClientFace {
const val Face_BASE_URL = "https://aip.baidubce.com/" const val Face_BASE_URL = "https://aip.baidubce.com/"
private val retrofit: Retrofit by lazy { createRetrofit() }
private val apiService: AIApiService by lazy { retrofit.create(AIApiService::class.java) }
@Provides @Provides
@Singleton @Singleton
fun myAIRetrofit(): Retrofit { fun myAIRetrofit(): Retrofit = retrofit
private fun createRetrofit(): Retrofit {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(Face_BASE_URL) .baseUrl(Face_BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
@@ -40,6 +44,6 @@ object RetrofitClientFace {
@Provides @Provides
@Singleton @Singleton
fun faceApiInterface(): AIApiService = myAIRetrofit().create(AIApiService::class.java) fun faceApiInterface(): AIApiService = apiService
} }
@@ -16,10 +16,14 @@ import javax.inject.Singleton
object RetrofitClientIOT { object RetrofitClientIOT {
const val AI_BASE_URL = "https://iot.api.bbitcn.net/" const val AI_BASE_URL = "https://iot.api.bbitcn.net/"
private val retrofit: Retrofit by lazy { createRetrofit() }
private val apiService: ApiServiceIOT by lazy { retrofit.create(ApiServiceIOT::class.java) }
@Provides @Provides
@Singleton @Singleton
fun myAIRetrofit(): Retrofit { fun myAIRetrofit(): Retrofit = retrofit
private fun createRetrofit(): Retrofit {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(AI_BASE_URL) .baseUrl(AI_BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
@@ -41,6 +45,6 @@ object RetrofitClientIOT {
@Provides @Provides
@Singleton @Singleton
fun apiInterface(): ApiServiceIOT = myAIRetrofit().create(ApiServiceIOT::class.java) fun apiInterface(): ApiServiceIOT = apiService
} }
@@ -13,59 +13,72 @@ import okhttp3.Response
import okhttp3.Route import okhttp3.Route
/** /**
* 401 拦截器 * 401 token refresh interceptor.
*/ */
class TokenAuthenticator() : Authenticator { class TokenAuthenticator : Authenticator {
private val lock = Any() // 用于同步刷新 token 的操作 private val lock = Any()
private var isRefreshing = false // 标志:是否正在刷新 token
override fun authenticate(route: Route?, response: Response): Request? { override fun authenticate(route: Route?, response: Response): Request? {
MyLog.network("TokenAuthenticator: authenticate") MyLog.network("TokenAuthenticator: authenticate")
if (response.code == 401) { if (response.code != 401 || responseCount(response) >= 2) {
MyLog.network("401TokenAuthenticator: authenticate") return null
if (MMKVUtil.get(RxTag.REFRESH_TOKEN).isEmpty()) { }
if (response.request.url.encodedPath.contains("RefreshToken", ignoreCase = true)) {
Toasty.loginExpired()
return null
}
synchronized(lock) {
val refreshToken = MMKVUtil.get(RxTag.REFRESH_TOKEN, "")
if (refreshToken.isEmpty()) {
Toasty.loginExpired() Toasty.loginExpired()
return null
} }
synchronized(lock) {
// 如果正在刷新 token,直接返回 null,等待刷新完成 val currentToken = MMKVUtil.get(RxTag.ACCESS_TOKEN, "")
if (isRefreshing) val requestToken = response.request.header("Authorization")?.removePrefix("Bearer ")
return null if (currentToken.isNotEmpty() && requestToken != currentToken) {
isRefreshing = true return response.request.withToken(currentToken)
} }
val newToken = runBlocking { val newToken = runBlocking {
val accessToken = MMKVUtil.get(RxTag.ACCESS_TOKEN, "") MyLog.network("刷新Token: $currentToken, $refreshToken")
val refreshToken = MMKVUtil.get(RxTag.REFRESH_TOKEN, "")
MyLog.network("刷新Token: $accessToken, $refreshToken")
val result = RetrofitClient.apiInterface().refreshToken( val result = RetrofitClient.apiInterface().refreshToken(
RefreshTokenRequest( RefreshTokenRequest(
hardwareid = Global.getDeviceId(), hardwareid = Global.getDeviceId(),
refToken = refreshToken, refToken = refreshToken,
token = accessToken token = currentToken
) )
) )
if (result.code == 1) { if (result.code == 1) {
MMKVUtil.put(RxTag.ACCESS_TOKEN, result.data.accessToken) MMKVUtil.put(RxTag.ACCESS_TOKEN, result.data.accessToken)
result.data.accessToken result.data.accessToken
} else { } else {
// 刷新 token 失败,跳转到登录页面
Toasty.loginExpired() Toasty.loginExpired()
MyLog.networkError("刷新Token失败,跳转到登录页面: ${result.msg}") MyLog.networkError("刷新Token失败,跳转到登录页面: ${result.msg}")
null null
} }
} }
synchronized(lock) { return newToken?.let { response.request.withToken(it) }
isRefreshing = false // 刷新完成,允许其他请求进行刷新
}
return newToken?.let {
response.request.newBuilder()
.header("Authorization", "Bearer $it")
.build()
}
} }
return null }
private fun Request.withToken(token: String): Request {
return newBuilder()
.header("Authorization", "Bearer $token")
.build()
}
private fun responseCount(response: Response): Int {
var count = 1
var prior = response.priorResponse
while (prior != null) {
count++
prior = prior.priorResponse
}
return count
} }
} }
@@ -19,7 +19,7 @@ class MyPager<T : Any, R : Any>(
initialRequestData: R, // 初始的请求数据 initialRequestData: R, // 初始的请求数据
private val initialLoadSize: Int = 12, private val initialLoadSize: Int = 12,
private val pageSize: Int = 12, private val pageSize: Int = 12,
private val enablePlaceholders: Boolean = true private val enablePlaceholders: Boolean = false
) { ) {
// 内部的 MutableStateFlow,用来保存请求数据 // 内部的 MutableStateFlow,用来保存请求数据