搜索菜品入库改为自动拍照保存,取消手动拍照操作;增加向量数据库相关日志
This commit is contained in:
@@ -47,6 +47,7 @@ import com.sw.inbound.utils.BitmapCropper
|
||||
import com.sw.inbound.utils.CameraUtils
|
||||
import com.sw.inbound.utils.DateTimeUtils
|
||||
import com.sw.inbound.utils.ImageUtil
|
||||
import com.sw.inbound.utils.LogSaveUtil
|
||||
import com.sw.inbound.utils.PreciseDelayHandler
|
||||
import com.sw.inbound.utils.ext.dp
|
||||
import com.sw.inbound.utils.ext.gone
|
||||
@@ -229,7 +230,7 @@ abstract class GoodsListActivity : ComponentActivity() {
|
||||
|
||||
private var lastWeight = 0
|
||||
private fun recognizeWeight(weight: Int) {
|
||||
Timber.tag(TAG).d("recognizeWeight,lastWeight=$lastWeight,weight=$weight")
|
||||
logInfo("recognizeWeight,lastWeight=$lastWeight,weight=$weight")
|
||||
//if (this.lastWeight - weight >= 0) {
|
||||
// this.lastWeight = weight
|
||||
// //从秤上取物品中
|
||||
@@ -265,8 +266,7 @@ abstract class GoodsListActivity : ComponentActivity() {
|
||||
// Timber.tag(TAG).d("recognizeWeight, checkResult=true")
|
||||
// return
|
||||
// }
|
||||
Timber.tag(TAG)
|
||||
.d("recognizeWeight,recognizeFood:isShowRecognizeDialog=$isShowRecognizeDialog")
|
||||
logInfo("recognizeWeight,recognizeFood:isShowRecognizeDialog=$isShowRecognizeDialog")
|
||||
//两次相差5克内视作重量未变,不进行重新识别
|
||||
if (abs(lastWeight - weight) > 5) {
|
||||
recognizeFood()
|
||||
@@ -650,8 +650,7 @@ abstract class GoodsListActivity : ComponentActivity() {
|
||||
supplierId = supplierId,
|
||||
receiveGoodsInfos = goodsList
|
||||
)
|
||||
Timber.tag("SelfProcurementActivity")
|
||||
.d("receiptSubmit确认收货提交数据:${uploadInfo.toJsonString()}")
|
||||
logInfo("receiptSubmit确认收货提交数据:${uploadInfo.toJsonString()}")
|
||||
showWaitingDialog2("加载中")
|
||||
receiptViewModel.confirmReceipt(uploadInfo) { success ->
|
||||
hideWaitingDialog()
|
||||
@@ -690,8 +689,7 @@ abstract class GoodsListActivity : ComponentActivity() {
|
||||
buyToInventoryValue = it.buyToInventoryValue ?: ""
|
||||
)
|
||||
}
|
||||
Timber.tag("SelfProcurementActivity")
|
||||
.d("selfProcurementSubmit自采入库提交数据:${submitData.toJsonString()}")
|
||||
logInfo("selfProcurementSubmit自采入库提交数据:${submitData.toJsonString()}")
|
||||
showWaitingDialog2("加载中")
|
||||
receiptViewModel.addToWarehouse(submitData) { success ->
|
||||
hideWaitingDialog()
|
||||
@@ -796,4 +794,10 @@ abstract class GoodsListActivity : ComponentActivity() {
|
||||
tvTip?.text = tip
|
||||
}
|
||||
}
|
||||
|
||||
private fun logInfo(msg:String) {
|
||||
Timber.tag(TAG).d(msg)
|
||||
LogSaveUtil.saveLogFile(msg)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,6 +64,10 @@ class PurchaseOrderActivity : ComponentActivity() {
|
||||
putExtra(GoodsListActivity.IS_RECEIPT_PAGE, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Loading.show(this)
|
||||
viewModel.getOrderListNew(pageNum = 1, pageSize = 100) { suppliers ->
|
||||
runOnUiThread {
|
||||
@@ -82,6 +86,7 @@ class PurchaseOrderActivity : ComponentActivity() {
|
||||
binding.layoutEmptyView.visible()
|
||||
return
|
||||
}
|
||||
list.clear()
|
||||
list.addAll(suppliers)
|
||||
adapter.notifyDataSetChanged()
|
||||
binding.rvOrderList.visible()
|
||||
|
||||
@@ -32,9 +32,13 @@ class GoodsSearchDialog(
|
||||
override fun initView() {
|
||||
activity = getReceiptActivity()
|
||||
binding.ivClose.setOnClickListener { dismiss() }
|
||||
binding.ivGoodsSearch.setOnClickListener { searchGoods() }
|
||||
binding.ivGoodsSearch.setOnClickListener {
|
||||
pageNo = 1
|
||||
searchGoods()
|
||||
}
|
||||
binding.etInputGoods.let { v ->
|
||||
v.addOnActionSearchListener {
|
||||
pageNo = 1
|
||||
searchGoods()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,10 +284,10 @@ class GoodsStoreDialog(
|
||||
}
|
||||
|
||||
private fun confirmInputData() {
|
||||
if (funcType == 1 && imageUri == null) {
|
||||
context.toast("请采集图片")
|
||||
return
|
||||
}
|
||||
// if (funcType == 1 && imageUri == null) {
|
||||
// context.toast("请采集图片")
|
||||
// return
|
||||
// }
|
||||
val unitName = binding.tvProcurementUnit.text.trim().toString()
|
||||
if (unitName.isBlank()) {
|
||||
context.toast("请选择入库单位")
|
||||
@@ -328,11 +328,31 @@ class GoodsStoreDialog(
|
||||
return
|
||||
}
|
||||
if (funcType == 1) {
|
||||
collectGoodsVector()
|
||||
activity?.let { Loading.show(it) }
|
||||
cameraUtils.takePhoto { uri ->
|
||||
//imageUri = uri
|
||||
collectGoodsVector(uri) {
|
||||
activity?.uploadImage(goods.id!!, goods.goodsName!!, imageFile)
|
||||
}
|
||||
binding.root.postDelayed({
|
||||
Loading.dismiss()
|
||||
confirm(unitName, count, price, totalPrice)
|
||||
}, 500)
|
||||
}
|
||||
cameraUtils.addFailCallback {
|
||||
binding.root.postDelayed({
|
||||
Loading.dismiss()
|
||||
confirm(unitName, count, price, totalPrice)
|
||||
}, 500)
|
||||
}
|
||||
} else {
|
||||
confirm(unitName, count, price, totalPrice)
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirm(unitName:String, count:Double, price:Double, totalPrice:Double?) {
|
||||
val id = binding.tvProcurementUnit.tag.toString()
|
||||
val unitVo = goods.unitVoList!!.first { it.businessUnitId == id }
|
||||
activity?.uploadImage(goods.id!!, goods.goodsName!!, imageFile)
|
||||
confirmBlock(
|
||||
GoodsInfo(
|
||||
goodId = goods.id,
|
||||
@@ -389,15 +409,15 @@ class GoodsStoreDialog(
|
||||
}, 2600)
|
||||
}
|
||||
|
||||
private var imageUri: Uri? = null
|
||||
private val takePhotoCallback: (Uri) -> Unit = { uri ->
|
||||
takePhotoBinding?.let {
|
||||
imageUri = uri
|
||||
it.flCameraPreview.gone()
|
||||
it.cvImageBg.visible()
|
||||
it.ivGoodsImage.load(uri)
|
||||
}
|
||||
}
|
||||
// private var imageUri: Uri? = null
|
||||
// private val takePhotoCallback: (Uri) -> Unit = { uri ->
|
||||
// takePhotoBinding?.let {
|
||||
// imageUri = uri
|
||||
// it.flCameraPreview.gone()
|
||||
// it.cvImageBg.visible()
|
||||
// it.ivGoodsImage.load(uri)
|
||||
// }
|
||||
// }
|
||||
|
||||
private var previewView: PreviewView? = null
|
||||
private var takePhotoBinding: LayoutTakePhotoBinding? = null
|
||||
@@ -416,13 +436,13 @@ class GoodsStoreDialog(
|
||||
previewView = it.previewView
|
||||
it.flCameraPreview.visible()
|
||||
it.cvImageBg.gone()
|
||||
it.btnStartPreview.setOnClickListener { _ ->
|
||||
it.cvImageBg.gone()
|
||||
it.flCameraPreview.visible()
|
||||
}
|
||||
it.btnTakePhoto.setOnClickListener { _ ->
|
||||
cameraUtils.takePhoto(takePhotoCallback)
|
||||
}
|
||||
// it.btnStartPreview.setOnClickListener { _ ->
|
||||
// it.cvImageBg.gone()
|
||||
// it.flCameraPreview.visible()
|
||||
// }
|
||||
// it.btnTakePhoto.setOnClickListener { _ ->
|
||||
// cameraUtils.takePhoto(takePhotoCallback)
|
||||
// }
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -444,9 +464,9 @@ class GoodsStoreDialog(
|
||||
private var imageFile: File? = null
|
||||
|
||||
// private var box: Box<Food>? = null
|
||||
private fun collectGoodsVector() {
|
||||
private fun collectGoodsVector(imageUri:Uri, block: ()-> Unit = {}) {
|
||||
try {
|
||||
ImageUtil.uriToBitmap(context, imageUri!!)?.let { bitmap ->
|
||||
ImageUtil.uriToBitmap(context, imageUri)?.let { bitmap ->
|
||||
val cropBitmap = BitmapCropper.cropCenter(
|
||||
original = bitmap,
|
||||
targetWidth = 1000,
|
||||
@@ -477,18 +497,7 @@ class GoodsStoreDialog(
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
// Thread {
|
||||
// try {
|
||||
// val imageVector = FoodModule.bitmap2FloatArray(bitmap, true)
|
||||
// if (imageVector != null) {
|
||||
// ObjectBox.boxStore.runInTx {
|
||||
// box?.put(Food(name = goods.goodsName + goods.goodsCode, foodIdx = 0, foodVector = imageVector))
|
||||
// }
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// }.start()
|
||||
block()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.sw.inbound.MyApp
|
||||
import com.sw.inbound.objbox.ObjectBox.boxStore
|
||||
import com.sw.inbound.utils.AssetsTool
|
||||
import com.sw.inbound.utils.ImageUtil
|
||||
import com.sw.inbound.utils.LogSaveUtil
|
||||
import com.sw.inbound.utils.ext.toJsonString
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.kotlin.boxFor
|
||||
@@ -82,6 +83,7 @@ object FoodModule {
|
||||
return outputTensor.dataAsFloatArray
|
||||
} catch (e: OutOfMemoryError) {
|
||||
e.printStackTrace()
|
||||
logInfo("bitmap2FloatArray异常:${e.message}")
|
||||
} finally {
|
||||
if (rgb565Bitmap != null && rgb565Bitmap.isRecycled.not()) {
|
||||
rgb565Bitmap.recycle()
|
||||
@@ -130,20 +132,20 @@ object FoodModule {
|
||||
// query.close()
|
||||
// }
|
||||
|
||||
Timber.tag("FoodModule").d("queryFood向量:${floatArray.slice(0 until 50).toJsonString()}")
|
||||
logInfo("queryFood向量:${floatArray.slice(0 until 50).toJsonString()}")
|
||||
val idScoreList = ObjectBox.query(floatArray, queryCount)
|
||||
val nameScoreList = mutableListOf<IdNameScore>()
|
||||
idScoreList.forEach {
|
||||
val name = ObjectBox.get(it.id)?.name ?: ""
|
||||
nameScoreList.add(IdNameScore(id = it.id, name = name, score = it.score))
|
||||
}
|
||||
Timber.tag("FoodModule").d("queryFood数据:${nameScoreList.toJsonString()}")
|
||||
logInfo("queryFood数据:${nameScoreList.toJsonString()}")
|
||||
return nameScoreList
|
||||
}
|
||||
|
||||
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
||||
val floatArray = bitmap2FloatArray(bitmap, false) ?: return emptyList()
|
||||
Timber.tag("FoodModule").d("向量:${floatArray.toJsonString()}")
|
||||
logInfo("向量:${floatArray.toJsonString()}")
|
||||
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||
if (nameScoreList.isEmpty()) {
|
||||
return emptyList()
|
||||
@@ -168,7 +170,7 @@ object FoodModule {
|
||||
val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
||||
orderList.indexOf(it.name)
|
||||
})
|
||||
Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
||||
logInfo("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
||||
return sortedScoreList
|
||||
}
|
||||
|
||||
@@ -211,7 +213,7 @@ object FoodModule {
|
||||
val size = embeddingsList.size
|
||||
val size2 = labelsList.size
|
||||
val size3 = foodMap.size
|
||||
Log.d("TAG", "initDefFoodData: $size,$size2,$size3")
|
||||
logInfo("initDefFoodData: $size,$size2,$size3")
|
||||
embeddingsList.forEachIndexed { index, floatList ->
|
||||
val classIdx = labelsList[index]
|
||||
val foodName = foodMap["$classIdx"]
|
||||
@@ -227,7 +229,7 @@ object FoodModule {
|
||||
action()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.d("TAG", "initDefFoodData: ---${e.message}--")
|
||||
logInfo("initDefFoodData: ---${e.message}--")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +301,7 @@ object FoodModule {
|
||||
return cacheFile.absolutePath
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Timber.tag("FoodModule").d("文件拷贝失败 fileName=$fileName, error=${e.message}")
|
||||
logInfo("文件拷贝失败 fileName=$fileName, error=${e.message}")
|
||||
// 拷贝失败时删除残缺文件,避免下次读取到损坏文件
|
||||
if (cacheFile.exists()) {
|
||||
cacheFile.delete()
|
||||
@@ -311,4 +313,9 @@ object FoodModule {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun logInfo(msg:String) {
|
||||
Timber.tag("FoodModule").d(msg)
|
||||
LogSaveUtil.saveLogFile(msg)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import com.sw.inbound.MyApp
|
||||
import com.sw.inbound.utils.LogSaveUtil
|
||||
import io.objectbox.Box
|
||||
import io.objectbox.BoxStore
|
||||
import io.objectbox.BoxStoreBuilder
|
||||
@@ -86,8 +87,10 @@ object ObjectBox {
|
||||
// Failed to build BoxStore due to database file issue, store message;
|
||||
// checked in NoteListActivity to notify user.
|
||||
dbExceptionMessage = e.toString()
|
||||
LogSaveUtil.saveLogFile("ObjectBox.init异常1:$dbExceptionMessage")
|
||||
return
|
||||
} else {
|
||||
LogSaveUtil.saveLogFile("ObjectBox.init异常2:${e.message}")
|
||||
// Failed to build BoxStore due to developer error.
|
||||
throw e
|
||||
}
|
||||
@@ -141,12 +144,14 @@ object ObjectBox {
|
||||
} catch (e: FileCorruptException) {
|
||||
// 操作中触发损坏,尝试重建数据库
|
||||
e.printStackTrace()
|
||||
LogSaveUtil.saveLogFile("safeDbOp出现FileCorruptException异常:${e.message},${e.errorCode}")
|
||||
//val context = boxStore.context
|
||||
//deleteDbFiles(context)
|
||||
init(MyApp.instance!!)
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
LogSaveUtil.saveLogFile("safeDbOp出现其它异常:${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ class CameraUtils(private var activity: ComponentActivity) {
|
||||
private var photoCaptureHelper: PhotoCaptureHelper? = null
|
||||
|
||||
// private var isCameraReady = false
|
||||
fun takePhoto(callback: (Uri) -> Unit) {
|
||||
fun takePhoto(successCallback: (Uri) -> Unit) {
|
||||
cameraController?.let {
|
||||
if (photoCaptureHelper == null) {
|
||||
initCaptureHelper()
|
||||
}
|
||||
}
|
||||
photoCaptureHelper?.let {
|
||||
it.addSuccessCallback(callback)
|
||||
it.addSuccessCallback(successCallback)
|
||||
it.bindCameraCallback {
|
||||
bind()
|
||||
}
|
||||
@@ -28,6 +28,11 @@ class CameraUtils(private var activity: ComponentActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
private var failCallback:(()-> Unit)?=null
|
||||
fun addFailCallback(callback:()->Unit) {
|
||||
failCallback = callback
|
||||
}
|
||||
|
||||
private fun initCaptureHelper() {
|
||||
photoCaptureHelper = PhotoCaptureHelper(
|
||||
context = activity,
|
||||
@@ -35,6 +40,7 @@ class CameraUtils(private var activity: ComponentActivity) {
|
||||
onSuccess = {},
|
||||
onError = { msg ->
|
||||
//toast(msg)
|
||||
failCallback?.invoke()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ class CrashHandler private constructor(private val context: Context) :
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CrashHandler"
|
||||
private const val CRASH_REPORTS_DIR = "crash_reports"
|
||||
private const val LOG_LINES = 500 // 收集最近500行日志
|
||||
|
||||
@Volatile
|
||||
@@ -109,7 +108,7 @@ class CrashHandler private constructor(private val context: Context) :
|
||||
val crashInfo = collectCrashInfo(thread, ex)
|
||||
|
||||
// 保存日志文件
|
||||
saveCrashInfoToFile(crashInfo)
|
||||
LogSaveUtil.saveLogFile("yyyy-MM-dd_HH-mm-ss",crashInfo)
|
||||
|
||||
// 这里可以添加其他处理逻辑,比如上传到服务器等
|
||||
}
|
||||
@@ -203,36 +202,11 @@ class CrashHandler private constructor(private val context: Context) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveLog(logInfo:String) {
|
||||
val time = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()).format(Date())
|
||||
val fileName = "crash_$time.log"
|
||||
|
||||
val crashDir = getCrashDir()
|
||||
|
||||
val crashFile = File(crashDir, fileName)
|
||||
FileOutputStream(crashFile).use { it.write(logInfo.toByteArray()) }
|
||||
|
||||
Timber.tag(TAG).d("Crash info saved to: ${crashFile.absolutePath}")
|
||||
}
|
||||
|
||||
private fun saveCrashInfoToFile(crashInfo: String) {
|
||||
try {
|
||||
saveLog(crashInfo)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "Error saving crash info to file")
|
||||
try {
|
||||
saveLog("Error saving crash info to file:${e.message}")
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "save exception")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理旧的崩溃日志
|
||||
*/
|
||||
fun cleanupOldCrashReports(maxAgeDays: Int = 7) {
|
||||
val crashDir = getCrashDir()
|
||||
val crashDir = LogSaveUtil.getCrashDir()
|
||||
if (!crashDir.exists() || !crashDir.isDirectory) return
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
@@ -245,17 +219,4 @@ class CrashHandler private constructor(private val context: Context) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCrashDir():File {
|
||||
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
|
||||
var crashDir = File(context.filesDir, CRASH_REPORTS_DIR)
|
||||
if (!crashDir.exists()) {
|
||||
crashDir.mkdirs()
|
||||
}
|
||||
|
||||
if (!(crashDir.exists())) {
|
||||
crashDir = File(context.cacheDir, CRASH_REPORTS_DIR)
|
||||
}
|
||||
return crashDir
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.sw.inbound.utils
|
||||
|
||||
import com.sw.inbound.MyApp
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
object LogSaveUtil {
|
||||
|
||||
private const val TAG = "LogSaveUtil"
|
||||
private const val CRASH_REPORTS_DIR = "crash_reports"
|
||||
|
||||
public fun saveLogFile(msg: String) {
|
||||
saveLogFile("yyyy-MM-dd", msg)
|
||||
}
|
||||
|
||||
public fun saveLogFile(pattern: String, msg: String) {
|
||||
try {
|
||||
saveLog(pattern, msg)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "Error saving crash info to file")
|
||||
try {
|
||||
saveLog(pattern, "Error saving crash info to file:${e.message}")
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(TAG).e(e, "save exception")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveLog(pattern: String, logInfo: String) {
|
||||
val time = SimpleDateFormat(pattern, Locale.getDefault()).format(Date())
|
||||
val fileName = "crash_$time.log"
|
||||
|
||||
val crashDir = getCrashDir()
|
||||
|
||||
val crashFile = File(crashDir, fileName)
|
||||
|
||||
val saveTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
|
||||
FileOutputStream(crashFile, true).use { it.write("$saveTime||$logInfo\n".toByteArray()) }
|
||||
|
||||
Timber.tag(TAG).d("Crash info saved to: ${crashFile.absolutePath}")
|
||||
}
|
||||
|
||||
fun getCrashDir(): File {
|
||||
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
|
||||
var crashDir = File(MyApp.instance!!.filesDir, CRASH_REPORTS_DIR)
|
||||
if (!crashDir.exists()) {
|
||||
crashDir.mkdirs()
|
||||
}
|
||||
|
||||
if (!(crashDir.exists())) {
|
||||
crashDir = File(MyApp.instance!!.cacheDir, CRASH_REPORTS_DIR)
|
||||
}
|
||||
return crashDir
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,7 +80,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
|
||||
Reference in New Issue
Block a user