优化性能问题;完成需求;重构弹窗

This commit is contained in:
BBIT-Kai
2026-05-28 09:31:06 +08:00
parent 22eeac6c62
commit d1bc715aac
26 changed files with 862 additions and 662 deletions
@@ -21,6 +21,10 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -34,6 +38,8 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.bbitcn.f8.pad.M
import com.bbitcn.f8.pad.MyApp
import com.bbitcn.f8.pad.ui.screen.view.GlobalDialogData
import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.blankj.utilcode.util.ActivityUtils
@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
@@ -68,7 +74,44 @@ fun MyDialog(
onClickOK: () -> Unit = {},
content: @Composable () -> Unit,
) {
MyAnimatedVisibility(showDialog) {
val ownerKey = remember { Any() }
val latestOnDismissRequest = rememberUpdatedState(onDismissRequest)
val latestOnClickOK = rememberUpdatedState(onClickOK)
val latestContent = rememberUpdatedState(content)
LaunchedEffect(showDialog, title, clickOKStr) {
if (showDialog) {
Toasty.showGlobalDialog(
GlobalDialogData(
ownerKey = ownerKey,
title = title,
onDismissRequest = {
latestOnDismissRequest.value()
},
clickOKStr = clickOKStr,
onClickOK = {
latestOnClickOK.value()
},
content = {
latestContent.value()
}
)
)
} else {
Toasty.hideGlobalDialog(ownerKey)
}
}
DisposableEffect(ownerKey) {
onDispose {
Toasty.hideGlobalDialog(ownerKey)
}
}
}
@Composable
fun GlobalDialogHost(data: GlobalDialogData) {
MyAnimatedVisibility(data.showDialog) {
Box(
modifier = M
.fillMaxSize()
@@ -78,8 +121,8 @@ fun MyDialog(
},
contentAlignment = Alignment.Center
) {
BaseDialogFrame(title, {
content()
BaseDialogFrame(data.title, {
data.content()
}) {
Row(modifier = M.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
BigButton(
@@ -87,13 +130,13 @@ fun MyDialog(
.weight(1f)
.widthIn(max = 100.dp)
) {
onDismissRequest()
data.onDismissRequest()
}
// 判断是否有点击事件
if (clickOKStr != "") {
if (data.clickOKStr != "") {
Spacer(modifier = M.width(30.dp))
BigButton(clickOKStr, modifier = M.weight(1f), true) {
onClickOK()
BigButton(data.clickOKStr, modifier = M.weight(1f), true) {
data.onClickOK()
}
}
}
@@ -110,7 +153,7 @@ fun BaseDialogFrame(
) {
MyCard(
modifier = M
.padding(top = 10.dp, bottom = 50.dp, start = 100.dp, end = 100.dp)
.padding(vertical = 40.dp, horizontal = 100.dp)
.noVisualFeedbackClickable { }
.fillMaxSize()
) {
@@ -27,6 +27,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.net.SocketException
import kotlin.coroutines.cancellation.CancellationException
import retrofit2.HttpException
open class BaseViewModel : ViewModel() {
@@ -78,6 +79,11 @@ open class BaseViewModel : ViewModel() {
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)
@@ -208,6 +214,7 @@ open class BaseViewModel : ViewModel() {
override fun onCleared() {
super.onCleared()
pollingTask.stopAllTasks()
taskMap.values.forEach { it.cancel() }
taskMap.clear()
}
@@ -172,13 +172,23 @@ fun MyButton(
onClick: () -> Unit,
) {
Button(
onClick = onClick,
onClick = {
if (enabled) {
onClick()
} else {
Toasty.showToast("正在开发中,敬请期待")
}
},
modifier = modifier,
enabled = enabled,
enabled = true,
interactionSource = interactionSource,
shape = shape,
border = border,
colors = ButtonDefaults.buttonColors(containerColor = colors),
colors = ButtonDefaults.buttonColors(
containerColor = if (enabled) colors else MyColors.Disabled,
disabledContainerColor = MyColors.Disabled,
disabledContentColor = MyColors.Gray
),
contentPadding = contentPadding,
) {
Text(
@@ -42,6 +42,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.bbitcn.f8.pad.M
import com.bbitcn.f8.pad.base.GlobalDialogHost
import com.bbitcn.f8.pad.ui.screen.TopInfoViewModel
import com.bbitcn.f8.pad.ui.screen.LoginScreen
import com.bbitcn.f8.pad.ui.screen.MainScreen
@@ -273,6 +274,7 @@ class MainActivity : ComponentActivity() {
val loadingDialog by Toasty.loadingDialog.collectAsState()
val confirmDialog by Toasty.confirmDialog.collectAsState()
val inputDialog by Toasty.inputDialog.collectAsState()
val globalDialog by Toasty.globalDialog.collectAsState()
TipsDialog(
showDialog = tipsDialog.showDialog,
onDismiss = { Toasty.hideTipsDialog() },
@@ -281,6 +283,7 @@ class MainActivity : ComponentActivity() {
LoadingDialog(loadingDialog)
ConfirmDialog(confirmDialog)
InputDialog(inputDialog)
GlobalDialogHost(globalDialog)
}
}
}
@@ -51,6 +51,7 @@ import com.bbitcn.f8.pad.base.isBluetoothEnabled
import com.bbitcn.f8.pad.receiver.SystemInfoReceiver
import com.bbitcn.f8.pad.ui.screen.view.drawer.DrawerViewModel
import com.bbitcn.f8.pad.ui.theme.MyColors
import com.bbitcn.f8.pad.utils.MyUtil
import com.bbitcn.f8.pad.utils.externalModules.devices.scale.ScaleBT
import com.bbitcn.f8.pad.utils.externalModules.devices.printer.PrinterBT
import com.bbitcn.f8.pad.utils.externalModules.devices.printer.JTPrinterUSB
@@ -88,6 +89,7 @@ fun MyTopBar(
val date by topInfoViewModel.date.collectAsState()
val time by topInfoViewModel.time.collectAsState()
val logoState by topInfoViewModel.logoState.collectAsState()
val versionName = remember { MyUtil.getVersionName() }
DisposableEffect(context) {
systemInfoReceiver.register()
@@ -151,6 +153,7 @@ fun MyTopBar(
)
}, label = "animated content"
) { value ->
Row(verticalAlignment = Alignment.Bottom) {
Text(
text = value.first.second,
M.padding(horizontal = 8.dp),
@@ -158,6 +161,13 @@ fun MyTopBar(
fontWeight = FontWeight.Bold,
fontSize = MaterialTheme.typography.headlineMedium.fontSize
)
Text(
text = "v$versionName",
M.padding(bottom = 2.dp),
color = MyColors.LightGray,
fontSize = MaterialTheme.typography.bodySmall.fontSize
)
}
}
Spacer(modifier = M.weight(1f))
@@ -1,5 +1,7 @@
package com.bbitcn.f8.pad.ui.screen.dialog
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
@@ -28,6 +30,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.bbitcn.f8.pad.M
@@ -46,6 +49,8 @@ data class MessageDialogData(
val time: String = "",
val username: String = "",
val content: String = "",
val title: String = "",
val isWebUrl: Boolean = false,
)
@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
@@ -63,7 +68,7 @@ fun MessagePreview() {
@Composable
fun MessageDialog(info: MessageDialogData) {
MyDialog("查看消息",
MyDialog(info.title.ifEmpty { "查看消息" },
info.showDialog,
onDismissRequest = { info.onDismiss() }
) {
@@ -98,6 +103,23 @@ fun MessageDialog(info: MessageDialogData) {
.padding(30.dp)
.verticalScroll(rememberScrollState()) // 添加滚动支持
) {
if (info.isWebUrl) {
AndroidView(
modifier = M.fillMaxSize(),
factory = { context ->
WebView(context).apply {
webViewClient = WebViewClient()
settings.javaScriptEnabled = true
loadUrl(info.content)
}
},
update = { webView ->
if (webView.url != info.content) {
webView.loadUrl(info.content)
}
}
)
} else {
Text(
text = info.content,
fontSize = MaterialTheme.typography.headlineMedium.fontSize,
@@ -107,4 +129,5 @@ fun MessageDialog(info: MessageDialogData) {
}
}
}
}
}
@@ -48,6 +48,7 @@ import com.bbitcn.f8.pad.base.VipBadge
import com.bbitcn.f8.pad.model.net.request.CocoonTypeTranslateRequest
import com.bbitcn.f8.pad.model.net.response.PurchaseDataResponse
import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.ui.theme.MyColors
import com.bbitcn.f8.pad.utils.MyUtil
import kotlin.collections.map
@@ -157,13 +158,12 @@ fun TicketMoreDialog(
}
}
item {
MyImageButton("茧别转换", R.drawable.ic_ticket_convert) {
MyImageButton("茧别转换", R.drawable.ic_ticket_convert, enabled = false) {
isTranslated = !isTranslated
}
}
item {
MyImageButton("茧票过户", R.drawable.ic_ticket_transfer, true) {
MyImageButton("茧票过户", R.drawable.ic_ticket_transfer, vip = true, enabled = false) {
}
}
item {
@@ -176,13 +176,11 @@ fun TicketMoreDialog(
}
}
item {
MyImageButton("上传图片", R.drawable.ic_upload_pic, true) {
MyImageButton("上传图片", R.drawable.ic_upload_pic, vip = true, enabled = false) {
}
}
item {
MyImageButton("隔日作废", R.drawable.ic_ticket_delete_next_day, true) {
MyImageButton("隔日作废", R.drawable.ic_ticket_delete_next_day, vip = true, enabled = false) {
}
}
}
@@ -238,12 +236,18 @@ fun CocoonTypeTranslate(
}
@Composable
fun MyImageButton(text: String, resId: Int, vip: Boolean = false, onClick: () -> Unit) {
fun MyImageButton(text: String, resId: Int, vip: Boolean = false, enabled: Boolean = true, onClick: () -> Unit) {
Column(
modifier = M
.fillMaxWidth()
.padding(10.dp)
.clickable { onClick() },
.clickable {
if (enabled) {
onClick()
} else {
Toasty.showToast("正在开发中,敬请期待")
}
},
horizontalAlignment = Alignment.CenterHorizontally
) {
if (vip) {
@@ -251,18 +255,21 @@ fun MyImageButton(text: String, resId: Int, vip: Boolean = false, onClick: () ->
Image(
painter = painterResource(id = resId),
contentDescription = text,
modifier = M.size(90.dp)
modifier = M.size(90.dp),
alpha = if (enabled) 1f else 0.35f
)
}
} else {
Image(
painter = painterResource(id = resId),
contentDescription = text,
modifier = M.size(90.dp)
modifier = M.size(90.dp),
alpha = if (enabled) 1f else 0.35f
)
}
Text(
text = text,
color = if (enabled) MyColors.Black else MyColors.Gray,
fontSize = MaterialTheme.typography.headlineMedium.fontSize,
textAlign = TextAlign.Center
)
@@ -153,15 +153,15 @@ fun FundsScreen(
)
}
Spacer(modifier = M.weight(1f))
MyButton(modifier = M.fillMaxWidth(), text = "查询配额") {
MyButton(modifier = M.fillMaxWidth(), text = "查询配额", enabled = false) {
fundsViewModel.showQueryBalanceDialog()
}
VipBadge {
MyButton(modifier = M.fillMaxWidth(), text = "查询余额") {
MyButton(modifier = M.fillMaxWidth(), text = "查询余额", enabled = false) {
fundsViewModel.showQueryBalanceDialog()
}
}
MyButton(modifier = M.fillMaxWidth(), text = "修改支付密码") {
MyButton(modifier = M.fillMaxWidth(), text = "修改支付密码", enabled = false) {
Toasty.showToast("正在开发中,敬请期待!")
}
}
@@ -413,6 +413,7 @@ fun WeatherItem(
@Composable
fun MessageInfo(homeViewModel: HomeViewModel) {
val notices by homeViewModel.notices.collectAsState()
Box(modifier = M.padding(5.dp)) {
Image(
painter = painterResource(id = R.drawable.bg_weather),
@@ -429,14 +430,16 @@ fun MessageInfo(homeViewModel: HomeViewModel) {
})
}
LazyColumn {
items(count = 0) { index ->
items(count = notices.size) { index ->
val notice = notices[index]
MessageItem(
homeViewModel,
true,
true,
"管理员",
"2024-08-02 11:25:32",
"正在开发中"
!homeViewModel.isNoticeRead(notice.id),
notice.sender,
notice.time,
notice.title,
onClick = { homeViewModel.showNoticeDialog(notice) }
)
}
}
@@ -451,12 +454,13 @@ fun MessageItem(
unRead: Boolean,
name: String,
time: String,
message: String
message: String,
onClick: (() -> Unit)? = null
) {
Row(
modifier = M
.clickable {
homeViewModel.showMsgDialog(name, time, message)
onClick?.invoke() ?: homeViewModel.showMsgDialog(name, time, message)
}
.padding(bottom = 5.dp),
verticalAlignment = Alignment.CenterVertically) {
@@ -22,6 +22,14 @@ import java.util.Date
class HomeViewModel : BaseViewModel() {
data class Notice(
val id: String,
val title: String,
val sender: String,
val time: String,
val content: String
)
private val _todayPrice = MutableStateFlow<List<TodayPriceResponse.Data>>(emptyList())
val todayPrice = _todayPrice.asStateFlow()
@@ -37,7 +45,11 @@ class HomeViewModel : BaseViewModel() {
private val _messageDialogData = MutableStateFlow(MessageDialogData())
val messageDialogData: StateFlow<MessageDialogData> = _messageDialogData.asStateFlow()
private val _notices = MutableStateFlow<List<Notice>>(emptyList())
val notices = _notices.asStateFlow()
init {
loadMockNotices()
refreshWeatherInfo(false)
refreshTodayPrice(false)
doInIoThreadNoDialog {
@@ -58,6 +70,47 @@ class HomeViewModel : BaseViewModel() {
}
}
private fun loadMockNotices() {
val readIds = getReadNoticeIds()
_notices.value = listOf(
Notice(
id = "notice_001",
title = "收购系统版本提示",
sender = "系统通知",
time = "2026-05-27 09:30:00",
content = "本次前端已调整农户建档、通知展示和称重信息展示,后端接口将陆续配合。"
),
Notice(
id = "notice_002",
title = "操作指引",
sender = "管理员",
time = "2026-05-27 10:15:00",
content = "https://www.bbitcn.com"
),
Notice(
id = "notice_003",
title = "设备巡检提醒",
sender = "运维",
time = "2026-05-27 11:00:00",
content = "请在每日开秤前检查打印机、电子秤、读卡器连接状态。"
)
).map { it.copy(title = if (readIds.contains(it.id)) it.title else it.title) }
}
fun isNoticeRead(id: String): Boolean = getReadNoticeIds().contains(id)
private fun getReadNoticeIds(): Set<String> {
return MMKVUtil.get("HOME_NOTICE_READ_IDS", "")
.split(",")
.filter { it.isNotBlank() }
.toSet()
}
private fun markNoticeRead(id: String) {
val ids = getReadNoticeIds() + id
MMKVUtil.put("HOME_NOTICE_READ_IDS", ids.joinToString(","))
}
fun refreshAcquireData(
startDate: Date = _dateRange2.value.first,
endDate: Date = _dateRange2.value.second,
@@ -106,6 +159,22 @@ class HomeViewModel : BaseViewModel() {
)
}
fun showNoticeDialog(notice: Notice) {
markNoticeRead(notice.id)
_notices.update { it.toList() }
_messageDialogData.value = MessageDialogData(
showDialog = true,
username = notice.sender,
time = notice.time,
content = notice.content,
title = notice.title,
isWebUrl = notice.content.startsWith("http://") || notice.content.startsWith("https://"),
onDismiss = {
_messageDialogData.update { it.copy(showDialog = false) }
}
)
}
fun logout(onFinished: () -> Unit) {
// 退出登录
doInIoThreadThenUI(loadingTips = "正在退出登录", onIO = {
@@ -118,6 +118,7 @@ fun PurchaseScreen(
var queryInput by rememberSaveable { mutableStateOf("") }
// 筛选条件3:查询类型
var queryType by rememberSaveable { mutableStateOf(0) }
var queryInputInitialized by rememberSaveable { mutableStateOf(false) }
val info = purchaseViewModel.infoPager.collectAsLazyPagingItems()
// 查询方法
@@ -131,6 +132,10 @@ fun PurchaseScreen(
info.refresh()
}
LaunchedEffect(queryInput) {
if (!queryInputInitialized) {
queryInputInitialized = true
return@LaunchedEffect
}
delay(350)
updateParams()
}
@@ -65,7 +65,10 @@ class PurchaseViewModel : BaseViewModel() {
init {
doInIoThreadNoDialog {
// 获取一个月前的日期
updateParams(getRecentMonthsDate(1), Date(), "", 0)
val start = getRecentMonthsDate(1)
val end = Date()
_dateRange.value = start to end
updateParams(start, end, "", 0)
}
}
@@ -167,7 +167,7 @@ fun StatisticsLeft(
}
Spacer(modifier = M.weight(1f))
VipBadge {
MyButton(modifier = M.fillMaxWidth(), text = "发送短信") {
MyButton(modifier = M.fillMaxWidth(), text = "发送短信", enabled = false) {
Toasty.showToast("正在开发中,敬请期待")
}
}
@@ -1,77 +1,50 @@
package com.bbitcn.f8.pad.ui.screen.mainFunc
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.rememberCoroutineScope
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bbitcn.f8.pad.M
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.M
import com.bbitcn.f8.pad.base.AssistChipFilter
import com.bbitcn.f8.pad.base.InfoText
import com.bbitcn.f8.pad.base.MainFuncFrame
import com.bbitcn.f8.pad.base.MyButton
import com.bbitcn.f8.pad.base.MyCard
import com.bbitcn.f8.pad.base.MyDialog
import com.bbitcn.f8.pad.base.MyInfoCard
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
import com.blankj.utilcode.util.StringUtils
import kotlinx.coroutines.launch
/**
*
* @Description 主功能-预约售茧
* @Author DuanKaiji
* @CreateTime 2024年08月02日 11:25:32
*/
@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
@Composable
fun UserScreenPV() {
@@ -83,339 +56,14 @@ fun UserScreen(
navController: NavController,
userViewModel: UserViewModel = viewModel()
) {
val treeData by userViewModel.treeData.collectAsState()
val userData = userViewModel.usersInfoPager.collectAsLazyPagingItems()
MainFuncFrame {
if (isLandscape()) {
UserScreenInLandscape(navController, userViewModel, treeData, userData)
} else {
UserScreenInPortrait(navController, userViewModel, treeData, userData)
}
}
}
@Composable
fun UserScreenInPortrait(
navController: NavController,
userViewModel: UserViewModel,
treeData: List<Pair<Pair<String, Int>, Any>>,
userData: LazyPagingItems<UserDataResponse.Data>
) {
Column(
modifier = M.fillMaxSize()
) {
MyInfoCard(
modifier = M
.weight(2f)
.padding(bottom = 15.dp)
) {
CollapsibleList(userViewModel, treeData, userData)
}
MyInfoCard(
modifier = M.weight(8f)
) {
MyInfoCard(modifier = M.fillMaxSize()) {
UserManageList(navController, userViewModel, userData)
}
}
}
@Composable
fun UserScreenInLandscape(
navController: NavController,
userViewModel: UserViewModel,
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) }
Row(
modifier = M.fillMaxSize()
) {
MyInfoCard(
modifier = M
.weight(leftWeight) // 使用动态权重
.fillMaxHeight()
) {
CollapsibleList(userViewModel, treeData, userData)
}
Box(
modifier = M
.fillMaxHeight()
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume() // 消费掉手势事件
val dragDelta = dragAmount.x // 获取横向拖动的距离
leftWeight = (leftWeight + dragDelta * 0.01f).coerceIn(2.5f, 4.5f)
rightWeight = 10f - leftWeight
// rightWeight = (rightWeight - dragDelta * 0.01f).coerceIn(5f, 8f)
}
},
contentAlignment = Alignment.Center
) {
MyInfoCard(
modifier = M
.width(15.dp)
.height(50.dp)
.padding(horizontal = 5.dp)
) {
}
}
MyInfoCard(
modifier = M
.weight(rightWeight) // 使用动态权重
.fillMaxHeight()
) {
UserManageList(navController, userViewModel, userData)
}
}
}
@Composable
fun CollapsibleList(
userViewModel: UserViewModel,
listData: List<Pair<Pair<String, Int>, Any>>,
pager: LazyPagingItems<UserDataResponse.Data>
) {
val queryInput by userViewModel.areaLike.collectAsState()
Column {
Row(
modifier = M
.fillMaxWidth()
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically
) {
QueryTextField(M.weight(1f), queryInput) {
userViewModel.updateAreaLike(it)
userViewModel.getUsersArea(false)
}
Image(
imageVector = Icons.Default.Refresh,
contentDescription = "Refresh",
modifier = M.clickable {
userViewModel.updateParams()
userViewModel.getUsersArea(showLoading = true)
pager.refresh()
}
)
}
LazyColumn {
items(listData) { item ->
CollapsibleItem(
userViewModel,
pager,
item = item,
currentLevel = 1,
needExpand = queryInput != ""
)
}
}
}
}
@Composable
fun CollapsibleItem(
userViewModel: UserViewModel,
pager: LazyPagingItems<UserDataResponse.Data>,
item: Pair<Pair<String, Int>, Any>,
currentLevel: Int,
xian: String = "",
xiang: String = "",
cun: String = "",
needExpand: Boolean = false
) {
val titleAndCount = item.first
if (StringUtils.isEmpty(titleAndCount.first)) {
return
}
val subItems = item.second
// 当前项的展开状态 0表示展开,-1表示折叠 1表示加载中
var expandedIndex by rememberSaveable { mutableStateOf(if (needExpand) 0 else -1) }
LaunchedEffect(needExpand) {
if (needExpand != (expandedIndex == 0)) {
expandedIndex = if (needExpand) 0 else -1
}
}
// 被选中的项
val xianTmp = if (currentLevel == 1) titleAndCount.first else xian
val xiangTmp = if (currentLevel == 2) titleAndCount.first else xiang
val cunTmp = if (currentLevel == 3) titleAndCount.first else cun
val expendListener = {
if (expandedIndex == -1) {
// 加载子项数据
expandedIndex = 1
userViewModel.loadArea(
currentLevel,
xian = xianTmp,
xiang = xiangTmp,
cun = cunTmp,
) {
expandedIndex = 0
}
} else {
expandedIndex = -1
}
}
Column {
val titleIsSelect =
userViewModel.areaFilter.collectAsState().value.contains(item.first.first)
// 顶层项的显示和点击逻辑
MyCard(radius = 20.dp, elevation = 0.dp, modifier = M.padding(1.5.dp)) {
Row(
modifier = M
.fillMaxWidth()
.background(if (titleIsSelect) MyColors.BlueGreen else MyColors.White)
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
expendListener()
},
onTap = {
userViewModel.updateParams(xianTmp, xiangTmp, cunTmp)
pager.refresh()
}
)
}
.padding(horizontal = 15.dp, vertical = 5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = M.weight(1f),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = titleAndCount.first,
color = if (titleIsSelect) MyColors.White else MyColors.Black,
fontSize = if (currentLevel == 1) MaterialTheme.typography.titleLarge.fontSize
else if (currentLevel == 2) MaterialTheme.typography.bodyLarge.fontSize
else MaterialTheme.typography.bodyMedium.fontSize,
)
if (titleAndCount.second != -1) {
Text(
modifier = M.padding(end = 5.dp),
text = "${titleAndCount.second}",
color = if (titleIsSelect) MyColors.LightGray else MyColors.Gray,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
style = MaterialTheme.typography.bodyMedium
)
}
}
if (expandedIndex == 1) {
CircularProgressIndicator(
strokeWidth = 3.dp,
modifier = M.size(20.dp),
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
} else {
Icon(
modifier = M
.clickable {
expendListener()
},
tint = if (titleIsSelect) MyColors.White else MyColors.Black,
imageVector = if (expandedIndex == 0)
Icons.Default.ExpandLess
else
Icons.Default.ExpandMore,
contentDescription = if (expandedIndex != -1) "Collapse" else "Expand"
)
}
}
}
AnimatedVisibility(visible = expandedIndex != -1) {
Column(modifier = M.padding(start = 10.dp)) {
when (subItems) {
// 如果子项是 Map 类型,表示还有嵌套项,需要递归渲染
is Map<*, *> -> {
subItems.forEach { subItem ->
if (subItem is Map.Entry<*, *>) {
val subPair = subItem.key to subItem.value
CollapsibleItem(
userViewModel,
pager,
subPair as Pair<Pair<String, Int>, Any>,
currentLevel + 1,
xianTmp,
xiangTmp,
cunTmp,
needExpand
)
}
}
}
// 如果子项是 List 类型,表示这是最底层的组数据
is List<*> -> {
subItems.forEach { subItem ->
if (subItem is Pair<*, *>) {
if (StringUtils.isEmpty(subItem.first.toString())) {
return@forEach
}
val isSelect = titleIsSelect
&& userViewModel.areaFilter.collectAsState().value.contains(
subItem.first.toString()
)
// 最底层的组
MyCard(
radius = 20.dp,
elevation = 0.dp,
modifier = M.padding(2.5.dp)
) {
Row(
modifier = M
.fillMaxWidth()
.background(
if (isSelect) MyColors.BlueGreen else MyColors.White
)
.clickable {
userViewModel.updateParams(
xianTmp,
xiangTmp,
cunTmp,
subItem.first.toString()
)
pager.refresh()
}
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = subItem.first.toString(),
color = if (isSelect) MyColors.White else MyColors.Black,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
style = MaterialTheme.typography.bodyMedium
)
if (subItem.second != -1) {
Text(
modifier = M.padding(end = 33.dp),
text = "${subItem.second}",
color = if (isSelect) MyColors.White else MyColors.Gray,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
// 未来处理更多层级时,可以继续添加逻辑
// else if (subItem is Pair<*, *>) {
// // 递归处理更深层的结构
// (subItem as? Pair<String, Any>)?.let { subItemPair ->
// CollapsibleItem(userViewModel,subItemPair,
// currentLevel + 1)
// }
// }
}
}
}
}
}
}
}
@Composable
fun UserManageList(
navController: NavController,
@@ -424,6 +72,7 @@ fun UserManageList(
) {
var queryInput by rememberSaveable { mutableStateOf("") }
val areaFilter by userViewModel.areaFilter.collectAsState()
var detailUser by remember { mutableStateOf<UserDataResponse.Data?>(null) }
val onFilterLikeChanged: (String) -> Unit = {
queryInput = it
userViewModel.updateParamsLike(it)
@@ -434,25 +83,14 @@ fun UserManageList(
.padding(15.dp)
.fillMaxSize()
) {
Row(
modifier = M
.fillMaxWidth()
.padding(bottom = 5.dp),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
) {
MyButton(text = "新增", onClick = {
navController.navigate("addUser")
})
AssistChipFilter("区域:", areaFilter, onClick = {
userViewModel.updateParams()
userData.refresh()
})
AssistChipFilter("筛选:", queryInput, onFilterLikeChanged)
Spacer(modifier = M.weight(1f))
QueryTextField(M.width(200.dp), queryInput, onValueChange = onFilterLikeChanged)
// MyButton(text = "更多", modifier = M.padding(horizontal = 10.dp), onClick = {})
}
UserFilterBar(
userViewModel = userViewModel,
userData = userData,
queryInput = queryInput,
onQueryChanged = onFilterLikeChanged,
areaFilter = areaFilter,
navController = navController
)
val myPager = userViewModel.usersInfoMyPager
val isRefreshing by myPager.listIsRefreshing.collectAsState()
MyRefreshTable(
@@ -467,8 +105,8 @@ fun UserManageList(
MyTableData(1, isIndex = true),
MyTableData("姓名", 1, { it.nhname }),
MyTableData("手机号", 2, { it.phone }),
MyTableData("身份证", 1, { if (it.idcard != "") "✔️" else "" }),
MyTableData("银行卡", 1, { if (it.bankcode != "") "✔️" else "" }),
MyTableData("身份证", 1, { if (it.idcard != "") "已录" else "" }),
MyTableData("银行卡", 1, { if (it.bankcode != "") "已录" else "" }),
MyTableData("所属地址", 3, { "${it.xian}${it.xiang}${it.cun}${it.zu}" }),
MyTableData("建档时间", 2, { TimeUtils.formatDateTimeStrToDateStr(it.createtime) }),
MyTableData("", 1, { "修改" }, true) {
@@ -478,27 +116,8 @@ fun UserManageList(
navController.navigate("weight/${it.sysid}")
},
),
onExpend = {
Column(modifier = M.fillMaxWidth().border(1.dp, MyColors.Gray).padding(15.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
InfoText("姓名", it.nhname,M.weight(2f),true)
InfoText("电话", it.phone,M.weight(2f),true)
}
Row(verticalAlignment = Alignment.CenterVertically) {
InfoText("银行", it.bankname,M.weight(2f),true)
InfoText("卡号", it.bankcode,M.weight(2f),true)
}
Row(verticalAlignment = Alignment.CenterVertically) {
InfoText("建档时间", it.createtime,M.weight(2f),true)
InfoText("身份证", it.idcard,M.weight(2f),true)
}
Row(verticalAlignment = Alignment.CenterVertically) {
InfoText("", it.xian,M.weight(1f),true)
InfoText("", it.xiang,M.weight(1f),true)
InfoText("", it.cun,M.weight(1f),true)
InfoText("", it.zu,M.weight(1f),true)
}
}
onClick = {
detailUser = it
},
onLongClick = {
userViewModel.deleteUser(it.nhname, it.sysid) {
@@ -507,4 +126,130 @@ fun UserManageList(
}
)
}
UserDetailDialog(detailUser) {
detailUser = null
}
}
@Composable
private fun UserDetailDialog(
user: UserDataResponse.Data?,
onDismiss: () -> Unit
) {
MyDialog(
title = "农户详情",
showDialog = user != null,
onDismissRequest = onDismiss
) {
if (user == null) {
return@MyDialog
}
Column(
modifier = M
.fillMaxSize()
.padding(horizontal = 24.dp, vertical = 8.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
DetailSection("基本信息") {
DetailRow("姓名", user.nhname, "手机号", user.phone)
DetailRow("身份证号", user.idcard, "建档时间", user.createtime)
}
DetailSection("银行卡") {
DetailRow("银行卡号", user.bankcode, "银行名称", user.bankname)
DetailRow("银行简称", user.bankshortname, "联行号", user.recbankcode)
}
DetailSection("地址") {
DetailRow("", user.xian, "", user.xiang)
DetailRow("", user.cun, "", user.zu)
DetailSingleRow("完整地址", "${user.xian}${user.xiang}${user.cun}${user.zu}")
}
DetailSection("系统信息") {
DetailRow("系统ID", user.sysid, "状态标记", user.flag.toString())
}
}
}
}
@Composable
private fun DetailSection(title: String, content: @Composable () -> Unit) {
MyCard(elevation = 0.5.dp, radius = 6.dp, modifier = M.fillMaxWidth()) {
Column(
modifier = M
.fillMaxWidth()
.background(MyColors.LightGray)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = title,
color = MyColors.BlueGreen,
fontWeight = FontWeight.Bold,
fontSize = MaterialTheme.typography.titleMedium.fontSize
)
content()
}
}
}
@Composable
private fun DetailRow(label1: String, value1: String, label2: String, value2: String) {
Row(modifier = M.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
DetailField(label1, value1, M.weight(1f))
DetailField(label2, value2, M.weight(1f))
}
}
@Composable
private fun DetailSingleRow(label: String, value: String) {
DetailField(label, value, M.fillMaxWidth())
}
@Composable
private fun DetailField(label: String, value: String, modifier: androidx.compose.ui.Modifier = M) {
Column(modifier = modifier) {
Text(
text = label,
color = MyColors.Gray,
fontSize = MaterialTheme.typography.bodySmall.fontSize
)
Text(
text = value.ifBlank { "-" },
color = MyColors.Black,
fontWeight = FontWeight.Medium,
fontSize = MaterialTheme.typography.bodyLarge.fontSize
)
}
}
@Composable
private fun UserFilterBar(
userViewModel: UserViewModel,
userData: LazyPagingItems<UserDataResponse.Data>,
queryInput: String,
onQueryChanged: (String) -> Unit,
areaFilter: String,
navController: NavController
) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = M.padding(bottom = 8.dp)) {
Row(
modifier = M.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
MyButton(text = "新增", onClick = {
navController.navigate("addUser")
})
Spacer(modifier = M.weight(1f))
QueryTextField(M.width(200.dp), queryInput, onValueChange = onQueryChanged)
}
Row(verticalAlignment = Alignment.CenterVertically) {
AssistChipFilter("区域:", areaFilter, onClick = {
userViewModel.clearAreaFilter()
userData.refresh()
})
AssistChipFilter("筛选:", queryInput, onQueryChanged)
Spacer(modifier = M.weight(1f))
}
}
}
@@ -3,28 +3,51 @@ package com.bbitcn.f8.pad.ui.screen.mainFunc;
import androidx.lifecycle.viewModelScope
import com.bbitcn.f8.pad.base.BaseViewModel
import com.bbitcn.f8.pad.model.net.request.UserListDataRequest
import com.bbitcn.f8.pad.model.net.response.UsersAreaResponse
import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.utils.pager.MyPager
import com.bbitcn.f8.pad.utils.pager.UsersInfoPagingSource
import com.blankj.utilcode.util.StringUtils
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class UserViewModel : BaseViewModel() {
private val _areaLike = MutableStateFlow("")
val areaLike = _areaLike.asStateFlow()
private val _treeData = MutableStateFlow<List<Pair<Pair<String, Int>, Any>>>(emptyList())
val treeData: StateFlow<List<Pair<Pair<String, Int>, Any>>> = _treeData.asStateFlow()
private val _tempData: MutableList<UsersAreaResponse.Data> = mutableListOf()
private val _areaFilter = MutableStateFlow("")
val areaFilter = _areaFilter.asStateFlow()
data class AreaOption(
val xian: String,
val xiang: String,
val cun: String,
val zu: String
)
private val mockAreas = listOf(
AreaOption("江安县", "怡乐镇", "公平村", "一组"),
AreaOption("江安县", "怡乐镇", "公平村", "二组"),
AreaOption("江安县", "大井镇", "友好村", "三组"),
AreaOption("长宁县", "竹海镇", "联盟村", "一组"),
AreaOption("长宁县", "梅硐镇", "石陇村", "五组"),
AreaOption("兴文县", "古宋镇", "建设村", "二组")
)
private val _xianList = MutableStateFlow<List<String>>(emptyList())
val xianList = _xianList.asStateFlow()
private val _xiangList = MutableStateFlow<List<String>>(emptyList())
val xiangList = _xiangList.asStateFlow()
private val _cunList = MutableStateFlow<List<String>>(emptyList())
val cunList = _cunList.asStateFlow()
private val _zuList = MutableStateFlow<List<String>>(emptyList())
val zuList = _zuList.asStateFlow()
private val _selectedXian = MutableStateFlow("")
val selectedXian = _selectedXian.asStateFlow()
private val _selectedXiang = MutableStateFlow("")
val selectedXiang = _selectedXiang.asStateFlow()
private val _selectedCun = MutableStateFlow("")
val selectedCun = _selectedCun.asStateFlow()
private val _selectedZu = MutableStateFlow("")
val selectedZu = _selectedZu.asStateFlow()
val usersInfoMyPager = MyPager(
pagingSourceFactory = { UsersInfoPagingSource(it) },
initialRequestData = UserListDataRequest(), // 传入初始的请求数据
@@ -33,7 +56,7 @@ class UserViewModel : BaseViewModel() {
val usersInfoPager = usersInfoMyPager.createPager(viewModelScope)
init {
getUsersArea(false)
refreshAreaOptions()
}
fun updateParamsLike(like: String) {
@@ -51,6 +74,67 @@ class UserViewModel : BaseViewModel() {
_areaFilter.value = buildAreaFilter(xian, xiang, cun, zu)
}
fun selectXian(value: String) {
_selectedXian.value = value
_selectedXiang.value = ""
_selectedCun.value = ""
_selectedZu.value = ""
refreshAreaOptions()
updateParams(value)
}
fun selectXiang(value: String) {
_selectedXiang.value = value
_selectedCun.value = ""
_selectedZu.value = ""
refreshAreaOptions()
updateParams(_selectedXian.value, value)
}
fun selectCun(value: String) {
_selectedCun.value = value
_selectedZu.value = ""
refreshAreaOptions()
updateParams(_selectedXian.value, _selectedXiang.value, value)
}
fun selectZu(value: String) {
_selectedZu.value = value
updateParams(_selectedXian.value, _selectedXiang.value, _selectedCun.value, value)
}
fun clearAreaFilter() {
_selectedXian.value = ""
_selectedXiang.value = ""
_selectedCun.value = ""
_selectedZu.value = ""
refreshAreaOptions()
updateParams()
}
private fun refreshAreaOptions() {
val xian = _selectedXian.value
val xiang = _selectedXiang.value
val cun = _selectedCun.value
_xianList.value = mockAreas.map { it.xian }.distinct()
_xiangList.value = mockAreas
.filter { xian.isEmpty() || it.xian == xian }
.map { it.xiang }
.distinct()
_cunList.value = mockAreas
.filter { (xian.isEmpty() || it.xian == xian) && (xiang.isEmpty() || it.xiang == xiang) }
.map { it.cun }
.distinct()
_zuList.value = mockAreas
.filter {
(xian.isEmpty() || it.xian == xian) &&
(xiang.isEmpty() || it.xiang == xiang) &&
(cun.isEmpty() || it.cun == cun)
}
.map { it.zu }
.distinct()
}
fun buildAreaFilter(xian: String, xiang: String, cun: String, zu: String): String {
return buildString {
append(xian)
@@ -60,154 +144,6 @@ class UserViewModel : BaseViewModel() {
}
}
fun updateAreaLike(like: String) {
_areaLike.value = like
}
fun getUsersArea(showLoading: Boolean) {
doInIoThreadWith(showLoading, "正在加载区域数据...") {
_treeData.value = emptyList()
val like = _areaLike.value
if (StringUtils.isEmpty(like)) {
val response = apiService.getUsersAreaForXian()
if (response.code == 1) {
response.data.forEach {
_tempData.add(UsersAreaResponse.Data(xian = it.xian, count = it.count))
}
_treeData.value = convertToNestedStructure(_tempData)
}
} else {
val response = apiService.getUsersArea(like)
if (response.code == 1) {
_treeData.value = convertToNestedStructure(response.data)
} else {
Toasty.showTipsDialog(response.msg)
}
}
}
}
fun loadArea(
currentLevel: Int,
xian: String,
xiang: String,
cun: String,
onFinish: () -> Unit
) {
// 只有在搜索信息为空的时候才逐级加载
if (StringUtils.isEmpty(_areaLike.value)) {
doInIoThreadNoDialog {
if (currentLevel == 1) {
// 级别为1时,加载乡
val list = apiService.getUsersAreaForXiang(xian = xian)
if (list.code == 1) {
list.data.forEach {
_tempData.add(
UsersAreaResponse.Data(
xian = xian,
xiang = it.xiang,
count = it.count
)
)
}
_treeData.value = convertToNestedStructure(_tempData)
} else {
Toasty.showTipsDialog(list.msg)
}
} else if (currentLevel == 2) {
// 级别为2时,加载村
val list = apiService.getUsersAreaForCun(xian = xian, xiang = xiang)
if (list.code == 1) {
list.data.forEach {
_tempData.add(
UsersAreaResponse.Data(
xian = xian,
xiang = xiang,
cun = it.cun,
count = it.count
)
)
}
_treeData.value = convertToNestedStructure(_tempData)
} else {
Toasty.showTipsDialog(list.msg)
}
} else if (currentLevel == 3) {
// 级别为3时,加载组
val list = apiService.getUsersAreaForZu(xian = xian, xiang = xiang, cun = cun)
if (list.code == 1) {
list.data.forEach {
_tempData.add(
UsersAreaResponse.Data(
xian = xian,
xiang = xiang,
cun = cun,
zu = it.zu,
count = it.count
)
)
}
_treeData.value = convertToNestedStructure(_tempData)
} else {
Toasty.showTipsDialog(list.msg)
}
}
onFinish()
}
}
}
private suspend fun convertToNestedStructure(data: List<UsersAreaResponse.Data>): List<Pair<Pair<String, Int>, Any>> {
val result = mutableListOf<Pair<Pair<String, Int>, Any>>()
// val result = mutableListOf<Pair<Pair<String, Int>, Pair<Pair<String, Int>, Pair<Pair<String, Int>, Pair<Pair<String, Int>, String>>>>>()
// 按县(Xian)分组
data.groupBy { it.xian }.forEach { (xian, xiangList) ->
// 按乡(Xiang)分组
val xiangGroups = xiangList.groupBy { it.xiang }.map { (xiang, cunList) ->
// 按村(Cun)分组
val cunGroups = cunList.groupBy { it.cun }.map { (cun, zuList) ->
// 按组(Zu)分组
val zuListWithCount = zuList.map { it.zu to it.count }
Pair(cun to getCount(data, xian, xiang, cun), zuListWithCount)
}.toMap()
// 返回乡及其村的分组
Pair(xiang to getCount(data, xian, xiang), cunGroups)
}.toMap()
result.add(Pair(xian to getCount(data, xian), xiangGroups))
}
return result
}
fun getCount(
list: List<UsersAreaResponse.Data>,
xian: String, xiang: String? = null, cun: String? = null, zu: String? = null
): Int {
for (item in list) {
// 找县的数量
if (xiang == null) {
if (item.xian == xian && item.xiang == "" && item.cun == "" && item.zu == "") {
return item.count
}
} else if (cun == null) {
// 找乡的数量
if (item.xian == xian && item.xiang == xiang && item.cun == "" && item.zu == "") {
return item.count
}
} else if (zu == null) {
// 找村的数量
if (item.xian == xian && item.xiang == xiang && item.cun == cun && item.zu == "") {
return item.count
}
} else {
// 找组的数量
if (item.xian == xian && item.xiang == xiang && item.cun == cun && item.zu == zu) {
return item.count
}
}
}
return -1
}
fun deleteUser(name: String, sysid: String, onSuccess: () -> Unit) {
Toasty.showConfirmDialog("确定删除农户" + name + "吗?") {
doInIoThread("正在删除农户...") {
@@ -1,11 +1,15 @@
package com.bbitcn.f8.pad.ui.screen.secondFunc
import android.graphics.Bitmap
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -26,8 +30,10 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
@@ -74,29 +80,23 @@ fun AddUserScreen(
addUserViewModel.initExtendData()
}
}
val scanDialogData by addUserViewModel.scanDialogData.collectAsState()
val ocrDialogData by addUserViewModel.ocrDialogData.collectAsState()
val faceDialogData by addUserViewModel.faceDialogData.collectAsState()
MainFuncFrame {
MyCard(modifier = M.fillMaxSize(), radius = 7.dp, elevation = 1.dp) {
VerticalTabPages(
modifier = M
.fillMaxHeight()
.padding(vertical = 15.dp),
tabs = listOf("基本信息", "人脸录入", "附件上传", "拓展信息"),
tabs = listOf("基本信息", "拓展信息"),
) {
when (it) {
0 -> AddUserBaseInfo(addUserViewModel)
1 -> AddUserFaceInfo(addUserViewModel)
2 -> AddUserAttachmentInfo(addUserViewModel)
3 -> AddUSerExtendInfo(addUserViewModel)
1 -> AddUSerExtendInfo(addUserViewModel)
}
}
}
}
ScanDialog(scanDialogData)
OCRDialog(ocrDialogData)
FaceDialog(faceDialogData)
}
@Composable
@@ -170,6 +170,12 @@ fun AddUserBaseInfo(
addUserViewModel: AddUserViewModel
) {
val configuration = LocalConfiguration.current
var selectedCard by rememberSaveable { mutableStateOf("id") }
var extraBankCount by rememberSaveable { mutableStateOf(0) }
var avatar by remember { mutableStateOf<Bitmap?>(null) }
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) {
avatar = it
}
// 判断设备是否为竖屏
if (configuration.orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
// 竖屏模式
@@ -182,10 +188,18 @@ fun AddUserBaseInfo(
) {
addUserViewModel.editFarmer()
}
AddUserLeftColumn(
addUserViewModel = addUserViewModel
FarmerCardWallet(
selectedCard = selectedCard,
extraBankCount = extraBankCount,
avatar = avatar,
onSelect = { selectedCard = it },
onAddBank = {
extraBankCount += 1
selectedCard = "bank_extra_$extraBankCount"
},
onAvatarClick = { cameraLauncher.launch(null) }
)
AddUserRightColumn(addUserViewModel = addUserViewModel)
FarmerCardEditor(selectedCard, addUserViewModel)
}
}
} else {
@@ -198,12 +212,103 @@ fun AddUserBaseInfo(
) {
addUserViewModel.editFarmer()
}
AddUserLeftColumn(
FarmerCardWallet(
modifier = M.weight(1f),
addUserViewModel,
selectedCard = selectedCard,
extraBankCount = extraBankCount,
avatar = avatar,
onSelect = { selectedCard = it },
onAddBank = {
extraBankCount += 1
selectedCard = "bank_extra_$extraBankCount"
},
onAvatarClick = { cameraLauncher.launch(null) }
)
}
AddUserRightColumn(M.weight(1f), addUserViewModel)
FarmerCardEditor(selectedCard, addUserViewModel, M.weight(1.6f))
}
}
}
@Composable
fun FarmerCardWallet(
modifier: Modifier = M,
selectedCard: String,
extraBankCount: Int,
avatar: Bitmap?,
onSelect: (String) -> Unit,
onAddBank: () -> Unit,
onAvatarClick: () -> Unit,
) {
Column(modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
MyCard(
radius = 8.dp,
elevation = 0.5.dp,
modifier = M.fillMaxWidth().clickable(onClick = onAvatarClick)
) {
Row(M.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
if (avatar == null) {
Image(
painter = painterResource(id = R.drawable.icon_user),
contentDescription = null,
modifier = M.size(54.dp)
)
} else {
Image(
bitmap = avatar.asImageBitmap(),
contentDescription = null,
modifier = M.size(54.dp)
)
}
Column(M.padding(start = 12.dp)) {
Text("头像", fontWeight = FontWeight.Bold)
Text("点击拍照", fontSize = MaterialTheme.typography.bodySmall.fontSize)
}
}
}
WalletCard("id", "身份证", "基础身份信息", selectedCard == "id", onSelect)
WalletCard("bank_main", "银行卡", "主银行卡", selectedCard == "bank_main", onSelect)
repeat(extraBankCount) { index ->
val id = "bank_extra_${index + 1}"
WalletCard(id, "银行卡 ${index + 2}", "备用银行卡", selectedCard == id, onSelect)
}
MyButton(text = "新增银行卡", modifier = M.fillMaxWidth()) {
onAddBank()
}
}
}
@Composable
private fun WalletCard(
id: String,
title: String,
subTitle: String,
selected: Boolean,
onSelect: (String) -> Unit
) {
MyCard(
radius = 8.dp,
elevation = if (selected) 1.5.dp else 0.3.dp,
modifier = M.fillMaxWidth().clickable { onSelect(id) }
) {
Column(M.fillMaxWidth().padding(14.dp)) {
Text(title, fontWeight = FontWeight.Bold)
Text(subTitle, fontSize = MaterialTheme.typography.bodySmall.fontSize)
}
}
}
@Composable
fun FarmerCardEditor(
selectedCard: String,
addUserViewModel: AddUserViewModel,
modifier: Modifier = M
) {
Column(modifier, verticalArrangement = Arrangement.spacedBy(5.dp)) {
when {
selectedCard == "id" -> AddUserIdentityEditor(addUserViewModel)
selectedCard == "bank_main" -> AddUserBankEditor(addUserViewModel, true, "主银行卡")
selectedCard.startsWith("bank_extra_") -> AddUserBankEditor(addUserViewModel, false, "备用银行卡")
}
}
}
@@ -299,6 +404,134 @@ fun AddUSerExtendInfo(addUserViewModel: AddUserViewModel) {
}
}
@Composable
fun AddUserIdentityEditor(addUserViewModel: AddUserViewModel) {
val userTypeList by addUserViewModel.userTypeList.collectAsState()
val userLabelList by addUserViewModel.userLabelList.collectAsState()
val xianList by addUserViewModel.xianList.collectAsState()
val xiangList by addUserViewModel.xiangList.collectAsState()
val cunList by addUserViewModel.cunList.collectAsState()
val zuList by addUserViewModel.zuList.collectAsState()
InputFrame("基本信息") {
MyTextField(modifier = M.weight(1f), hint = "姓名", value = addUserViewModel.idName) {
addUserViewModel.idName = it
}
CombinedDropdownMenu(M.weight(1f), listOf("", ""), "性别", addUserViewModel.idGender) {
addUserViewModel.idGender = it
}
CombinedDropdownMenu(M.weight(1f), userTypeList.map { it.name }, "农户类别", addUserViewModel.userType) {
addUserViewModel.userType = it
}
}
InputFrame("农户标签") {
SelectableChipGroup(M.weight(1f), userLabelList) {
addUserViewModel.switchUserLabel(it)
}
}
InputFrame("手机号码") {
MyTextField(
modifier = M.weight(1f),
hint = "请输入",
value = addUserViewModel.userPhone,
isNumberInputType = true,
) {
addUserViewModel.userPhone = it
}
}
InputFrame("身份证号") {
MyTextField(
modifier = M.weight(1f),
hint = "请输入",
value = addUserViewModel.idCardNumber,
isNumberInputType = true,
) {
addUserViewModel.idCardNumber = it
}
VipBadge {
MyButton(text = "拍照识别") {
addUserViewModel.recognizeIdCard({ name, gender, idCard, address ->
addUserViewModel.idName = name
addUserViewModel.idGender = gender
addUserViewModel.idCardNumber = idCard
addUserViewModel.idCardAddress = address
}) { xian, xiang, cun, zu ->
addUserViewModel.userXian = xian
addUserViewModel.userXiang = xiang
addUserViewModel.userCun = cun
addUserViewModel.userZu = zu
}
}
}
}
InputFrame("身份证地址") {
MyTextField(
modifier = M.weight(1f),
hint = "请输入",
readOnly = true,
value = addUserViewModel.idCardAddress
)
}
InputFrame("地址") {
CombinedDropdownMenu(M.weight(1f), xianList, "", addUserViewModel.userXian, true) {
addUserViewModel.userXian = it
addUserViewModel.onAddressSelected()
}
CombinedDropdownMenu(M.weight(1f), xiangList, "", addUserViewModel.userXiang, true) {
addUserViewModel.userXiang = it
addUserViewModel.onAddressSelected()
}
CombinedDropdownMenu(M.weight(1f), cunList, "", addUserViewModel.userCun, true) {
addUserViewModel.userCun = it
addUserViewModel.onAddressSelected()
}
CombinedDropdownMenu(M.weight(1f), zuList, "", addUserViewModel.userZu, true) {
addUserViewModel.userZu = it
}
}
}
@Composable
fun AddUserBankEditor(
addUserViewModel: AddUserViewModel,
isMainCard: Boolean,
title: String
) {
val cardInfo by if (isMainCard) {
addUserViewModel.mainCardInfo.collectAsState()
} else {
addUserViewModel.subCardInfo.collectAsState()
}
InputFrame(title) {
MyTextField(
modifier = M.weight(1f),
hint = "请输入",
value = cardInfo.first,
isNumberInputType = true,
) {
addUserViewModel.clearBankCardInfo(isMainCard)
addUserViewModel.updateBankCardCode(isMainCard, it)
}
VipBadge {
MyButton(text = "拍照识别") {
addUserViewModel.recognizeBankCard(isMainCard)
}
}
}
InputFrame("开户银行") {
MyTextField(
modifier = M.weight(1f),
hint = "请输入",
value = cardInfo.second.bankName,
readOnly = true,
isNumberInputType = true,
)
MyButton(text = "查询归属行") {
addUserViewModel.analysisBankCard(cardInfo.first, isMainCard)
}
}
}
@Composable
fun AddUserLeftColumn(
modifier: Modifier = M,
@@ -478,7 +711,7 @@ fun AddUserRightColumn(
}
@Composable
fun InputFrame(title: String, content: @Composable () -> Unit) {
fun InputFrame(title: String, content: @Composable RowScope.() -> Unit) {
Column(modifier = M.padding(top = 5.dp)) {
Text(text = title, fontSize = MaterialTheme.typography.headlineMedium.fontSize, fontWeight = FontWeight.Bold, modifier = M.padding(vertical = 7.5.dp))
Row(
@@ -99,11 +99,12 @@ class AddUserViewModel : BaseViewModel() {
doInIoThreadNoDialog {
// 更新四级联动数据
_xianList.value = _address.map { it.first }
_xiangList.value = _address.first { it.first == xian }.second.map { it.first }
_cunList.value =
_address.first { it.first == xian }.second.first { it.first == xiang }.second.map { it.first }
_zuList.value =
_address.first { it.first == xian }.second.first { it.first == xiang }.second.first { it.first == cun }.second
val xianNode = _address.firstOrNull { it.first == xian }
_xiangList.value = xianNode?.second?.map { it.first } ?: emptyList()
val xiangNode = xianNode?.second?.firstOrNull { it.first == xiang }
_cunList.value = xiangNode?.second?.map { it.first } ?: emptyList()
val cunNode = xiangNode?.second?.firstOrNull { it.first == cun }
_zuList.value = cunNode?.second ?: emptyList()
}
}
@@ -179,7 +179,7 @@ fun PayByElectronic(payViewModel: PayViewModel) {
MyTextField(value = input, isNumberInputType = true, modifier = M.width(200.dp)) {
input = it
}
MyButton(modifier = M.padding(horizontal = 10.dp), text = "电子支付") { }
MyButton(modifier = M.padding(horizontal = 10.dp), text = "电子支付", enabled = false) { }
}
}
@@ -191,7 +191,7 @@ fun PayByCash(payViewModel: PayViewModel) {
MyTextField(value = input, isNumberInputType = true, modifier = M.width(200.dp)) {
input = it
}
MyButton(modifier = M.padding(horizontal = 10.dp), text = "现金支付") { }
MyButton(modifier = M.padding(horizontal = 10.dp), text = "现金支付", enabled = false) { }
}
}
@@ -208,6 +208,6 @@ fun PayByMix(payViewModel: PayViewModel) {
MyTextField(value = input2, isNumberInputType = true, modifier = M.width(200.dp)) {
input2 = it
}
MyButton(modifier = M.padding(horizontal = 10.dp), text = "开始支付") { }
MyButton(modifier = M.padding(horizontal = 10.dp), text = "开始支付", enabled = false) { }
}
}
@@ -64,6 +64,7 @@ import com.bbitcn.f8.pad.ui.screen.dialog.SplitDialog
import com.bbitcn.f8.pad.ui.screen.dialog.WaterCutRecordDialog
import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.ui.screen.view.deviceManager.scale.MyWeightShow
import com.bbitcn.f8.pad.utils.WeightUnitFormatter
import kotlinx.coroutines.launch
@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
@@ -231,7 +232,7 @@ fun WeightList(weightViewModel: WeightViewModel) {
)
Text(
color = MyColors.Gray,
text = "单位公斤、元、元/公斤,支持长按删除",
text = "单位${WeightUnitFormatter.unitLabel}、元、${WeightUnitFormatter.priceUnitLabel},支持长按删除",
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
}
@@ -267,13 +268,13 @@ fun WeightList(weightViewModel: WeightViewModel) {
item.categoryName to 1,
item.packaging to 1,
item.basketCount.toString() to 1,
item.grossWeight.toString() to 1,
item.tareWeight.toString() to 1,
item.netWeight.toString() to 1,
item.unitPrice.toString() to 1,
WeightUnitFormatter.formatWeight(item.grossWeight) to 1,
WeightUnitFormatter.formatWeight(item.tareWeight) to 1,
WeightUnitFormatter.formatWeight(item.netWeight) to 1,
WeightUnitFormatter.formatPrice(item.unitPrice) to 1,
item.time to 2
),
verticalPadding = 15.dp,
verticalPadding = 20.dp,
onLongClick = {
Toasty.showConfirmDialog("是否删除第${item.weighingTimes}磅次记录?") {
weightViewModel.deleteDetail(item.sysid)
@@ -304,10 +305,10 @@ fun WeightList(weightViewModel: WeightViewModel) {
listOf(
it.key,
sumBasketCount,
sumGrossWeight,
sumTareWeight,
sumNetWeight,
sumUnitPrice
WeightUnitFormatter.formatWeight(sumGrossWeight),
WeightUnitFormatter.formatWeight(sumTareWeight),
WeightUnitFormatter.formatWeight(sumNetWeight),
WeightUnitFormatter.formatPrice(sumUnitPrice)
)
}
)
@@ -464,14 +465,12 @@ fun WeightRight(navController: NavController, weightViewModel: WeightViewModel)
horizontalArrangement = Arrangement.spacedBy(20.dp),
) {
VipBadge(modifier = M.weight(1f)) {
SaveButton(title = "按重量\n拆分茧别") {
Toasty.showToast("VIP功能,暂未开放")
SaveButton(title = "按重量\n拆分茧别", enabled = false) {
// weightViewModel.showSplitDialog(true)
}
}
VipBadge(modifier = M.weight(1f)) {
SaveButton(title = "按比例\n拆分茧别") {
Toasty.showToast("VIP功能,暂未开放")
SaveButton(title = "按比例\n拆分茧别", enabled = false) {
// weightViewModel.showSplitDialog(false)
}
}
@@ -535,7 +534,7 @@ fun AddKindsButton(
)
Text(
modifier = M.padding(top = 5.dp),
text = "${info.minPrice}-${info.maxPrice}元/公斤",
text = "${WeightUnitFormatter.formatPrice(info.minPrice)}-${WeightUnitFormatter.formatPrice(info.maxPrice)}${WeightUnitFormatter.priceUnitLabel}",
color = MyColors.Black,
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
@@ -545,15 +544,21 @@ fun AddKindsButton(
}
@Composable
fun SaveButton(modifier: Modifier = M, title: String, onClick: () -> Unit) {
MyCard(colors = MyColors.BlueGreen, modifier = modifier) {
Box(modifier = M.clickable { onClick() }) {
fun SaveButton(modifier: Modifier = M, title: String, enabled: Boolean = true, onClick: () -> Unit) {
MyCard(colors = if (enabled) MyColors.BlueGreen else MyColors.Disabled, modifier = modifier) {
Box(modifier = M.clickable {
if (enabled) {
onClick()
} else {
Toasty.showToast("正在开发中,敬请期待")
}
}) {
Text(
modifier = M
.fillMaxWidth()
.padding(10.dp),
.padding(vertical = 16.dp, horizontal = 10.dp),
text = title,
color = MyColors.White,
color = if (enabled) MyColors.White else MyColors.Gray,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
@@ -51,6 +51,8 @@ object Toasty : BaseViewModel() {
val confirmDialog = _confirmDialog.asStateFlow()
private val _inputDialog = MutableStateFlow(InputDialogData())
val inputDialog = _inputDialog.asStateFlow()
private val _globalDialog = MutableStateFlow(GlobalDialogData())
val globalDialog = _globalDialog.asStateFlow()
// 控制 Drawer 状态的 Boolean Flow
private val _isDrawerOpen = MutableStateFlow(false)
@@ -233,6 +235,17 @@ object Toasty : BaseViewModel() {
_confirmDialog.value = ConfirmDialogData(showDialog = false)
}
fun showGlobalDialog(data: GlobalDialogData) {
_globalDialog.value = data.copy(showDialog = true)
}
fun hideGlobalDialog(ownerKey: Any? = null) {
val current = _globalDialog.value
if (ownerKey == null || current.ownerKey == ownerKey) {
_globalDialog.value = GlobalDialogData(showDialog = false)
}
}
/**
* 登录过期事件
*/
@@ -254,3 +267,13 @@ object Toasty : BaseViewModel() {
}
}
data class GlobalDialogData(
val ownerKey: Any? = null,
val title: String = "",
val showDialog: Boolean = false,
val onDismissRequest: () -> Unit = {},
val clickOKStr: String = "",
val onClickOK: () -> Unit = {},
val content: @Composable () -> Unit = {}
)
@@ -17,6 +17,7 @@ object MyColors {
val Orange = Color(0xFFFFAE00)
val LightGray = Color(0xCCEEEEEE)
val Gray = Color(0xFF999999)
val Disabled = Color(0xFFE5E7EB)
val BlueGreen = Color(0xFF2FBAA3)
val LightBlueGreen = Color(0x802FBAA3)
val LightLightBlueGreen = Color(0xFFF7FFFE)
@@ -33,6 +33,7 @@ class PollingTask private constructor() {
* @param task 需要执行的任务
*/
fun startDelayedTask(taskId: String, delaySeconds: Long, task: Runnable) {
ensureScheduler()
stopTask(taskId) // 如果已有相同ID的任务,先停止
val future = scheduler!!.schedule({
@@ -99,6 +100,7 @@ class PollingTask private constructor() {
* @param task 需要执行的任务
*/
private fun startPollingTask(taskId: String, intervalSeconds: Long, task: Runnable) {
ensureScheduler()
stopTask(taskId) // 如果已有相同ID的任务,先停止
val future = scheduler!!.scheduleWithFixedDelay(task, 0, intervalSeconds, TimeUnit.SECONDS)
@@ -132,6 +134,12 @@ class PollingTask private constructor() {
}
}
private fun ensureScheduler() {
if (scheduler == null || scheduler!!.isShutdown || scheduler!!.isTerminated) {
createNewScheduler()
}
}
// 定义IO任务接口
interface IRxIOTask<T> {
fun doInIOThread(): T
@@ -0,0 +1,35 @@
package com.bbitcn.f8.pad.utils
object WeightUnitFormatter {
private const val KEY = "GLOBAL_WEIGHT_UNIT"
const val UNIT_KG = "公斤"
const val UNIT_JIN = ""
var displayUnit: String
get() = MMKVUtil.get(KEY, UNIT_KG)
set(value) {
MMKVUtil.put(KEY, if (value == UNIT_JIN) UNIT_JIN else UNIT_KG)
}
val unitLabel: String
get() = displayUnit
val priceUnitLabel: String
get() = "元/$unitLabel"
fun weight(kg: Double): Double = if (displayUnit == UNIT_JIN) kg * 2 else kg
fun price(pricePerKg: Double): Double = if (displayUnit == UNIT_JIN) pricePerKg / 2 else pricePerKg
fun formatWeight(kg: Double): String = trim(weight(kg))
fun formatPrice(pricePerKg: Double): String = trim(price(pricePerKg))
private fun trim(value: Double): String {
return if (value % 1.0 == 0.0) {
value.toInt().toString()
} else {
"%.2f".format(value).trimEnd('0').trimEnd('.')
}
}
}
@@ -15,10 +15,14 @@ import com.bbitcn.f8.pad.ui.screen.view.Toasty
import com.bbitcn.f8.pad.utils.ConverterUtil
import com.bbitcn.f8.pad.utils.externalModules.manager.serial.uhfSerial.UHFReaderForSerial
import com.bbitcn.f8.pad.utils.log.MyLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.io.ByteArrayOutputStream
import java.util.UUID
import kotlin.experimental.xor
@@ -28,6 +32,7 @@ object UHFReaderG06M_G25M : UHFReaderForSerial() {
private var mRcpBase: RcpBase? = null
private var mSioBase: SioCom? = null
private var onCommListener: OnCommListener? = null
private val protocolScope = CoroutineScope(SupervisorJob() + Dispatchers.IO.limitedParallelism(1))
private var mBattaryLevel = 0
@@ -128,7 +133,7 @@ object UHFReaderG06M_G25M : UHFReaderForSerial() {
Command(RcpMM.RCP_MM_PARA, RcpBase.RCP_MSG_GET)
mRcpBase?.setOnProtocolListener({ obj, protocolEventArg ->
doInIoThreadNoDialog {
protocolScope.launch {
val psData = protocolEventArg.protocolPacket
when (psData.Code) {
RcpMM.RCP_MM_READ_C_UII ->
@@ -45,7 +45,13 @@ object RetrofitClient {
try {
val response = chain.proceed(chain.request())
if (response.code !in listOf(200, 401)) {
if (response.code == 401) {
Toasty.loginExpired()
Toasty.error("登录已过期")
return@Interceptor response
}
if (response.code != 200) {
val content = response.peekBody(ERROR_BODY_PEEK_BYTES).string()
runCatching {
Gson().fromJson(content, CommonResponse::class.java)
@@ -8,9 +8,13 @@ import com.bbitcn.f8.pad.utils.global.RxTag
import com.bbitcn.f8.pad.utils.log.MyLog
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
/**
* 401 token refresh interceptor.
@@ -18,6 +22,20 @@ import okhttp3.Route
class TokenAuthenticator : Authenticator {
private val lock = Any()
private val refreshApi: ApiService by lazy {
Retrofit.Builder()
.baseUrl(RetrofitClient.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(
OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
)
.build()
.create(ApiService::class.java)
}
override fun authenticate(route: Route?, response: Response): Request? {
MyLog.network("TokenAuthenticator: authenticate")
@@ -45,7 +63,7 @@ class TokenAuthenticator : Authenticator {
val newToken = runBlocking {
MyLog.network("刷新Token: $currentToken, $refreshToken")
val result = RetrofitClient.apiInterface().refreshToken(
val result = refreshApi.refreshToken(
RefreshTokenRequest(
hardwareid = Global.getDeviceId(),
refToken = refreshToken,