添加注释和日志,添加自采添加物品添加后删除图片

This commit is contained in:
zxj
2025-07-14 11:07:04 +08:00
parent f1283ba11e
commit ab55bb0fdc
9 changed files with 103 additions and 176 deletions
@@ -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 -1
View File
@@ -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)