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