添加了图片拍照及识别逻辑
This commit is contained in:
@@ -20,7 +20,7 @@ import javax.inject.Singleton
|
|||||||
object NetworkModule {
|
object NetworkModule {
|
||||||
|
|
||||||
// private const val BASE_URL = "https://127.0.0.1"
|
// private const val BASE_URL = "https://127.0.0.1"
|
||||||
// private const val BASE_URL = "https://vip.shuziweidao.com"
|
// private const val BASE_URL = "https://yyjk.shuziweidao.com/gateway/"
|
||||||
private const val BASE_URL = "http://192.168.1.237:9002/"
|
private const val BASE_URL = "http://192.168.1.237:9002/"
|
||||||
|
|
||||||
// private const val BASE_URL = "https://vip.shuziweidao.com/gateway/"
|
// private const val BASE_URL = "https://vip.shuziweidao.com/gateway/"
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ object SensorScaleUtils {
|
|||||||
* 去皮置零
|
* 去皮置零
|
||||||
*/
|
*/
|
||||||
fun tare() {
|
fun tare() {
|
||||||
|
Timber.d("tare isOpened = $isOpened")
|
||||||
if (!isOpened) return
|
if (!isOpened) return
|
||||||
mSensorScale?.tare {
|
mSensorScale?.tare {
|
||||||
Timber.d("去皮置零操作成功")
|
Timber.d("去皮置零操作成功")
|
||||||
|
|||||||
@@ -98,6 +98,16 @@ fun ReceiptProductScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
Timber.d("开始传感器采集")
|
||||||
|
viewModel.startSensorScale()
|
||||||
|
onDispose {
|
||||||
|
Timber.d("停止传感器采集")
|
||||||
|
viewModel.stopSensorScale()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
TopTitleBar()
|
TopTitleBar()
|
||||||
Box(
|
Box(
|
||||||
@@ -241,6 +251,10 @@ fun TabScreen(viewModel: ReceiptViewModel) {
|
|||||||
val tabs =
|
val tabs =
|
||||||
listOf("未确认(${purchaseUnadjustedList.size})", "已确认(${purchaseAdjustList.size})")
|
listOf("未确认(${purchaseUnadjustedList.size})", "已确认(${purchaseAdjustList.size})")
|
||||||
|
|
||||||
|
LaunchedEffect(purchaseUnadjustedList) {
|
||||||
|
viewModel.updateReceiptList(purchaseUnadjustedList)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(
|
.background(
|
||||||
@@ -493,7 +507,6 @@ private fun AdjustedRowInputLayout(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReceiptRightView(modifier: Modifier = Modifier, viewModel: ReceiptViewModel) {
|
fun ReceiptRightView(modifier: Modifier = Modifier, viewModel: ReceiptViewModel) {
|
||||||
val searchResultList by viewModel.searchListItems.collectAsState()
|
|
||||||
val selectedItem by viewModel.selectedItem.collectAsState()
|
val selectedItem by viewModel.selectedItem.collectAsState()
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -507,7 +520,11 @@ fun ReceiptRightView(modifier: Modifier = Modifier, viewModel: ReceiptViewModel)
|
|||||||
) {
|
) {
|
||||||
if (selectedItem == null) {
|
if (selectedItem == null) {
|
||||||
Spacer(modifier = Modifier.height(30.dp))
|
Spacer(modifier = Modifier.height(30.dp))
|
||||||
IdentityView(list = searchResultList, showSearchView = false, onOptionSelected = {})
|
IdentityView(
|
||||||
|
showSearchView = false,
|
||||||
|
onOptionSelected = {},
|
||||||
|
baseViewModel = viewModel
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ReceiptProductEditView(viewModel)
|
ReceiptProductEditView(viewModel)
|
||||||
}
|
}
|
||||||
@@ -525,16 +542,6 @@ private fun ReceiptProductEditView(
|
|||||||
) {
|
) {
|
||||||
val selectedItem by viewModel.selectedItem.collectAsState()
|
val selectedItem by viewModel.selectedItem.collectAsState()
|
||||||
val warehouseTypeList = GlobalData.warehouseTypeList
|
val warehouseTypeList = GlobalData.warehouseTypeList
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
|
||||||
|
|
||||||
DisposableEffect(lifecycleOwner) {
|
|
||||||
Timber.d("开始传感器采集")
|
|
||||||
viewModel.startSensorScale()
|
|
||||||
onDispose {
|
|
||||||
Timber.d("停止传感器采集")
|
|
||||||
viewModel.stopSensorScale()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedItem?.let {
|
selectedItem?.let {
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -256,13 +252,7 @@ fun ContentRightView(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ProductIdentification(viewModel: SelfProcurementViewModel) {
|
fun ProductIdentification(viewModel: SelfProcurementViewModel) {
|
||||||
val searchResultList by viewModel.searchListItems.collectAsState()
|
val selectedItem by viewModel.selectedItem.collectAsState()
|
||||||
// 需要搜索的信息
|
|
||||||
var searchInfo by remember { mutableStateOf("") }
|
|
||||||
var pageNum by remember { mutableIntStateOf(1) }
|
|
||||||
val isMoreLoading by viewModel.isMoreLoading.collectAsState()
|
|
||||||
val canLoadMore by viewModel.canLoadMore.collectAsState()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(457.dp)
|
.width(457.dp)
|
||||||
@@ -275,19 +265,9 @@ fun ProductIdentification(viewModel: SelfProcurementViewModel) {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
// 菜品识别
|
// 菜品识别
|
||||||
IdentityView(list = searchResultList, showSearchView = true, onSearchClick = {
|
IdentityView(showSearchView = true, onOptionSelected = {
|
||||||
searchInfo = it
|
|
||||||
viewModel.searchGoodsInfoList(it)
|
|
||||||
}, onOptionSelected = {
|
|
||||||
viewModel.updateSelectedItemWithSearch(it)
|
viewModel.updateSelectedItemWithSearch(it)
|
||||||
}, onLoadMore = {
|
}, checkedItem = selectedItem, baseViewModel = viewModel)
|
||||||
Timber.d("加载更多 canLoadMore = $canLoadMore")
|
|
||||||
if (!canLoadMore) {
|
|
||||||
return@IdentityView
|
|
||||||
}
|
|
||||||
pageNum++
|
|
||||||
viewModel.searchGoodsInfoList(goodsName = searchInfo, pageNo = pageNum)
|
|
||||||
}, isMoreLoading = isMoreLoading, canLoadMore = canLoadMore)
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
Row(
|
Row(
|
||||||
@@ -873,7 +853,9 @@ private fun SelfProductEditView(
|
|||||||
textColor = colorResource(R.color.green),
|
textColor = colorResource(R.color.green),
|
||||||
borderColor = colorResource(R.color.green),
|
borderColor = colorResource(R.color.green),
|
||||||
onClick = {
|
onClick = {
|
||||||
|
viewModel.updateLastPhotoUri(null)
|
||||||
viewModel.updateSelectedItem(null)
|
viewModel.updateSelectedItem(null)
|
||||||
|
viewModel.updateIdentityList(emptyList())
|
||||||
})
|
})
|
||||||
CustomButton(
|
CustomButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
package com.sw.inbound.ui.weight
|
package com.sw.inbound.ui.weight
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.camera.core.ImageCapture
|
|
||||||
import androidx.camera.core.ImageCaptureException
|
|
||||||
import androidx.camera.view.CameraController
|
import androidx.camera.view.CameraController
|
||||||
import androidx.camera.view.LifecycleCameraController
|
import androidx.camera.view.LifecycleCameraController
|
||||||
import androidx.camera.view.PreviewView
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -21,7 +15,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -37,8 +30,6 @@ import androidx.compose.ui.res.colorResource
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.sw.inbound.GlobalData
|
import com.sw.inbound.GlobalData
|
||||||
@@ -47,8 +38,8 @@ import com.sw.inbound.ext.dashedBorder
|
|||||||
import com.sw.inbound.ui.theme.AppTypography
|
import com.sw.inbound.ui.theme.AppTypography
|
||||||
import com.sw.inbound.utils.FileUtils
|
import com.sw.inbound.utils.FileUtils
|
||||||
import com.sw.inbound.utils.ToastUtils
|
import com.sw.inbound.utils.ToastUtils
|
||||||
|
import com.sw.inbound.utils.rememberPhotoCapture
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 相机预览 -默认不预览,支持拍照
|
* 相机预览 -默认不预览,支持拍照
|
||||||
@@ -72,6 +63,24 @@ fun CameraCaptureLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建拍照工具实例
|
||||||
|
val (photoCaptureHelper, takePhoto) = rememberPhotoCapture(
|
||||||
|
cameraController = cameraController,
|
||||||
|
onSuccess = { savedUri ->
|
||||||
|
// 处理拍照成功的逻辑
|
||||||
|
photoUri = savedUri
|
||||||
|
Timber.d("photoUri = $photoUri")
|
||||||
|
Toast.makeText(context, "图片已保存", Toast.LENGTH_SHORT).show()
|
||||||
|
showCamera = false
|
||||||
|
GlobalData.imageUri = photoUri
|
||||||
|
},
|
||||||
|
onError = { error ->
|
||||||
|
// 处理拍照失败的逻辑
|
||||||
|
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||||
|
GlobalData.imageUri = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(428.dp)
|
.width(428.dp)
|
||||||
@@ -165,38 +174,7 @@ fun CameraCaptureLayout(
|
|||||||
ToastUtils.showToast("请先开启图片预览")
|
ToastUtils.showToast("请先开启图片预览")
|
||||||
return@CustomButton
|
return@CustomButton
|
||||||
}
|
}
|
||||||
val executor = ContextCompat.getMainExecutor(context)
|
takePhoto()
|
||||||
val cacheDir = context.cacheDir
|
|
||||||
val photoFile = File.createTempFile(
|
|
||||||
"IMG_${System.currentTimeMillis()}",
|
|
||||||
".jpg",
|
|
||||||
cacheDir
|
|
||||||
)
|
|
||||||
|
|
||||||
val cacheOutputOptions =
|
|
||||||
ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
|
||||||
cameraController.takePicture(
|
|
||||||
cacheOutputOptions,
|
|
||||||
executor,
|
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
|
||||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
|
||||||
val savedUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
|
|
||||||
photoUri = savedUri
|
|
||||||
Timber.d("photoUri = $photoUri")
|
|
||||||
Toast.makeText(context, "图片已保存", Toast.LENGTH_SHORT).show()
|
|
||||||
showCamera = false
|
|
||||||
GlobalData.imageUri = photoUri
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(exception: ImageCaptureException) {
|
|
||||||
GlobalData.imageUri = null
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"拍照失败: ${exception.message}",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
borderColor = colorResource(R.color.green),
|
borderColor = colorResource(R.color.green),
|
||||||
textColor = colorResource(R.color.white),
|
textColor = colorResource(R.color.white),
|
||||||
@@ -213,52 +191,3 @@ fun CameraCaptureLayout(
|
|||||||
onDispose { }
|
onDispose { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun CameraContent(modifier: Modifier, cameraController: LifecycleCameraController) {
|
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = modifier,
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
//在Compose中使用View系统中的PreviewView
|
|
||||||
AndroidView(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(),
|
|
||||||
factory = { context ->
|
|
||||||
PreviewView(context).apply {
|
|
||||||
//设置布局宽度和高度占据全屏
|
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
)
|
|
||||||
//设置背景颜色
|
|
||||||
setBackgroundColor(android.graphics.Color.BLACK)
|
|
||||||
//设置渲染的实现模式
|
|
||||||
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
|
|
||||||
//设置缩放方式
|
|
||||||
scaleType = PreviewView.ScaleType.FILL_START
|
|
||||||
}.also {
|
|
||||||
it.controller = cameraController
|
|
||||||
cameraController.bindToLifecycle(lifecycleOwner)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReset = {},
|
|
||||||
onRelease = {
|
|
||||||
Timber.d("cameraController.unbind()")
|
|
||||||
cameraController.unbind()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Image(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(30.dp),
|
|
||||||
painter = painterResource(R.mipmap.ic_scan),
|
|
||||||
contentDescription = "扫描"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sw.inbound.ui.weight
|
package com.sw.inbound.ui.weight
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.camera.view.LifecycleCameraController
|
import androidx.camera.view.LifecycleCameraController
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
@@ -11,6 +12,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import coil.compose.AsyncImage
|
||||||
import com.sw.inbound.R
|
import com.sw.inbound.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +21,8 @@ import com.sw.inbound.R
|
|||||||
@Composable
|
@Composable
|
||||||
fun CameraPreview(
|
fun CameraPreview(
|
||||||
controller: LifecycleCameraController,
|
controller: LifecycleCameraController,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
photoUri: Uri? = null
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
@@ -31,6 +34,13 @@ fun CameraPreview(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
if (photoUri != null) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
model = photoUri,
|
||||||
|
contentDescription = "照片",
|
||||||
|
)
|
||||||
|
}
|
||||||
Image(
|
Image(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sw.inbound.ui.weight
|
package com.sw.inbound.ui.weight
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.camera.view.CameraController
|
import androidx.camera.view.CameraController
|
||||||
import androidx.camera.view.LifecycleCameraController
|
import androidx.camera.view.LifecycleCameraController
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -8,39 +9,103 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableDoubleStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import com.sw.inbound.ext.medium
|
import com.sw.inbound.ext.medium
|
||||||
|
import com.sw.inbound.model.request.PurchaseWarehouseParam
|
||||||
import com.sw.inbound.model.response.SearchGoodsInfo
|
import com.sw.inbound.model.response.SearchGoodsInfo
|
||||||
import com.sw.inbound.ui.theme.AppTypography
|
import com.sw.inbound.ui.theme.AppTypography
|
||||||
|
import com.sw.inbound.utils.InteractionUtils.Debouncer
|
||||||
|
import com.sw.inbound.utils.rememberPhotoCapture
|
||||||
|
import com.sw.inbound.viewmodel.BaseViewModel
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜品识别组件
|
* 菜品识别组件
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun IdentityView(
|
fun IdentityView(
|
||||||
list: List<SearchGoodsInfo.Record>,
|
|
||||||
showSearchView: Boolean = false,
|
showSearchView: Boolean = false,
|
||||||
onSearchClick: (String) -> Unit = {},
|
|
||||||
onOptionSelected: (SearchGoodsInfo.Record) -> Unit = {},
|
onOptionSelected: (SearchGoodsInfo.Record) -> Unit = {},
|
||||||
onLoadMore: (Int) -> Unit = {},
|
checkedItem: PurchaseWarehouseParam? = null,
|
||||||
isMoreLoading: Boolean = false,
|
baseViewModel: BaseViewModel,
|
||||||
canLoadMore: Boolean = true
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
val weightInfo by baseViewModel.weightInfo.collectAsState()
|
||||||
|
val identityList by baseViewModel.identityListItems.collectAsState()
|
||||||
|
val searchList by baseViewModel.searchListItems.collectAsState()
|
||||||
|
// var lastWeight by remember { mutableStateOf<Double?>(null) }
|
||||||
|
var lastWeight by remember { mutableDoubleStateOf(0.0) }
|
||||||
|
val lastPhotoUri by baseViewModel.lastPhotoUri.collectAsState()
|
||||||
|
var pageNum by remember { mutableIntStateOf(1) }
|
||||||
|
val isMoreLoading by baseViewModel.isMoreLoading.collectAsState()
|
||||||
|
val canLoadMore by baseViewModel.canLoadMore.collectAsState()
|
||||||
|
var list = if (identityList.isNotEmpty()) {
|
||||||
|
identityList
|
||||||
|
} else {
|
||||||
|
searchList
|
||||||
|
}
|
||||||
|
val debouncer = remember { Debouncer(2000) }
|
||||||
|
|
||||||
// CameraX 控制器
|
// CameraX 控制器
|
||||||
val cameraController = remember {
|
val cameraController = remember {
|
||||||
LifecycleCameraController(context).apply {
|
LifecycleCameraController(context).apply {
|
||||||
setEnabledUseCases(CameraController.IMAGE_CAPTURE)
|
// 必须设置有效的用例
|
||||||
bindToLifecycle(lifecycleOwner)
|
setEnabledUseCases(
|
||||||
|
CameraController.IMAGE_CAPTURE or
|
||||||
|
CameraController.VIDEO_CAPTURE
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 需要搜索的信息
|
||||||
|
var searchInfo by remember { mutableStateOf("") }
|
||||||
|
// 创建拍照工具实例
|
||||||
|
val (photoCaptureHelper, takePhoto) = rememberPhotoCapture(
|
||||||
|
cameraController = cameraController,
|
||||||
|
onSuccess = { savedUri ->
|
||||||
|
// 处理拍照成功的逻辑
|
||||||
|
baseViewModel.updateLastPhotoUri(savedUri)
|
||||||
|
Timber.d("photoUri = $savedUri")
|
||||||
|
baseViewModel.getIdentityList(savedUri)
|
||||||
|
},
|
||||||
|
onError = { error ->
|
||||||
|
// 处理拍照失败的逻辑
|
||||||
|
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||||
|
baseViewModel.updateLastPhotoUri(null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 绑定生命周期
|
||||||
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
cameraController.bindToLifecycle(lifecycleOwner)
|
||||||
|
onDispose { /* 清理资源 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(weightInfo) {
|
||||||
|
Timber.d("LaunchedEffect weightInfo1 = $weightInfo, lastWeight = $lastWeight")
|
||||||
|
if (weightInfo - lastWeight > 1) {
|
||||||
|
Timber.d("weightInfo2 = $weightInfo")
|
||||||
|
debouncer.debounce {
|
||||||
|
takePhoto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastWeight = weightInfo
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
@@ -50,11 +115,15 @@ fun IdentityView(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(397.dp)
|
.width(397.dp)
|
||||||
.height(298.dp),
|
.height(298.dp),
|
||||||
controller = cameraController
|
controller = cameraController,
|
||||||
|
photoUri = lastPhotoUri
|
||||||
)
|
)
|
||||||
if (showSearchView) {
|
if (showSearchView) {
|
||||||
Spacer(modifier = Modifier.height(30.dp))
|
Spacer(modifier = Modifier.height(30.dp))
|
||||||
CustomSearchView(onSearchClick = onSearchClick)
|
CustomSearchView(onSearchClick = {
|
||||||
|
searchInfo = it
|
||||||
|
baseViewModel.searchGoodsInfoList(it)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(30.dp))
|
Spacer(modifier = Modifier.height(30.dp))
|
||||||
if (showSearchView) {
|
if (showSearchView) {
|
||||||
@@ -63,7 +132,15 @@ fun IdentityView(
|
|||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.width(192.dp), options = list,
|
.width(192.dp), options = list,
|
||||||
onOptionSelected = onOptionSelected,
|
onOptionSelected = onOptionSelected,
|
||||||
onLoadMore = onLoadMore,
|
onLoadMore = {
|
||||||
|
Timber.d("加载更多 canLoadMore = $canLoadMore")
|
||||||
|
if (!canLoadMore) {
|
||||||
|
return@SingleSelectButtonGroup
|
||||||
|
}
|
||||||
|
pageNum++
|
||||||
|
baseViewModel.searchGoodsInfoList(goodsName = searchInfo, pageNo = pageNum)
|
||||||
|
},
|
||||||
|
checkedItem = list.find { it.goodsId == checkedItem?.goodsId },
|
||||||
isMoreLoading = isMoreLoading,
|
isMoreLoading = isMoreLoading,
|
||||||
canLoadMore = canLoadMore
|
canLoadMore = canLoadMore
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ fun ReceiptUnadjustedListView(
|
|||||||
key = { index, it -> it.id ?: index }) { index, it ->
|
key = { index, it -> it.id ?: index }) { index, it ->
|
||||||
|
|
||||||
val checkedBg =
|
val checkedBg =
|
||||||
if (selectIndex == index) Gray_DCDCF0 else Color.Transparent
|
if (/*selectIndex == index ||*/ checkedItem?.id == it.id) Gray_DCDCF0 else Color.Transparent
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(color = checkedBg)
|
.background(color = checkedBg)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -72,10 +72,11 @@ fun SingleSelectButtonGroup(
|
|||||||
unSelectedTextStyle: TextStyle = AppTypography.gray96a0aaTextStyle,
|
unSelectedTextStyle: TextStyle = AppTypography.gray96a0aaTextStyle,
|
||||||
onLoadMore: (Int) -> Unit = {},
|
onLoadMore: (Int) -> Unit = {},
|
||||||
isMoreLoading: Boolean = false,
|
isMoreLoading: Boolean = false,
|
||||||
canLoadMore: Boolean = true
|
canLoadMore: Boolean = true,
|
||||||
|
checkedItem: SearchGoodsInfo.Record? = null
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var selectedOption by remember { mutableStateOf(options.firstOrNull() ?: "") }
|
var selectedItem by remember { mutableStateOf(options.firstOrNull() ?: "") }
|
||||||
|
|
||||||
val lazyGridState = rememberLazyGridState()
|
val lazyGridState = rememberLazyGridState()
|
||||||
var pageNum by remember { mutableIntStateOf(1) }
|
var pageNum by remember { mutableIntStateOf(1) }
|
||||||
@@ -109,26 +110,27 @@ fun SingleSelectButtonGroup(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
|
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
|
||||||
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
|
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
|
||||||
) {
|
) {
|
||||||
items(items = options, key = { it.goodsId ?: it.goodsCode!! }) { option ->
|
itemsIndexed(items = options, key = { index, it -> index }) { index, item ->
|
||||||
|
val isChecked = item.goodsId == checkedItem?.goodsId
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.border(
|
.border(
|
||||||
width = 2.dp,
|
width = 2.dp,
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
color = if (option == selectedOption) selectedColor else unselectedColor,
|
color = if (isChecked) selectedColor else unselectedColor,
|
||||||
)
|
)
|
||||||
.clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(if (option == selectedOption) selectedColor else Color.Transparent)
|
.background(if (isChecked) selectedColor else Color.Transparent)
|
||||||
.clickable {
|
.clickable {
|
||||||
selectedOption = option
|
// selectedItem = item
|
||||||
onOptionSelected(option)
|
onOptionSelected(item)
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
// .padding(vertical = 20.dp)
|
// .padding(vertical = 20.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = option.goodsNameStr,
|
text = item.goodsNameStr,
|
||||||
style = if (option == selectedOption) AppTypography.BlueTextStyle else unSelectedTextStyle,
|
style = if (isChecked) AppTypography.BlueTextStyle else unSelectedTextStyle,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
// .width(192.dp)
|
// .width(192.dp)
|
||||||
// .height(64.dp)
|
// .height(64.dp)
|
||||||
|
|||||||
@@ -63,65 +63,27 @@ object InteractionUtils {
|
|||||||
// ======================== 防抖处理 ========================
|
// ======================== 防抖处理 ========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防抖处理器
|
* 防抖工具类
|
||||||
|
* @param delayMillis 防抖延迟时间(毫秒)
|
||||||
*/
|
*/
|
||||||
class Debouncer(
|
class Debouncer(private val delayMillis: Long) {
|
||||||
private val delayMillis: Long = 300L,
|
private var lastActionTime = 0L
|
||||||
private val coroutineScope: CoroutineScope
|
|
||||||
) {
|
|
||||||
private var debounceJob: Job? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行防抖操作
|
* 执行防抖操作
|
||||||
|
* @param block 要执行的代码块
|
||||||
|
* @return Boolean 是否实际执行了操作
|
||||||
*/
|
*/
|
||||||
fun <T> debounce(value: T, action: (T) -> Unit) {
|
fun debounce(block: () -> Unit): Boolean {
|
||||||
debounceJob?.cancel()
|
val currentTime = System.currentTimeMillis()
|
||||||
debounceJob = coroutineScope.launch {
|
if (currentTime - lastActionTime >= delayMillis) {
|
||||||
delay(delayMillis)
|
lastActionTime = currentTime
|
||||||
action(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记住防抖处理器
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun rememberDebouncer(
|
|
||||||
delayMillis: Long = 300L,
|
|
||||||
coroutineScope: CoroutineScope = rememberCoroutineScope()
|
|
||||||
): Debouncer {
|
|
||||||
return remember { Debouncer(delayMillis, coroutineScope) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== 节流处理 ========================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节流处理器
|
|
||||||
*/
|
|
||||||
class Throttler(private val timeoutMs: Long = 300L) {
|
|
||||||
private var lastRunTime: Long = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行节流操作
|
|
||||||
*/
|
|
||||||
fun throttle(block: () -> Unit) {
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
if (now - lastRunTime > timeoutMs) {
|
|
||||||
lastRunTime = now
|
|
||||||
block()
|
block()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记住节流处理器
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun rememberThrottler(timeoutMs: Long = 300L): Throttler {
|
|
||||||
return remember { Throttler(timeoutMs) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== 双击检测 ========================
|
// ======================== 双击检测 ========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.sw.inbound.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
|
import androidx.camera.core.ImageCaptureException
|
||||||
|
import androidx.camera.view.CameraController
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拍照工具类
|
||||||
|
* @param context Context 上下文
|
||||||
|
* @param cameraController CameraController 相机控制器
|
||||||
|
* @param onSuccess (Uri) -> Unit 拍照成功回调
|
||||||
|
* @param onError (String) -> Unit 拍照失败回调
|
||||||
|
*/
|
||||||
|
class PhotoCaptureHelper(
|
||||||
|
private val context: Context,
|
||||||
|
private val cameraController: CameraController,
|
||||||
|
private val onSuccess: (Uri) -> Unit = {},
|
||||||
|
private val onError: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拍照方法
|
||||||
|
* @param fileNamePrefix 文件名前缀,默认为"IMG_"
|
||||||
|
* @param fileExtension 文件扩展名,默认为".jpg"
|
||||||
|
*/
|
||||||
|
fun takePhoto(
|
||||||
|
fileNamePrefix: String = "IMG_",
|
||||||
|
fileExtension: String = ".jpg"
|
||||||
|
) {
|
||||||
|
Timber.d("开始拍照采集")
|
||||||
|
|
||||||
|
try {
|
||||||
|
val executor = ContextCompat.getMainExecutor(context)
|
||||||
|
val cacheDir = context.cacheDir
|
||||||
|
val photoFile = File.createTempFile(
|
||||||
|
"${fileNamePrefix}${System.currentTimeMillis()}",
|
||||||
|
fileExtension,
|
||||||
|
cacheDir
|
||||||
|
)
|
||||||
|
|
||||||
|
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
||||||
|
|
||||||
|
cameraController.takePicture(
|
||||||
|
outputOptions,
|
||||||
|
executor,
|
||||||
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
|
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
|
||||||
|
Timber.d("照片保存成功: $photoUri")
|
||||||
|
onSuccess(photoUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: ImageCaptureException) {
|
||||||
|
val errorMsg = "拍照失败: ${exception.message}"
|
||||||
|
Timber.e(exception, errorMsg)
|
||||||
|
onError(errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val errorMsg = "创建临时文件失败: ${e.message}"
|
||||||
|
Timber.e(e, errorMsg)
|
||||||
|
onError(errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于Compose的拍照Hook
|
||||||
|
* @param cameraController CameraController 相机控制器
|
||||||
|
* @param onSuccess (Uri) -> Unit 拍照成功回调
|
||||||
|
* @param onError (String) -> Unit 拍照失败回调
|
||||||
|
* @return Pair<PhotoCaptureHelper, () -> Unit> 返回工具类实例和拍照函数
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberPhotoCapture(
|
||||||
|
cameraController: CameraController,
|
||||||
|
onSuccess: (Uri) -> Unit = {},
|
||||||
|
onError: (String) -> Unit = {}
|
||||||
|
): Pair<PhotoCaptureHelper, () -> Unit> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val photoCaptureHelper = remember {
|
||||||
|
PhotoCaptureHelper(
|
||||||
|
context = context,
|
||||||
|
cameraController = cameraController,
|
||||||
|
onSuccess = onSuccess,
|
||||||
|
onError = onError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(photoCaptureHelper) { photoCaptureHelper.takePhoto() }
|
||||||
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
package com.sw.inbound.viewmodel
|
package com.sw.inbound.viewmodel
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.sw.inbound.GlobalData
|
import com.sw.inbound.GlobalData
|
||||||
import com.sw.inbound.model.response.ApiResponse
|
import com.sw.inbound.model.response.ApiResponse
|
||||||
import com.sw.inbound.model.response.DictType
|
import com.sw.inbound.model.response.DictType
|
||||||
|
import com.sw.inbound.model.response.GoodsInfo
|
||||||
|
import com.sw.inbound.model.response.SearchGoodsInfo
|
||||||
import com.sw.inbound.network.LoadingState
|
import com.sw.inbound.network.LoadingState
|
||||||
import com.sw.inbound.repository.RemoteRepository
|
import com.sw.inbound.repository.RemoteRepository
|
||||||
|
import com.sw.inbound.sdk.SensorScaleUtils
|
||||||
import com.sw.inbound.utils.ToastUtils
|
import com.sw.inbound.utils.ToastUtils
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -16,6 +22,40 @@ import java.io.IOException
|
|||||||
abstract class BaseViewModel(
|
abstract class BaseViewModel(
|
||||||
private val repository: RemoteRepository
|
private val repository: RemoteRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
// 需要拍照
|
||||||
|
private val _takePhotoState = MutableStateFlow(false)
|
||||||
|
val takePhotoState: StateFlow<Boolean> = _takePhotoState
|
||||||
|
|
||||||
|
// 称重结果
|
||||||
|
private val _weightInfo = MutableStateFlow<Double>(0.0)
|
||||||
|
val weightInfo: StateFlow<Double> = _weightInfo
|
||||||
|
|
||||||
|
// 搜索物品列表
|
||||||
|
private val _searchListItems = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
|
||||||
|
val searchListItems: StateFlow<List<SearchGoodsInfo.Record>> = _searchListItems
|
||||||
|
|
||||||
|
// 识别列表
|
||||||
|
private val _identityListItems = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
|
||||||
|
val identityListItems: StateFlow<List<SearchGoodsInfo.Record>> = _identityListItems
|
||||||
|
|
||||||
|
// 待收货的列表 用于识别时从中获取
|
||||||
|
private val _receiptList = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
|
||||||
|
|
||||||
|
// 拍照uri
|
||||||
|
private val _lastPhotoUri = MutableStateFlow<Uri?>(null)
|
||||||
|
val lastPhotoUri: StateFlow<Uri?> = _lastPhotoUri
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可以加载更多
|
||||||
|
*/
|
||||||
|
private val _canLoadMore = MutableStateFlow(true)
|
||||||
|
val canLoadMore: StateFlow<Boolean> = _canLoadMore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示进度条
|
||||||
|
*/
|
||||||
|
private val _isMoreLoading = MutableStateFlow(false)
|
||||||
|
val isMoreLoading = _isMoreLoading
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 带进度条的请求
|
* 带进度条的请求
|
||||||
@@ -67,6 +107,107 @@ abstract class BaseViewModel(
|
|||||||
ToastUtils.showToast(message)
|
ToastUtils.showToast(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startSensorScale() {
|
||||||
|
Timber.d("开始称重")
|
||||||
|
SensorScaleUtils.startScale(callback = { weight ->
|
||||||
|
_weightInfo.value = weight
|
||||||
|
handleSensorScale(weight)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopSensorScale() {
|
||||||
|
Timber.d("关闭称重")
|
||||||
|
SensorScaleUtils.stopContinuousRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun handleSensorScale(weight: Double)
|
||||||
|
|
||||||
|
fun updateCanLoadMore(canLoad: Boolean) {
|
||||||
|
_canLoadMore.value = canLoad
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLastPhotoUri(photoUri: Uri?) {
|
||||||
|
_lastPhotoUri.value = photoUri
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateReceiptList(list: List<GoodsInfo>) {
|
||||||
|
_receiptList.value = list.map { goods ->
|
||||||
|
SearchGoodsInfo.Record(
|
||||||
|
goodsId = goods.goodId,
|
||||||
|
goodsName = goods.goodName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateIdentityList(list: List<SearchGoodsInfo.Record>) {
|
||||||
|
_identityListItems.value = list
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取识别列表
|
||||||
|
*/
|
||||||
|
fun getIdentityList(photoUri: Uri) {
|
||||||
|
Timber.d("getIdentityList")
|
||||||
|
launchWithLoading {
|
||||||
|
_searchListItems.value = emptyList<SearchGoodsInfo.Record>()
|
||||||
|
// 有收货列表,则返回收货列表
|
||||||
|
if (_receiptList.value.isNotEmpty()) {
|
||||||
|
_identityListItems.value = _receiptList.value
|
||||||
|
if (_identityListItems.value.isNotEmpty()) {
|
||||||
|
handleIdentityItem(_identityListItems.value[0])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val response = repository.searchGoodsInfoList("", 1, 10)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
_identityListItems.value = response.result?.records ?: emptyList()
|
||||||
|
if (_identityListItems.value.isNotEmpty()) {
|
||||||
|
handleIdentityItem(_identityListItems.value[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理识别后要选中的值
|
||||||
|
*/
|
||||||
|
open fun handleIdentityItem(goodsInfo: SearchGoodsInfo.Record) {}
|
||||||
|
|
||||||
|
fun updateMoreLoading(moreLoading: Boolean) {
|
||||||
|
_isMoreLoading.value = moreLoading
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索物品
|
||||||
|
*/
|
||||||
|
fun searchGoodsInfoList(
|
||||||
|
goodsName: String,
|
||||||
|
pageNo: Int = 1,
|
||||||
|
pageSize: Int = 10
|
||||||
|
) {
|
||||||
|
launch {
|
||||||
|
if (pageNo > 1) {
|
||||||
|
_isMoreLoading.value = true
|
||||||
|
}
|
||||||
|
_identityListItems.value = emptyList<SearchGoodsInfo.Record>()
|
||||||
|
val response = repository.searchGoodsInfoList(goodsName, pageNo, pageSize)
|
||||||
|
_isMoreLoading.value = false
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
val data = response.result
|
||||||
|
data?.records?.let {
|
||||||
|
_canLoadMore.value = data.records.size >= pageSize
|
||||||
|
if (pageNo > 1) {
|
||||||
|
_searchListItems.value = _searchListItems.value + data.records
|
||||||
|
} else {
|
||||||
|
_searchListItems.value = data.records
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_searchListItems.value = emptyList<SearchGoodsInfo.Record>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getDictType() {
|
fun getDictType() {
|
||||||
Timber.d("获取所有字典列表")
|
Timber.d("获取所有字典列表")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
|
|||||||
@@ -21,32 +21,18 @@ class PurchaseOrderViewModel @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
private val _supplierList = MutableStateFlow<List<SupplierInfo?>>(emptyList())
|
private val _supplierList = MutableStateFlow<List<SupplierInfo?>>(emptyList())
|
||||||
val supplierList: StateFlow<List<SupplierInfo?>> = _supplierList
|
val supplierList: StateFlow<List<SupplierInfo?>> = _supplierList
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否可以加载更多
|
|
||||||
*/
|
|
||||||
private val _canLoadMore = MutableStateFlow(true)
|
|
||||||
val canLoadMore: StateFlow<Boolean> = _canLoadMore
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否显示进度条
|
|
||||||
*/
|
|
||||||
private val _isMoreLoading = MutableStateFlow(false)
|
|
||||||
val isMoreLoading = _isMoreLoading
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取采购订单-供应商列表
|
* 获取采购订单-供应商列表
|
||||||
*/
|
*/
|
||||||
fun getOrderList(pageNum: Int = 1, pageSize: Int = 5) {
|
fun getOrderList(pageNum: Int = 1, pageSize: Int = 5) {
|
||||||
if (pageNum != 1) {
|
if (pageNum != 1) {
|
||||||
launch {
|
launch {
|
||||||
_isMoreLoading.value = true
|
updateMoreLoading(true)
|
||||||
val response = repository.getReceiveList(pageNum, pageSize)
|
val response = repository.getReceiveList(pageNum, pageSize)
|
||||||
_isMoreLoading.value = false
|
updateMoreLoading(false)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
response.result?.records?.let {
|
response.result?.records?.let {
|
||||||
_canLoadMore.value = it.size >= pageSize
|
updateCanLoadMore(it.size >= pageSize)
|
||||||
Timber.d("加载更多 canLoadMore =${it.size >= pageSize}")
|
Timber.d("加载更多 canLoadMore =${it.size >= pageSize}")
|
||||||
_supplierList.value = _supplierList.value + it
|
_supplierList.value = _supplierList.value + it
|
||||||
}
|
}
|
||||||
@@ -63,5 +49,9 @@ class PurchaseOrderViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleSensorScale(weight: Double) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.sw.inbound.model.response.GoodsInfo
|
|||||||
import com.sw.inbound.model.response.PurchaseInfo
|
import com.sw.inbound.model.response.PurchaseInfo
|
||||||
import com.sw.inbound.model.response.SearchGoodsInfo
|
import com.sw.inbound.model.response.SearchGoodsInfo
|
||||||
import com.sw.inbound.repository.RemoteRepository
|
import com.sw.inbound.repository.RemoteRepository
|
||||||
import com.sw.inbound.sdk.SensorScaleUtils
|
|
||||||
import com.sw.inbound.utils.ToastUtils
|
import com.sw.inbound.utils.ToastUtils
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -63,10 +62,6 @@ class ReceiptViewModel @Inject constructor(
|
|||||||
private val _selectedItem = MutableStateFlow<GoodsInfo?>(null)
|
private val _selectedItem = MutableStateFlow<GoodsInfo?>(null)
|
||||||
val selectedItem: StateFlow<GoodsInfo?> = _selectedItem
|
val selectedItem: StateFlow<GoodsInfo?> = _selectedItem
|
||||||
|
|
||||||
// 搜索物品列表
|
|
||||||
private val _searchListItems = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
|
|
||||||
val searchListItems: StateFlow<List<SearchGoodsInfo.Record>> = _searchListItems
|
|
||||||
|
|
||||||
// 收货请求结果
|
// 收货请求结果
|
||||||
private val _receiptResult = MutableStateFlow<Boolean>(false)
|
private val _receiptResult = MutableStateFlow<Boolean>(false)
|
||||||
val receiptResult: StateFlow<Boolean> = _receiptResult
|
val receiptResult: StateFlow<Boolean> = _receiptResult
|
||||||
@@ -109,6 +104,16 @@ class ReceiptViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleIdentityItem(goodsInfo: SearchGoodsInfo.Record) {
|
||||||
|
val firstItem = unadjustedOrders.value.find { order ->
|
||||||
|
order.goodId == goodsInfo.goodsId
|
||||||
|
}
|
||||||
|
updateLastPhotoUri(null)
|
||||||
|
updateIdentityList(emptyList())
|
||||||
|
_selectedItem.value = firstItem
|
||||||
|
// updateSelectedItemWithSwitch(firstItem)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新选中的item
|
* 更新选中的item
|
||||||
*/
|
*/
|
||||||
@@ -237,9 +242,7 @@ class ReceiptViewModel @Inject constructor(
|
|||||||
_amountUserInput.value = boolean
|
_amountUserInput.value = boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startSensorScale() {
|
override fun handleSensorScale(weight: Double) {
|
||||||
Timber.d("开始称重")
|
|
||||||
SensorScaleUtils.startScale(callback = { weight ->
|
|
||||||
val currentItem = _selectedItem.value
|
val currentItem = _selectedItem.value
|
||||||
|
|
||||||
currentItem?.let { info ->
|
currentItem?.let { info ->
|
||||||
@@ -248,7 +251,7 @@ class ReceiptViewModel @Inject constructor(
|
|||||||
_selectedItem.value = info.copy(
|
_selectedItem.value = info.copy(
|
||||||
goodsWeight = weight.toSafeBigDecimal(),
|
goodsWeight = weight.toSafeBigDecimal(),
|
||||||
)
|
)
|
||||||
return@startScale
|
return
|
||||||
}
|
}
|
||||||
var consumeValue = currentItem.consumeValue?.toDouble() ?: 1.0
|
var consumeValue = currentItem.consumeValue?.toDouble() ?: 1.0
|
||||||
var purchaseValue = currentItem.purchaseValue?.toDouble() ?: 1.0
|
var purchaseValue = currentItem.purchaseValue?.toDouble() ?: 1.0
|
||||||
@@ -300,12 +303,6 @@ class ReceiptViewModel @Inject constructor(
|
|||||||
recPriceExItemTemp = if (_amountUserInput.value) info.recPriceExItemTemp else finalAmount.toFormattedString(),
|
recPriceExItemTemp = if (_amountUserInput.value) info.recPriceExItemTemp else finalAmount.toFormattedString(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopSensorScale() {
|
|
||||||
Timber.d("关闭称重")
|
|
||||||
SensorScaleUtils.stopContinuousRead()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun partialReceipt(uploadInfo: UploadInfo) {
|
fun partialReceipt(uploadInfo: UploadInfo) {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import com.sw.inbound.model.request.PurchaseWarehouseParam
|
|||||||
import com.sw.inbound.model.response.DictType
|
import com.sw.inbound.model.response.DictType
|
||||||
import com.sw.inbound.model.response.SearchGoodsInfo
|
import com.sw.inbound.model.response.SearchGoodsInfo
|
||||||
import com.sw.inbound.repository.RemoteRepository
|
import com.sw.inbound.repository.RemoteRepository
|
||||||
import com.sw.inbound.sdk.SensorScaleUtils
|
|
||||||
import com.sw.inbound.utils.ContextUtils
|
import com.sw.inbound.utils.ContextUtils
|
||||||
import com.sw.inbound.utils.FileUtils
|
import com.sw.inbound.utils.FileUtils
|
||||||
import com.sw.inbound.utils.ToastUtils
|
import com.sw.inbound.utils.ToastUtils
|
||||||
@@ -68,13 +67,6 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
private val _showAddPurchaseUnitDialog = MutableStateFlow<Boolean>(false)
|
private val _showAddPurchaseUnitDialog = MutableStateFlow<Boolean>(false)
|
||||||
val showAddPurchaseUnitDialog: StateFlow<Boolean> = _showAddPurchaseUnitDialog
|
val showAddPurchaseUnitDialog: StateFlow<Boolean> = _showAddPurchaseUnitDialog
|
||||||
|
|
||||||
// 搜索物品列表
|
|
||||||
private val _searchListItems = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
|
|
||||||
val searchListItems: StateFlow<List<SearchGoodsInfo.Record>> = _searchListItems
|
|
||||||
|
|
||||||
// 称重结果
|
|
||||||
private val _weightInfo = MutableStateFlow<Double?>(0.0)
|
|
||||||
val weightInfo: StateFlow<Double?> = _weightInfo
|
|
||||||
|
|
||||||
// 是否可以下拉
|
// 是否可以下拉
|
||||||
private val _dropdownTextChange = MutableStateFlow<Boolean>(false)
|
private val _dropdownTextChange = MutableStateFlow<Boolean>(false)
|
||||||
@@ -95,18 +87,6 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
private val _amountUserInput = MutableStateFlow<Boolean>(false)
|
private val _amountUserInput = MutableStateFlow<Boolean>(false)
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否可以加载更多
|
|
||||||
*/
|
|
||||||
private val _canLoadMore = MutableStateFlow(true)
|
|
||||||
val canLoadMore: StateFlow<Boolean> = _canLoadMore
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否显示进度条
|
|
||||||
*/
|
|
||||||
private val _isMoreLoading = MutableStateFlow(false)
|
|
||||||
val isMoreLoading = _isMoreLoading
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新全局仓库
|
* 更新全局仓库
|
||||||
*/
|
*/
|
||||||
@@ -114,12 +94,7 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
_globalWarehouse.value = dictType
|
_globalWarehouse.value = dictType
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun handleSensorScale(weight: Double) {
|
||||||
* 开始称重
|
|
||||||
*/
|
|
||||||
fun startSensorScale() {
|
|
||||||
Timber.d("开始称重")
|
|
||||||
SensorScaleUtils.startScale(callback = { weight ->
|
|
||||||
val currentItem = _selectedItem.value
|
val currentItem = _selectedItem.value
|
||||||
|
|
||||||
currentItem?.let { info ->
|
currentItem?.let { info ->
|
||||||
@@ -129,7 +104,7 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
_selectedItem.value = info.copy(
|
_selectedItem.value = info.copy(
|
||||||
goodsWeight = weight.toSafeBigDecimal(),
|
goodsWeight = weight.toSafeBigDecimal(),
|
||||||
)
|
)
|
||||||
return@startScale
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// val selectUnitType = currentItem.selectUnitType
|
// val selectUnitType = currentItem.selectUnitType
|
||||||
@@ -178,12 +153,6 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
goodsPriceTemp = if (_amountUserInput.value) info.goodsPriceTemp else finalAmount.toFormattedString()
|
goodsPriceTemp = if (_amountUserInput.value) info.goodsPriceTemp else finalAmount.toFormattedString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopSensorScale() {
|
|
||||||
Timber.d("关闭称重")
|
|
||||||
SensorScaleUtils.stopContinuousRead()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAddProductDialog(show: Boolean) {
|
fun updateAddProductDialog(show: Boolean) {
|
||||||
@@ -194,6 +163,7 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateSelectedItem(purchaseOrder: PurchaseWarehouseParam?) {
|
fun updateSelectedItem(purchaseOrder: PurchaseWarehouseParam?) {
|
||||||
|
// updateLastPhotoUri(null)
|
||||||
if (purchaseOrder != null) {
|
if (purchaseOrder != null) {
|
||||||
if (purchaseOrder.goodsCount != 0.0) {
|
if (purchaseOrder.goodsCount != 0.0) {
|
||||||
if (purchaseOrder.goodsUnitPriceTemp.isNotEmpty()) { // 计算金额 单价不为空,并且金额没有手动输入
|
if (purchaseOrder.goodsUnitPriceTemp.isNotEmpty()) { // 计算金额 单价不为空,并且金额没有手动输入
|
||||||
@@ -277,34 +247,8 @@ class SelfProcurementViewModel @Inject constructor(
|
|||||||
_purchaseList.value = emptyList()
|
_purchaseList.value = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun handleIdentityItem(goodsInfo: SearchGoodsInfo.Record) {
|
||||||
* 搜索物品
|
updateSelectedItemWithSearch(goodsInfo)
|
||||||
*/
|
|
||||||
fun searchGoodsInfoList(
|
|
||||||
goodsName: String,
|
|
||||||
pageNo: Int = 1,
|
|
||||||
pageSize: Int = 10
|
|
||||||
) {
|
|
||||||
launch {
|
|
||||||
if (pageNo > 1) {
|
|
||||||
_isMoreLoading.value = true
|
|
||||||
}
|
|
||||||
val response = repository.searchGoodsInfoList(goodsName, pageNo, pageSize)
|
|
||||||
_isMoreLoading.value = false
|
|
||||||
if (parseResponse(response)) {
|
|
||||||
val data = response.result
|
|
||||||
data?.records?.let {
|
|
||||||
_canLoadMore.value = data.records.size >= pageSize
|
|
||||||
if (pageNo > 1) {
|
|
||||||
_searchListItems.value = _searchListItems.value + data.records
|
|
||||||
} else {
|
|
||||||
_searchListItems.value = data.records
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_searchListItems.value = emptyList<SearchGoodsInfo.Record>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSelectedItemWithSearch(searchFirst: SearchGoodsInfo.Record) {
|
fun updateSelectedItemWithSearch(searchFirst: SearchGoodsInfo.Record) {
|
||||||
|
|||||||
@@ -64,4 +64,8 @@ class UserViewModel @Inject constructor(
|
|||||||
SPUtil.getInstance().remove(GlobalKey.KEY_USER_INFO)
|
SPUtil.getInstance().remove(GlobalKey.KEY_USER_INFO)
|
||||||
SPUtil.getInstance().remove(GlobalKey.KEY_TOKEN)
|
SPUtil.getInstance().remove(GlobalKey.KEY_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleSensorScale(weight: Double) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user