添加注释和日志,添加自采添加物品添加后删除图片
This commit is contained in:
@@ -15,10 +15,29 @@ object GlobalData {
|
||||
*/
|
||||
var imageUri: Uri? = null
|
||||
|
||||
/**
|
||||
* 存储方式列表
|
||||
*/
|
||||
var storageTypeList: List<DictType> = arrayListOf()
|
||||
|
||||
/**
|
||||
* 物品类型列表
|
||||
*/
|
||||
var goodsTypeList: List<DictType> = arrayListOf()
|
||||
|
||||
/**
|
||||
* 仓库类型列表
|
||||
*/
|
||||
var warehouseTypeList: List<DictType> = arrayListOf()
|
||||
|
||||
/**
|
||||
* 供应商列表
|
||||
*/
|
||||
var supplierTypeList: List<DictType> = arrayListOf()
|
||||
|
||||
/**
|
||||
* 单位类型列表
|
||||
*/
|
||||
var unitTypeList: List<DictType> = arrayListOf()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.sw.inbound
|
||||
|
||||
import android.app.Application
|
||||
import com.sw.inbound.utils.ContextUtils
|
||||
import com.sw.inbound.utils.CrashHandler
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
|
||||
@@ -17,6 +18,6 @@ class MyApp : Application() {
|
||||
ContextUtils.initAppContext(this)
|
||||
|
||||
// 初始化崩溃处理器
|
||||
// CrashHandler.init(this)
|
||||
CrashHandler.init(this)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ abstract class BaseRepository {
|
||||
// 对于HTTP异常,尝试从响应体中获取错误信息
|
||||
val errorBody = e.response()?.errorBody()?.string()
|
||||
val errorMsg = if (!errorBody.isNullOrEmpty()) {
|
||||
// 可以尝试解析errorBody为JSON获取更详细的错误信息
|
||||
errorBody
|
||||
} else {
|
||||
"HTTP Error: ${e.code()} - ${e.message()}"
|
||||
|
||||
@@ -15,12 +15,15 @@ import com.sw.inbound.model.response.SupplierResponse
|
||||
import com.sw.inbound.model.response.User
|
||||
import com.sw.inbound.network.api.ApiService
|
||||
import com.sw.inbound.utils.ContextUtils
|
||||
import com.sw.inbound.utils.ImageUtils
|
||||
import com.sw.inbound.utils.FileUtils
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* 远程数据处理
|
||||
*/
|
||||
class RemoteRepository @Inject constructor(
|
||||
private val apiService: ApiService
|
||||
) : BaseRepository() {
|
||||
@@ -86,11 +89,11 @@ class RemoteRepository @Inject constructor(
|
||||
)
|
||||
|
||||
val part =
|
||||
ImageUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri)
|
||||
FileUtils.genRequestPart(context = ContextUtils.getAppContext(), imageUri = uri)
|
||||
if (part == null) {
|
||||
ApiResponse(code = -1, msg = "解析图片失败", data = "")
|
||||
} else {
|
||||
apiService.uploadImage(codeRequestBody, file = part)
|
||||
apiService.uploadImage(code = codeRequestBody, file = part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +78,6 @@ fun SingleSelectButtonGroup(
|
||||
var selectedOption by remember { mutableStateOf(options.firstOrNull() ?: "") }
|
||||
|
||||
val lazyGridState = rememberLazyGridState()
|
||||
// val canLoadMore by viewModel.canLoadMore.collectAsState()
|
||||
// val isMoreLoading by viewModel.isMoreLoading.collectAsState()
|
||||
var pageNum by remember { mutableIntStateOf(1) }
|
||||
|
||||
// 检测是否滚动到底部
|
||||
@@ -103,7 +101,6 @@ fun SingleSelectButtonGroup(
|
||||
val selectedColor = Color(0xFFD9E3F9)
|
||||
val unselectedColor = Color(0xFFDCDCF0)
|
||||
|
||||
|
||||
LazyVerticalGrid(
|
||||
state = lazyGridState,
|
||||
columns = GridCells.Fixed(2), // 每行2列
|
||||
@@ -112,7 +109,7 @@ fun SingleSelectButtonGroup(
|
||||
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
|
||||
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
|
||||
) {
|
||||
items(items = options) { option ->
|
||||
items(items = options, key = { it.goodsId ?: it.goodsCode!! }) { option ->
|
||||
Box(
|
||||
modifier = modifier
|
||||
.border(
|
||||
@@ -143,7 +140,6 @@ fun SingleSelectButtonGroup(
|
||||
softWrap = false
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,66 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
object FileUtils {
|
||||
/**
|
||||
* 从Uri获取File
|
||||
* example: file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg
|
||||
*/
|
||||
private fun getFileFromUri(context: Context, uri: Uri): File? {
|
||||
Timber.d("getFileFromUri uri = ${uri.scheme}")
|
||||
return when (uri.scheme) {
|
||||
"file" -> File(uri.path ?: return null)
|
||||
"content" -> {
|
||||
try {
|
||||
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
|
||||
val cacheDir = context.cacheDir
|
||||
val file = File.createTempFile(
|
||||
"upload_${System.currentTimeMillis()}",
|
||||
".jpg",
|
||||
cacheDir
|
||||
)
|
||||
file.outputStream().use { output ->
|
||||
inputStream.copyTo(output)
|
||||
}
|
||||
file
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过uri生成http请求体
|
||||
*/
|
||||
fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? {
|
||||
Timber.d("genRequestPart imageUri = $imageUri")
|
||||
// 1. 从Uri获取文件
|
||||
val file = getFileFromUri(context, imageUri)
|
||||
if (file == null) {
|
||||
Timber.e("getFileFromUri file is null")
|
||||
return null
|
||||
}
|
||||
// 2. 创建请求体
|
||||
val requestFile = file
|
||||
.asRequestBody("application/octet-stream".toMediaTypeOrNull())
|
||||
val imagePart = MultipartBody.Part.createFormData(
|
||||
"file",
|
||||
file.name,
|
||||
requestFile
|
||||
)
|
||||
return imagePart
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Uri删除文件
|
||||
* @param context 上下文
|
||||
@@ -16,6 +73,7 @@ object FileUtils {
|
||||
* @return Boolean 是否删除成功
|
||||
*/
|
||||
fun deleteFileWithUri(context: Context, uri: Uri): Boolean {
|
||||
Timber.d("deleteFileWithUri uri = ${uri.scheme}")
|
||||
return when {
|
||||
// 1. 处理 content:// 类型的Uri (MediaStore)
|
||||
uri.scheme.equals("content", ignoreCase = true) -> {
|
||||
@@ -34,6 +92,7 @@ object FileUtils {
|
||||
|
||||
// 删除Content Uri文件
|
||||
private fun deleteContentUriFile(context: Context, uri: Uri): Boolean {
|
||||
Timber.d("deleteContentUriFile uri = ${uri.scheme}")
|
||||
return try {
|
||||
context.contentResolver.delete(uri, null, null) > 0
|
||||
} catch (e: SecurityException) {
|
||||
@@ -44,6 +103,7 @@ object FileUtils {
|
||||
false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -51,6 +111,7 @@ object FileUtils {
|
||||
// Android 10+删除MediaStore文件
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean {
|
||||
Timber.d("deleteMediaStoreFile uri = ${uri.scheme}")
|
||||
val contentResolver = context.contentResolver
|
||||
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
||||
|
||||
@@ -66,24 +127,29 @@ object FileUtils {
|
||||
}
|
||||
} ?: false
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除File Uri文件
|
||||
private fun deleteFileUriFile(uri: Uri): Boolean {
|
||||
Timber.d("deleteFileUriFile uri = $uri")
|
||||
return try {
|
||||
File(uri.path ?: return false).delete()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// 直接通过路径删除文件
|
||||
private fun deleteFileFromPath(path: String): Boolean {
|
||||
Timber.d("deleteFileFromPath path = $path")
|
||||
return try {
|
||||
File(path).delete()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.sw.inbound.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg
|
||||
*/
|
||||
object ImageUtils {
|
||||
// 从Uri获取File
|
||||
private fun getFileFromUri(context: Context, uri: Uri): File? {
|
||||
return when (uri.scheme) {
|
||||
"file" -> File(uri.path ?: return null)
|
||||
"content" -> {
|
||||
try {
|
||||
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
|
||||
val cacheDir = context.cacheDir
|
||||
val file = File.createTempFile(
|
||||
"upload_${System.currentTimeMillis()}",
|
||||
".jpg",
|
||||
cacheDir
|
||||
)
|
||||
file.outputStream().use { output ->
|
||||
inputStream.copyTo(output)
|
||||
}
|
||||
file
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? {
|
||||
Timber.d("genRequestPart imageUri = $imageUri")
|
||||
// 1. 从Uri获取文件
|
||||
val file = getFileFromUri(context, imageUri)
|
||||
if (file == null) {
|
||||
Timber.e("getFileFromUri file is null")
|
||||
return null
|
||||
}
|
||||
// 2. 创建请求体
|
||||
val requestFile = RequestBody.create(
|
||||
"application/octet-stream".toMediaTypeOrNull(),
|
||||
file
|
||||
)
|
||||
val imagePart = MultipartBody.Part.createFormData(
|
||||
"file",
|
||||
file.name,
|
||||
requestFile
|
||||
)
|
||||
return imagePart
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package com.sw.inbound.utils
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
|
||||
open class TextFieldState(
|
||||
initialText: String = "",
|
||||
initialSelection: TextRange = TextRange(initialText.length)
|
||||
) {
|
||||
private var _value by mutableStateOf(TextFieldValue(initialText, initialSelection))
|
||||
|
||||
open var value: TextFieldValue
|
||||
get() = _value
|
||||
set(newValue) {
|
||||
_value = newValue
|
||||
}
|
||||
|
||||
open fun updateFromString(text: String) {
|
||||
_value = TextFieldValue(text, TextRange(text.length))
|
||||
}
|
||||
|
||||
val text: String get() = _value.text
|
||||
}
|
||||
|
||||
class AmountFieldState(initialAmount: String = "") : TextFieldState(initialAmount) {
|
||||
// 获取原始数字字符串(不含格式字符)
|
||||
fun getRawAmount(): String = value.text.filter { it.isDigit() }
|
||||
|
||||
// 获取Double类型的金额值
|
||||
fun getAmountValue(): Double = value.text
|
||||
.filter { it.isDigit() || it == '.' }
|
||||
.toDoubleOrNull() ?: 0.0
|
||||
|
||||
// 重写value的setter以实现金额格式化
|
||||
override var value: TextFieldValue
|
||||
get() = super.value
|
||||
set(newValue) {
|
||||
super.value = formatAmountValue(newValue)
|
||||
}
|
||||
|
||||
// 从外部更新金额(如从数据库加载)
|
||||
override fun updateFromString(amount: String) {
|
||||
super.updateFromString(formatAmount(amount))
|
||||
}
|
||||
|
||||
private fun formatAmountValue(input: TextFieldValue): TextFieldValue {
|
||||
val filtered = input.text.filter { it.isDigit() }
|
||||
val formatted = formatAmount(filtered)
|
||||
|
||||
// 计算新光标位置
|
||||
val newCursorPos = calculateNewCursorPosition(
|
||||
originalText = input.text,
|
||||
originalSelection = input.selection,
|
||||
filteredText = filtered,
|
||||
formattedText = formatted
|
||||
)
|
||||
|
||||
return TextFieldValue(
|
||||
text = formatted,
|
||||
selection = TextRange(newCursorPos)
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatAmount(amount: String): String {
|
||||
val filtered = amount.filter { it.isDigit() }
|
||||
return when {
|
||||
filtered.isEmpty() -> "0.00"
|
||||
filtered.length <= 2 -> "0.${filtered.padStart(2, '0')}"
|
||||
else -> "${filtered.dropLast(2)}.${filtered.takeLast(2)}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateNewCursorPosition(
|
||||
originalText: String,
|
||||
originalSelection: TextRange,
|
||||
filteredText: String,
|
||||
formattedText: String
|
||||
): Int {
|
||||
// 如果在末尾添加,保持光标在末尾
|
||||
if (originalSelection.start >= originalText.length) {
|
||||
return formattedText.length
|
||||
}
|
||||
|
||||
// 计算原始文本中光标前的数字个数
|
||||
val digitsBeforeCursor = originalText
|
||||
.substring(0, originalSelection.start)
|
||||
.count { it.isDigit() }
|
||||
|
||||
// 在格式化文本中找到对应位置
|
||||
var digitCount = 0
|
||||
formattedText.forEachIndexed { index, char ->
|
||||
if (char.isDigit()) {
|
||||
digitCount++
|
||||
if (digitCount > digitsBeforeCursor) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formattedText.length
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import com.sw.inbound.model.response.DictType
|
||||
import com.sw.inbound.model.response.SearchGoodsInfo
|
||||
import com.sw.inbound.repository.RemoteRepository
|
||||
import com.sw.inbound.sdk.SensorScaleUtils
|
||||
import com.sw.inbound.utils.ContextUtils
|
||||
import com.sw.inbound.utils.FileUtils
|
||||
import com.sw.inbound.utils.ToastUtils
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -247,6 +249,9 @@ class SelfProcurementViewModel @Inject constructor(
|
||||
_purchaseList.value = emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索物品
|
||||
*/
|
||||
fun searchGoodsInfoList(
|
||||
goodsName: String,
|
||||
pageNo: Int = 1,
|
||||
@@ -310,6 +315,9 @@ class SelfProcurementViewModel @Inject constructor(
|
||||
_amountUserInput.value = boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加商品
|
||||
*/
|
||||
fun addGoodsInfo() {
|
||||
val imageUri = GlobalData.imageUri
|
||||
if (imageUri == null) {
|
||||
@@ -336,6 +344,7 @@ class SelfProcurementViewModel @Inject constructor(
|
||||
parseResponse(response)
|
||||
return@launchWithLoading
|
||||
}
|
||||
FileUtils.deleteFileWithUri(context = ContextUtils.getAppContext(), uri = imageUri)
|
||||
_showAddProductDialog.value = false
|
||||
_goodsAddParam.value = GoodsAddParam()
|
||||
val searchFirst = response.data!!.records?.get(0)
|
||||
|
||||
Reference in New Issue
Block a user