添加注释和日志,添加自采添加物品添加后删除图片
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user