feat(network): 新增菜品向量采集数据上传接口及调用链路
- ApiService/RemoteRepository/NetViewModel 新增 uploadFoodVectorData 三层实现,新增 uploadFoodVectorDataBatch 供分批循环调用直接 await 结果 - VectorCollectionFragment 启用 upload() 链路:调用 uploadFoodVectorDataBatch 分批上传,去掉 runBlocking/runOnUiThread,统一在协程主线程更新 UI - ImageUploader 修正包路径并将 onProgress 改为 suspend lambda - Food 实体新增 version 字段,同步 ObjectBox model - SeasoningSelectDialog 切换为真实接口,FoodSearchDialog 注释模拟数据 - 调整删除按钮 drawable 为圆形并对列表 ItemView 增加 tint
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:6465153867413579585",
|
||||
"lastPropertyId": "4:3660727688686055762",
|
||||
"lastPropertyId": "5:7592685238899911344",
|
||||
"name": "Food",
|
||||
"properties": [
|
||||
{
|
||||
@@ -30,6 +30,11 @@
|
||||
"indexId": "1:5497952887792054610",
|
||||
"type": 28,
|
||||
"flags": 8
|
||||
},
|
||||
{
|
||||
"id": "5:7592685238899911344",
|
||||
"name": "version",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
|
||||
@@ -50,27 +50,27 @@ class FoodSearchDialog(
|
||||
|
||||
private val binding = DialogFoodSearchBinding.inflate(LayoutInflater.from(activity))
|
||||
private val list = mutableListOf<GoodsItem>()
|
||||
.apply {
|
||||
add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
|
||||
add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
|
||||
add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
|
||||
add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
|
||||
add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
|
||||
add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
|
||||
add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
|
||||
add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
|
||||
add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
|
||||
add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
|
||||
add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
|
||||
add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
||||
add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
||||
add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
||||
add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
||||
add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
||||
add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
||||
add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
||||
add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
||||
}
|
||||
// .apply {
|
||||
// add(GoodsItem(goodsId = "200001", goodsName = "土豆丝"))
|
||||
// add(GoodsItem(goodsId = "200002", goodsName = "土豆片"))
|
||||
// add(GoodsItem(goodsId = "200003", goodsName = "土豆丁"))
|
||||
// add(GoodsItem(goodsId = "200004", goodsName = "胡萝卜丝"))
|
||||
// add(GoodsItem(goodsId = "200005", goodsName = "胡萝卜片"))
|
||||
// add(GoodsItem(goodsId = "200006", goodsName = "胡萝卜丁"))
|
||||
// add(GoodsItem(goodsId = "200007", goodsName = "黄瓜丝"))
|
||||
// add(GoodsItem(goodsId = "200008", goodsName = "黄瓜片"))
|
||||
// add(GoodsItem(goodsId = "200009", goodsName = "黄瓜丁"))
|
||||
// add(GoodsItem(goodsId = "200010", goodsName = "洋葱丝"))
|
||||
// add(GoodsItem(goodsId = "200011", goodsName = "洋葱丁"))
|
||||
// add(GoodsItem(goodsId = "200012", goodsName = "A"))
|
||||
// add(GoodsItem(goodsId = "200013", goodsName = "B"))
|
||||
// add(GoodsItem(goodsId = "200014", goodsName = "C"))
|
||||
// add(GoodsItem(goodsId = "200015", goodsName = "D"))
|
||||
// add(GoodsItem(goodsId = "200016", goodsName = "E"))
|
||||
// add(GoodsItem(goodsId = "200017", goodsName = "F"))
|
||||
// add(GoodsItem(goodsId = "200018", goodsName = "G"))
|
||||
// add(GoodsItem(goodsId = "200019", goodsName = "H"))
|
||||
// }
|
||||
private val adapter = FoodAdapter(list).apply {
|
||||
isStateViewEnable = true
|
||||
setOnItemClickListener { _, _, position ->
|
||||
@@ -135,13 +135,13 @@ class FoodSearchDialog(
|
||||
|
||||
initObserver()
|
||||
|
||||
// // 若有默认食材名称,自动填充并触发搜索
|
||||
// //if (!defGoodsName.isNullOrBlank()) {
|
||||
// 若有默认食材名称,自动填充并触发搜索
|
||||
//if (!defGoodsName.isNullOrBlank()) {
|
||||
//binding.etSheetInput.setText(defGoodsName)
|
||||
// pageNo = 1
|
||||
// getGoodsList()
|
||||
// KeyboardUtil.hideKeyboard(binding.root)
|
||||
// //}
|
||||
pageNo = 1
|
||||
getGoodsList()
|
||||
KeyboardUtil.hideKeyboard(binding.root)
|
||||
//}
|
||||
}
|
||||
|
||||
/** 弹窗显示时在 Window.Callback 层提前拦截触摸,解决 BottomSheet 拖拽与列表滑动的手势冲突 */
|
||||
|
||||
@@ -186,25 +186,25 @@ class SeasoningSelectDialog(
|
||||
* 请求调料列表
|
||||
*/
|
||||
private fun getGoodsList() {
|
||||
// activity.netViewModel.queryGoodsList(
|
||||
// goodsType = "1",
|
||||
// pageNum = pageNo,
|
||||
// pageSize = PAGE_SIZE,
|
||||
// goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
||||
// )
|
||||
|
||||
// TODO: 联调时注释掉下方模拟数据,改为真实接口调用
|
||||
val nameList = listOf(
|
||||
"盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||
"料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||
activity.netViewModel.queryGoodsList(
|
||||
goodsType = "1",
|
||||
pageNum = pageNo,
|
||||
pageSize = PAGE_SIZE,
|
||||
goodsName = goodsName.takeUnless { it.isNullOrBlank() }
|
||||
)
|
||||
|
||||
val mockData = mutableListOf<GoodsItem>()
|
||||
nameList.forEachIndexed { index, name ->
|
||||
val padString = "${index + 1}".padStart(2, '0')
|
||||
mockData.add(GoodsItem(goodsId = "1000$padString", goodsName = name))
|
||||
}
|
||||
loadGoodsList(mockData)
|
||||
// // TODO: 联调时注释掉下方模拟数据,改为真实接口调用
|
||||
// val nameList = listOf(
|
||||
// "盐", "白砂糖","冰糖", "鸡精", "味精", "陈醋", "老抽", "生抽", "番茄酱", "胡椒粉", "孜然", "十三香",
|
||||
// "料酒", "白醋", "蚝油", "辣椒面", "辣椒酱", "豆瓣酱", "淀粉", "葱", "蒜", "姜", "香菜", "油"
|
||||
// )
|
||||
//
|
||||
// val mockData = mutableListOf<GoodsItem>()
|
||||
// nameList.forEachIndexed { index, name ->
|
||||
// val padString = "${index + 1}".padStart(2, '0')
|
||||
// mockData.add(GoodsItem(goodsId = "1000$padString", goodsName = name))
|
||||
// }
|
||||
// loadGoodsList(mockData)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,9 +4,14 @@ import com.shuwei.dish.match.base.GlobalData
|
||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||
import com.shuwei.dish.match.entity.FoodRecord
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PartMap
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.Url
|
||||
|
||||
@@ -58,4 +63,14 @@ interface ApiService {
|
||||
@Body param: MutableMap<String, Any>
|
||||
): ApiResponse<MutableList<GoodsItem>?>
|
||||
|
||||
/**
|
||||
* 提交采集图片数据
|
||||
*/
|
||||
@Multipart
|
||||
@POST
|
||||
suspend fun uploadFoodVectorData(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/addWeighingCollectionData",
|
||||
@PartMap params: Map<String, RequestBody>,
|
||||
@Part foodPics: List<MultipartBody.Part>
|
||||
): ApiResponse<List<String>?>
|
||||
}
|
||||
@@ -10,6 +10,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 网络请求 ViewModel,所有网络操作通过 RemoteRepository 发起,不直接依赖 apiService
|
||||
@@ -126,4 +131,44 @@ class NetViewModel(
|
||||
_goodsListState.value = repository.queryGoodsList(param)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传采集菜品信息 UI 状态流,UI 层通过 collect 监听
|
||||
*/
|
||||
private val _uploadCollectState = MutableStateFlow<UiState<List<String>?>>(UiState.Idle)
|
||||
val uploadCollectState: StateFlow<UiState<List<String>?>> = _uploadCollectState.asStateFlow()
|
||||
|
||||
/**
|
||||
* 上传采集菜品信息(触发 StateFlow,适合单次上传场景)
|
||||
*/
|
||||
fun uploadFoodVectorData(
|
||||
fileList: List<File>,
|
||||
params: MutableMap<String, RequestBody>
|
||||
) {
|
||||
val fileParts = fileList.map { file ->
|
||||
file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
|
||||
.let { MultipartBody.Part.createFormData("foodPics", file.name, it) }
|
||||
}
|
||||
viewModelScope.launch {
|
||||
_uploadCollectState.value = UiState.Loading
|
||||
_uploadCollectState.value = repository.uploadFoodVectorData(params, fileParts)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传采集菜品信息(直接返回结果,适合分批循环上传场景)
|
||||
* @param fileList 图片文件列表
|
||||
* @param params 附加参数
|
||||
* @return UiState 包装的结果
|
||||
*/
|
||||
suspend fun uploadFoodVectorDataBatch(
|
||||
fileList: List<File>,
|
||||
params: MutableMap<String, RequestBody>
|
||||
): UiState<List<String>?> {
|
||||
val fileParts = fileList.map { file ->
|
||||
file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
|
||||
.let { MultipartBody.Part.createFormData("foodPics", file.name, it) }
|
||||
}
|
||||
return repository.uploadFoodVectorData(params, fileParts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package com.shuwei.dish.match.net
|
||||
import com.shuwei.dish.match.entity.CookFoodEntity
|
||||
import com.shuwei.dish.match.entity.FoodRecord
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 网络数据仓库,封装所有 ApiService 调用
|
||||
@@ -90,4 +93,24 @@ class RemoteRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上次采集数据
|
||||
* @param params 参数
|
||||
* @param foodPics 图片
|
||||
* @return UiState 包装的结果,Success 携带列表数据,Error 携带错误信息
|
||||
*/
|
||||
suspend fun uploadFoodVectorData(
|
||||
params: Map<String, RequestBody>,
|
||||
foodPics: List<MultipartBody.Part>
|
||||
): UiState<List<String>?> {
|
||||
return try {
|
||||
val resp = apiService.uploadFoodVectorData(params = params, foodPics = foodPics)
|
||||
if (resp.isSuccess()) UiState.Success(resp.data)
|
||||
else UiState.Error(resp.code, resp.msg ?: "")
|
||||
} catch (e: Exception) {
|
||||
val ex = getApiException(e)
|
||||
UiState.Error("-1", ex.errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ data class Food(
|
||||
var name: String? = null,
|
||||
var foodIdx: Int = 0,
|
||||
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
|
||||
var foodVector: FloatArray? = null
|
||||
var foodVector: FloatArray? = null,
|
||||
var version: String? = null
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
|
||||
@@ -28,17 +28,23 @@ import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
|
||||
import com.shuwei.dish.match.dialog.Loading
|
||||
import com.shuwei.dish.match.entity.FoodCollectionBean
|
||||
import com.shuwei.dish.match.entity.GoodsItem
|
||||
import com.shuwei.dish.match.net.UiState
|
||||
import com.shuwei.dish.match.objbox.Food
|
||||
import com.shuwei.dish.match.objbox.FoodModule
|
||||
import com.shuwei.dish.match.objbox.ObjectBox
|
||||
import com.shuwei.dish.match.ui.CollectedFoodActivity
|
||||
import com.shuwei.dish.match.ui.SingleFragmentActivity
|
||||
import com.shuwei.dish.match.utils.BitmapSaver
|
||||
import com.shuwei.dish.match.utils.CameraUtils
|
||||
import com.shuwei.dish.match.utils.Debouncer
|
||||
import com.shuwei.dish.match.utils.ImageUploader
|
||||
import com.shuwei.dish.match.utils.ImageUtil
|
||||
import com.shuwei.dish.match.utils.LogSaveUtil
|
||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||
import com.shuwei.dish.match.utils.ext.dp
|
||||
import com.shuwei.dish.match.utils.ext.toast
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>() {
|
||||
@@ -159,13 +165,13 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
vector to file
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
log("操作失败:${e.message}")
|
||||
null to null
|
||||
}
|
||||
}
|
||||
// 回到主线程处理结果
|
||||
if (imageVector == null) {
|
||||
toast("操作失败")
|
||||
log("操作失败")
|
||||
hideWaitingDialog()
|
||||
return
|
||||
}
|
||||
@@ -237,16 +243,16 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
}
|
||||
|
||||
binding.btnSave.setOnClickListener {
|
||||
// if (checkedItem == null || checkedItem!!.isChecked.not()) {
|
||||
// toast("请选择菜品名称")
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
// val count = foodCollectionList.count { it.imageFile != null }
|
||||
// if (count == 0) {
|
||||
// toast("请拍摄菜品照片")
|
||||
// return@setOnClickListener
|
||||
// }
|
||||
// upload()
|
||||
if (checkedItem == null || checkedItem!!.isClicked.not()) {
|
||||
toast("请选择菜品名称")
|
||||
return@setOnClickListener
|
||||
}
|
||||
val count = vectorList.count { it.imageFile != null }
|
||||
if (count == 0) {
|
||||
toast("请拍摄菜品照片")
|
||||
return@setOnClickListener
|
||||
}
|
||||
upload()
|
||||
}
|
||||
|
||||
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
|
||||
@@ -281,67 +287,54 @@ class VectorCollectionFragment : BaseFragment<FragmentVectorCollectionBinding>()
|
||||
searchFood()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun upload() {
|
||||
// lifecycleScope.launch {
|
||||
// val totalFileCount = foodCollectionList.count { it.imageFile != null }
|
||||
// showWaitingDialog2("图片上传中0/$totalFileCount")
|
||||
// val params = HashMap<String, RequestBody>()
|
||||
// //params["placeId"] = restId.toRequestBody()
|
||||
// params["foodId"] = checkedItem!!.foodId.toRequestBody()
|
||||
// params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
|
||||
// params["version"] = GlobalData.foodModelVersion.toRequestBody()
|
||||
//
|
||||
// ImageUploader(totalList = foodCollectionList, uploadImage = { batch ->
|
||||
// val files = batch.map { it.imageFile }
|
||||
// val foodVectorList = batch.filter { it.imageVector != null }.map {
|
||||
// it.imageVector!!.joinToString(
|
||||
// separator = ",", prefix = "[", postfix = "]"
|
||||
// )
|
||||
// }
|
||||
// val foodVectorJson =
|
||||
// foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
// log("json=$foodVectorJson")
|
||||
// params["foodVector"] = foodVectorJson.toRequestBody()
|
||||
// viewModel?.uploadCollectFoodPics(files, params)
|
||||
// }, onProgress = { count, batch, idList ->
|
||||
// //batch.forEach {
|
||||
// // it.uploadSuccess = true
|
||||
// //}
|
||||
// runBlocking {
|
||||
// activity?.runOnUiThread {
|
||||
// showWaitingDialog("图片上传中$count/$totalFileCount")
|
||||
// }
|
||||
// val foodList = batch.mapIndexed { index, it ->
|
||||
// Food(
|
||||
// collectId = if (index < idList.size) idList[index] else null,
|
||||
// foodId = checkedItem!!.foodId,
|
||||
// foodName = checkedItem!!.foodName,
|
||||
// foodVector = it.imageVector,
|
||||
// version = GlobalData.foodModelVersion
|
||||
// )
|
||||
// }
|
||||
// ObjectBox.putAll(foodList)
|
||||
// activity?.runOnUiThread {
|
||||
// batch.forEach { it.isFinish = true }
|
||||
// collectionAdapter.notifyDataSetChanged()
|
||||
// }
|
||||
// }
|
||||
// }, onError = {
|
||||
// activity?.runOnUiThread {
|
||||
// binding.root.postDelayed({
|
||||
// hideWaitingDialog()
|
||||
// toast("上传失败,请稍后重试")
|
||||
// }, 1000)
|
||||
// }
|
||||
// }, onComplete = {
|
||||
// //vectorThread()
|
||||
// binding.root.postDelayed({
|
||||
// hideWaitingDialog()
|
||||
// toast("上传成功")
|
||||
// }, 1000)
|
||||
// }).processUploads()
|
||||
// }
|
||||
lifecycleScope.launch {
|
||||
val totalFileCount = vectorList.count { it.imageFile != null }
|
||||
showWaitingDialog("图片上传中0/$totalFileCount")
|
||||
val params = mutableMapOf<String, RequestBody>()
|
||||
params["goodsId"] = checkedItem!!.goodsId.toRequestBody()
|
||||
params["goodsName"] = checkedItem!!.goodsName!!.toRequestBody()
|
||||
//净材种类,逗号分隔
|
||||
// params["rawMaterialsTypes"] = "".toRequestBody()
|
||||
//params["version"] = "1.0.0".toRequestBody()
|
||||
|
||||
ImageUploader(totalList = vectorList, uploadImage = { batch ->
|
||||
val files = batch.mapNotNull { it.imageFile }
|
||||
val foodVectorList = batch.filter { it.imageVector != null }.map {
|
||||
it.imageVector!!.joinToString(
|
||||
separator = ",", prefix = "[", postfix = "]"
|
||||
)
|
||||
}
|
||||
val foodVectorJson =
|
||||
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||
log("json=$foodVectorJson")
|
||||
params["foodVector"] = foodVectorJson.toRequestBody()
|
||||
// 直接 await 单批上传结果,成功返回 id 列表,失败返回 null 触发 onError
|
||||
val result = currentActivity.netViewModel.uploadFoodVectorDataBatch(files, params)
|
||||
if (result is UiState.Success) result.data else null
|
||||
}, onProgress = { count, batch, idList ->
|
||||
showWaitingDialog("图片上传中$count/$totalFileCount")
|
||||
val foodList = batch.mapIndexed { index, it ->
|
||||
Food(
|
||||
name = checkedItem!!.goodsName,
|
||||
foodVector = it.imageVector,
|
||||
)
|
||||
}
|
||||
ObjectBox.putAll(foodList)
|
||||
batch.forEach { it.isFinish = true }
|
||||
vectorAdapter.notifyDataSetChanged()
|
||||
}, onError = {
|
||||
binding.root.postDelayed({
|
||||
hideWaitingDialog()
|
||||
toast("上传失败,请稍后重试")
|
||||
}, 1000)
|
||||
}, onComplete = {
|
||||
binding.root.postDelayed({
|
||||
hideWaitingDialog()
|
||||
toast("上传成功")
|
||||
}, 1000)
|
||||
}).processUploads()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.shuwei.dish.match.utils
|
||||
|
||||
import com.shuwei.dish.match.entity.FoodCollectionBean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class ImageUploader(
|
||||
private val totalList: List<FoodCollectionBean>,
|
||||
private val uploadImage: suspend (List<FoodCollectionBean>) -> List<String>?,
|
||||
private val onProgress: suspend (Int, List<FoodCollectionBean>, List<String>) -> Unit,
|
||||
private val onError: (List<FoodCollectionBean>) -> Unit,
|
||||
private val onComplete: () -> Unit
|
||||
) {
|
||||
companion object {
|
||||
private const val BATCH_SIZE = 3
|
||||
}
|
||||
private val uploadedCount = AtomicInteger(0)
|
||||
|
||||
suspend fun processUploads() {
|
||||
val batches = totalList.filter { it.imageVector!=null }.chunked(BATCH_SIZE)
|
||||
|
||||
for (batch in batches) {
|
||||
val idList = uploadImage(batch)
|
||||
if (idList.isNullOrEmpty()) {
|
||||
//println("上传失败,终止流程")
|
||||
onError(batch)
|
||||
return
|
||||
}
|
||||
val fileCount = batch.count { it.imageFile!=null }
|
||||
uploadedCount.addAndGet(fileCount)
|
||||
onProgress(uploadedCount.get(), batch, idList)
|
||||
//println("已上传 ${uploadedCount.get()}/$totalImages")
|
||||
}
|
||||
|
||||
onComplete()
|
||||
//println("流程完成,总计上传 ${uploadedCount.get()} 张图片")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 绿色圆角背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- 绿色圆角背景 + 白色垃圾桶图标,替代红色的 ic_delete_red.png -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- 绿色圆角背景 -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#00BC71" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding
|
||||
android:bottom="5dp"
|
||||
android:left="5dp"
|
||||
android:right="5dp"
|
||||
android:top="5dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="212dp"
|
||||
android:layout_height="159dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@drawable/bg_gray2">
|
||||
android:background="@drawable/bg_gray2"
|
||||
tools:ignore="ContentDescription">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
@@ -27,5 +29,6 @@
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_finish" />
|
||||
android:src="@drawable/ic_finish"
|
||||
app:tint="@color/dish_green" />
|
||||
</FrameLayout>
|
||||
Reference in New Issue
Block a user