优化
This commit is contained in:
Generated
+2
-2
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-01-09T06:05:40.833191600Z">
|
<DropdownSelection timestamp="2026-02-02T06:44:20.183314400Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="Default" identifier="serial=192.168.1.35:5555;connection=d6972001" />
|
<DeviceId pluginId="Default" identifier="serial=192.168.1.32:5555;connection=c1793c60" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ object GlobalData {
|
|||||||
/**
|
/**
|
||||||
* 具体业务baseurl
|
* 具体业务baseurl
|
||||||
*/
|
*/
|
||||||
// var appBaseUrl: String = "http://192.168.1.201:14801"
|
var appBaseUrl2: String = "http://192.168.1.201:14801"
|
||||||
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
|
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class MyApp : App() {
|
|||||||
Timber.d("deviceId = $deviceId")
|
Timber.d("deviceId = $deviceId")
|
||||||
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
||||||
GlobalData.deviceId = deviceId
|
GlobalData.deviceId = deviceId
|
||||||
|
GlobalData.appBaseUrl = if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) GlobalData.appBaseUrl2 else GlobalData.appBaseUrl
|
||||||
ObjectBox.init(this)
|
ObjectBox.init(this)
|
||||||
// 初始化崩溃处理器
|
// 初始化崩溃处理器
|
||||||
CrashHandler.init(this)
|
CrashHandler.init(this)
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ import com.sw.dualscreen.R
|
|||||||
import com.sw.dualscreen.databinding.DialogWaitingBinding
|
import com.sw.dualscreen.databinding.DialogWaitingBinding
|
||||||
import com.sw.dualscreen.model.IActivityResult
|
import com.sw.dualscreen.model.IActivityResult
|
||||||
import com.sw.dualscreen.model.response.PostEvent
|
import com.sw.dualscreen.model.response.PostEvent
|
||||||
|
import com.sw.dualscreen.utils.FileUtil
|
||||||
import com.sw.dualscreen.view.CustomDialog
|
import com.sw.dualscreen.view.CustomDialog
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -131,6 +133,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
launchPermissionCallback?.callback(result)
|
launchPermissionCallback?.callback(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun log(msg: String) {
|
||||||
|
Timber.d(msg)
|
||||||
|
FileUtil.saveLog(msg)
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onPostEvent(event: PostEvent) {
|
fun onPostEvent(event: PostEvent) {
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.sw.dualscreen.activity
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
@@ -13,16 +14,12 @@ import com.sw.dualscreen.ext.addOnActionSearchListener
|
|||||||
import com.sw.dualscreen.ext.gone
|
import com.sw.dualscreen.ext.gone
|
||||||
import com.sw.dualscreen.ext.hideKeyboard
|
import com.sw.dualscreen.ext.hideKeyboard
|
||||||
import com.sw.dualscreen.ext.visible
|
import com.sw.dualscreen.ext.visible
|
||||||
import com.sw.dualscreen.model.response.ResetBoxEvent
|
|
||||||
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
import com.sw.dualscreen.objbox.CollectedFoodInfo
|
||||||
import com.sw.dualscreen.objbox.Food
|
|
||||||
import com.sw.dualscreen.objbox.ObjectBox
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import io.objectbox.Box
|
import kotlinx.coroutines.launch
|
||||||
import io.objectbox.kotlin.boxFor
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
|
|
||||||
class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||||
|
|
||||||
@@ -51,8 +48,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
binding.root.setOnClickListener { it.hideKeyboard() }
|
||||||
//binding.root.setOnClickListener { it.hideKeyboard() }
|
|
||||||
binding.rvFoodList.let {
|
binding.rvFoodList.let {
|
||||||
it.layoutManager = LinearLayoutManager(this)
|
it.layoutManager = LinearLayoutManager(this)
|
||||||
it.adapter = adapter
|
it.adapter = adapter
|
||||||
@@ -103,9 +99,6 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
getCollectGoods()
|
getCollectGoods()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private lateinit var box: Box<Food>
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun getCollectGoods(searchName: String? = null) {
|
private fun getCollectGoods(searchName: String? = null) {
|
||||||
// if (box == null) {
|
// if (box == null) {
|
||||||
@@ -199,16 +192,13 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
showWaitingDialog("加载中……")
|
showWaitingDialog("加载中……")
|
||||||
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
|
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
|
||||||
if (deleteSuccess) {
|
if (deleteSuccess) {
|
||||||
Thread {
|
// Thread {}.start()
|
||||||
val filterFoodList = box.all.filter { it.foodName == food.foodName }
|
lifecycleScope.launch {
|
||||||
|
val filterFoodList = ObjectBox.filter(food.foodName)
|
||||||
filterFoodList.forEach { it.isDel = true }
|
filterFoodList.forEach { it.isDel = true }
|
||||||
box.put(filterFoodList)
|
if (filterFoodList.isNotEmpty()) {
|
||||||
// val filterIdList = filterFoodList.map { it.id }
|
ObjectBox.putAll(filterFoodList)
|
||||||
////// ObjectBox.boxStore.runInTx {
|
}
|
||||||
//// box.removeByIds(filterIdList)
|
|
||||||
////// EventBus.getDefault().post(ResetBoxEvent())
|
|
||||||
////// }
|
|
||||||
|
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
hideWaitingDialog()
|
hideWaitingDialog()
|
||||||
@@ -218,7 +208,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
|||||||
loadEmptyView()
|
loadEmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.sw.dualscreen.activity
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.databinding.ActivityInitBinding
|
import com.sw.dualscreen.databinding.ActivityInitBinding
|
||||||
import com.sw.dualscreen.model.response.FoodVector
|
import com.sw.dualscreen.model.response.FoodVector
|
||||||
@@ -10,13 +11,13 @@ import com.sw.dualscreen.objbox.FoodModule
|
|||||||
import com.sw.dualscreen.objbox.ObjectBox
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
import io.objectbox.Box
|
import kotlinx.coroutines.launch
|
||||||
import io.objectbox.kotlin.boxFor
|
|
||||||
|
|
||||||
class InitActivity : BaseActivity<ActivityInitBinding>() {
|
class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val PAGE_SIZE = 100
|
private const val PAGE_SIZE = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
private val userViewModel by viewModels<UserViewModel>()
|
private val userViewModel by viewModels<UserViewModel>()
|
||||||
private var pageNum = 1
|
private var pageNum = 1
|
||||||
|
|
||||||
@@ -28,12 +29,14 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
return ActivityInitBinding.inflate(layoutInflater)
|
return ActivityInitBinding.inflate(layoutInflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCollectedFoodVector(block:()-> Unit) {
|
private fun getCollectedFoodVector(block: () -> Unit) {
|
||||||
userViewModel.getCollectedFoodVector(param = mutableMapOf(
|
userViewModel.getCollectedFoodVector(
|
||||||
"pageNum" to "$pageNum",
|
param = mutableMapOf(
|
||||||
"pageSize" to "$PAGE_SIZE",
|
"pageNum" to "$pageNum",
|
||||||
"version" to GlobalData.foodModelVersion
|
"pageSize" to "$PAGE_SIZE",
|
||||||
)) { items ->
|
"version" to GlobalData.foodModelVersion
|
||||||
|
)
|
||||||
|
) { items ->
|
||||||
val list = items ?: emptyList()
|
val list = items ?: emptyList()
|
||||||
if (pageNum == 1 && list.isEmpty()) {
|
if (pageNum == 1 && list.isEmpty()) {
|
||||||
block()
|
block()
|
||||||
@@ -51,32 +54,37 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
|
|
||||||
|
// private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
|
||||||
private fun saveFoodVector(list: List<FoodVector>) {
|
private fun saveFoodVector(list: List<FoodVector>) {
|
||||||
val vectorList = list.map { vt ->
|
lifecycleScope.launch {
|
||||||
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")?.map { it.toFloatOrNull()?:0.0f }?.toFloatArray()
|
val vectorList = list.map { vt ->
|
||||||
Food(
|
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")
|
||||||
collectId = vt.id,
|
?.map { it.toFloatOrNull() ?: 0.0f }?.toFloatArray()
|
||||||
foodId = vt.foodId,
|
Food(
|
||||||
foodName = vt.foodName,
|
collectId = vt.id,
|
||||||
version = vt.version,
|
foodId = vt.foodId,
|
||||||
foodVector = foodVector
|
foodName = vt.foodName,
|
||||||
)
|
version = vt.version,
|
||||||
|
foodVector = foodVector
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ObjectBox.putAll(vectorList)
|
||||||
}
|
}
|
||||||
val box = ObjectBox.boxStore.boxFor(Food::class)
|
|
||||||
box.put(vectorList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
showWaitingDialog("加载中……")
|
showWaitingDialog("加载中……")
|
||||||
FoodModule.init(this) {
|
lifecycleScope.launch {
|
||||||
getCollectedFoodVector {
|
FoodModule.init(this@InitActivity) {
|
||||||
runOnUiThread {
|
getCollectedFoodVector {
|
||||||
hideWaitingDialog()
|
runOnUiThread {
|
||||||
binding.root.postDelayed({
|
hideWaitingDialog()
|
||||||
goMainActivity()
|
binding.root.postDelayed({
|
||||||
}, 1000)
|
goMainActivity()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.sw.dualscreen.activity
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -21,11 +22,11 @@ import androidx.camera.core.ImageCaptureException
|
|||||||
import androidx.camera.core.Preview
|
import androidx.camera.core.Preview
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.GlobalKey
|
import com.sw.dualscreen.GlobalKey
|
||||||
import com.sw.dualscreen.adapter.DialogSearchFoodAdapter
|
|
||||||
import com.sw.dualscreen.adapter.MainFoodListAdapter
|
import com.sw.dualscreen.adapter.MainFoodListAdapter
|
||||||
import com.sw.dualscreen.databinding.ActivityMainBinding
|
import com.sw.dualscreen.databinding.ActivityMainBinding
|
||||||
import com.sw.dualscreen.dialog.RemindDialog
|
import com.sw.dualscreen.dialog.RemindDialog
|
||||||
@@ -63,22 +64,21 @@ import com.sw.plate.utils.ToastUtils
|
|||||||
import com.sw.plate.utils.arcface.facedb.FaceDatabase
|
import com.sw.plate.utils.arcface.facedb.FaceDatabase
|
||||||
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
|
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||||
import io.objectbox.Box
|
|
||||||
import io.objectbox.kotlin.boxFor
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,7 +111,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
searchFoodList[index].isChecked = index == position
|
searchFoodList[index].isChecked = index == position
|
||||||
}
|
}
|
||||||
val item = searchFoodList[position]
|
val item = searchFoodList[position]
|
||||||
Timber.d("itemClick ${item.foodName}, position = $position")
|
log("itemClick ${item.foodName}, position = $position")
|
||||||
item.photoUri = null
|
item.photoUri = null
|
||||||
checkedItem = item
|
checkedItem = item
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
@@ -149,7 +149,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
super.initialize()
|
super.initialize()
|
||||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
|
||||||
isPageVisible = true
|
isPageVisible = true
|
||||||
addBackEventListener()
|
addBackEventListener()
|
||||||
initView()
|
initView()
|
||||||
@@ -177,6 +176,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
updateDateTime()
|
updateDateTime()
|
||||||
binding.ivSetting.setOnClickListener {
|
binding.ivSetting.setOnClickListener {
|
||||||
|
isRefreshPage = false
|
||||||
startActivity(Intent(this, SettingActivity::class.java))
|
startActivity(Intent(this, SettingActivity::class.java))
|
||||||
}
|
}
|
||||||
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
|
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
|
||||||
@@ -221,8 +221,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
pauseAnalysis()
|
pauseAnalysis()
|
||||||
recognizeWeight = lastWeight
|
recognizeWeight = lastWeight
|
||||||
Timber.tag(TAG)
|
log("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}")
|
||||||
.d("registerDataChange,副屏updateFood调用前耗时:${System.currentTimeMillis() - startTime}")
|
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
presentation?.updateFood(foodInfo)
|
presentation?.updateFood(foodInfo)
|
||||||
binding.tvFoodName.text = foodInfo.foodName
|
binding.tvFoodName.text = foodInfo.foodName
|
||||||
@@ -239,7 +238,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
override fun registerDataChange() {
|
override fun registerDataChange() {
|
||||||
super.registerDataChange()
|
super.registerDataChange()
|
||||||
SensorScaleUtils.addWeightListener { weight ->
|
SensorScaleUtils.addWeightListener { weight ->
|
||||||
Timber.d("registerDataChange weight = $weight")
|
log("registerDataChange weight = $weight")
|
||||||
if (isPageVisible.not() || isStartRecognize.not()) {
|
if (isPageVisible.not() || isStartRecognize.not()) {
|
||||||
if (ActivityManager.currentActivity() is MainActivity) {
|
if (ActivityManager.currentActivity() is MainActivity) {
|
||||||
isPageVisible = true
|
isPageVisible = true
|
||||||
@@ -247,7 +246,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
return@addWeightListener
|
return@addWeightListener
|
||||||
}
|
}
|
||||||
val isWeightChange = weight - lastWeight > 0.05
|
val isWeightChange = abs(weight - lastWeight) > 0.05
|
||||||
|
log("recognizeByWeight---00001,weight=$weight,lastWeight=$lastWeight,isWeightChange=$isWeightChange")
|
||||||
recognizeByWeight(weight, isWeightChange)
|
recognizeByWeight(weight, isWeightChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,16 +256,19 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
presentation?.updateWeight(weight)
|
presentation?.updateWeight(weight)
|
||||||
|
|
||||||
if (weight <= 0.005) {
|
if (weight <= 0.005) {
|
||||||
|
log("recognizeByWeight---00002,未超过5克")
|
||||||
////余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
|
////余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
|
||||||
isRecognitionFood = true
|
isRecognitionFood = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWeightChange && isRecognitionFood) { // 大于50g
|
if (isWeightChange && isRecognitionFood) { // 大于50g
|
||||||
|
log("recognizeByWeight---00003,mealPickupMode=${presentation?.mealPickupMode}")
|
||||||
if (presentation?.mealPickupMode == 1) {
|
if (presentation?.mealPickupMode == 1) {
|
||||||
isRecognitionFood = false
|
isRecognitionFood = false
|
||||||
}
|
}
|
||||||
debouncer.debounce {
|
debouncer.debounce {
|
||||||
|
log("recognizeByWeight---00004")
|
||||||
recognizeFood()
|
recognizeFood()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -282,31 +285,30 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
LightManager.closeRedLight()
|
LightManager.closeRedLight()
|
||||||
LightManager.openGreenLight()
|
LightManager.openGreenLight()
|
||||||
showWaitingDialog("识别中,请稍后……")
|
showWaitingDialog("识别中,请稍后……")
|
||||||
takePhoto(
|
takePhoto(successCallback = { photoUri ->
|
||||||
successCallback = { photoUri ->
|
Thread {
|
||||||
Thread {
|
onTakePhotoSuccess(photoUri)
|
||||||
onTakePhotoSuccess(photoUri)
|
}.start()
|
||||||
}.start()
|
failCount = 0
|
||||||
failCount = 0
|
}, failureCallback = {
|
||||||
}, failureCallback = {
|
runOnUiThread {
|
||||||
runOnUiThread {
|
if (failCount >= 10) {
|
||||||
if (failCount >= 10) {
|
ToastUtils.showToast("相机异常,请稍后重试")
|
||||||
//ToastUtils.showToast("相机异常,请稍后重试")
|
shutdownCamera()
|
||||||
shutdownCamera()
|
setupCamera()
|
||||||
setupCamera()
|
return@runOnUiThread
|
||||||
return@runOnUiThread
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
failCount++
|
}
|
||||||
hideWaitingDialog()
|
failCount++
|
||||||
|
hideWaitingDialog()
|
||||||
|
|
||||||
shutdownCamera()
|
shutdownCamera()
|
||||||
setupCamera()
|
setupCamera()
|
||||||
|
|
||||||
binding.root.postDelayed({
|
binding.root.postDelayed({
|
||||||
recognizeFood()
|
recognizeFood()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,7 +320,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
private fun updateFoodInfo(list: MutableList<FoodInfo>, scoreList: List<IdNameScore>) {
|
private fun updateFoodInfo(list: MutableList<FoodInfo>, scoreList: List<IdNameScore>) {
|
||||||
val queryData = GsonUtils.toJson(list)
|
val queryData = GsonUtils.toJson(list)
|
||||||
val recData = GsonUtils.toJson(scoreList)
|
val recData = GsonUtils.toJson(scoreList)
|
||||||
Timber.d("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData")
|
log("registerDataChange识别后查询接口数据:$queryData,识别数据:$recData")
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
binding.tvToSearch.let {
|
binding.tvToSearch.let {
|
||||||
it.text = "未查询到,手动搜索"
|
it.text = "未查询到,手动搜索"
|
||||||
@@ -359,24 +361,21 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupCamera() {
|
private fun setupCamera() {
|
||||||
Timber.d("setupCamera")
|
log("setupCamera")
|
||||||
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||||
cameraProviderFuture!!.addListener({
|
cameraProviderFuture!!.addListener({
|
||||||
val cameraProvider = cameraProviderFuture!!.get()
|
val cameraProvider = cameraProviderFuture!!.get()
|
||||||
|
|
||||||
// 1. 创建预览用例
|
// 1. 创建预览用例
|
||||||
val preview = Preview.Builder()
|
val preview = Preview.Builder().build().also {
|
||||||
.build()
|
it.setSurfaceProvider(binding.previewView.surfaceProvider)
|
||||||
.also {
|
}
|
||||||
it.setSurfaceProvider(binding.previewView.surfaceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 创建图像分析用例(用于副屏显示)
|
// 2. 创建图像分析用例(用于副屏显示)
|
||||||
imageAnalysis = ImageAnalysis.Builder()
|
imageAnalysis = ImageAnalysis.Builder()
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
// .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担
|
// .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担
|
||||||
.build()
|
.build().also { analysis ->
|
||||||
.also { analysis ->
|
|
||||||
analysis.setAnalyzer(executor) { imageProxy ->
|
analysis.setAnalyzer(executor) { imageProxy ->
|
||||||
// 仅在步骤1时传递
|
// 仅在步骤1时传递
|
||||||
if (isAnalyzing) {
|
if (isAnalyzing) {
|
||||||
@@ -389,10 +388,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 创建拍照用例
|
// 3. 创建拍照用例
|
||||||
imageCapture = ImageCapture.Builder()
|
imageCapture =
|
||||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||||
// .setTargetResolution(Size(1920, 1080)) // 设置拍照分辨率
|
// .setTargetResolution(Size(1920, 1080)) // 设置拍照分辨率
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// 4. 选择摄像头
|
// 4. 选择摄像头
|
||||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
@@ -405,20 +404,20 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
cameraProvider.bindToLifecycle(
|
cameraProvider.bindToLifecycle(
|
||||||
this, cameraSelector, preview, imageAnalysis, imageCapture
|
this, cameraSelector, preview, imageAnalysis, imageCapture
|
||||||
)
|
)
|
||||||
} catch (exc: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(exc.message)
|
log("setupCamera方法异常:${e.message}")
|
||||||
}
|
}
|
||||||
}, ContextCompat.getMainExecutor(this))
|
}, ContextCompat.getMainExecutor(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pauseAnalysis() {
|
fun pauseAnalysis() {
|
||||||
Timber.d("pauseAnalysis")
|
log("pauseAnalysis")
|
||||||
isAnalyzing = false
|
isAnalyzing = false
|
||||||
imageAnalysis?.clearAnalyzer()
|
imageAnalysis?.clearAnalyzer()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resumeAnalysis() {
|
fun resumeAnalysis() {
|
||||||
Timber.d("resumeAnalysis isAnalyzing = $isAnalyzing")
|
log("resumeAnalysis isAnalyzing = $isAnalyzing")
|
||||||
if (isAnalyzing) return
|
if (isAnalyzing) return
|
||||||
// isAnalyzing = true
|
// isAnalyzing = true
|
||||||
imageAnalysis?.setAnalyzer(executor) { imageProxy ->
|
imageAnalysis?.setAnalyzer(executor) { imageProxy ->
|
||||||
@@ -434,7 +433,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
* 停止相机识别
|
* 停止相机识别
|
||||||
*/
|
*/
|
||||||
private fun shutdownCamera() {
|
private fun shutdownCamera() {
|
||||||
Timber.d("shutdownCamera")
|
log("shutdownCamera")
|
||||||
try {
|
try {
|
||||||
// 1. 首先停止图像分析
|
// 1. 首先停止图像分析
|
||||||
imageAnalysis?.clearAnalyzer()
|
imageAnalysis?.clearAnalyzer()
|
||||||
@@ -449,13 +448,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
// 4. 停止副屏更新
|
// 4. 停止副屏更新
|
||||||
isAnalyzing = false
|
isAnalyzing = false
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Error shutting down camera")
|
log("Error shutting down camera:${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拍照功能
|
// 拍照功能
|
||||||
private fun takePhoto(successCallback: (Uri) -> Unit, failureCallback: () -> Unit) {
|
private fun takePhoto(successCallback: (Uri) -> Unit, failureCallback: () -> Unit) {
|
||||||
Timber.d("takePhoto")
|
log("takePhoto")
|
||||||
// 临时停止分析以避免干扰
|
// 临时停止分析以避免干扰
|
||||||
isAnalyzing = false
|
isAnalyzing = false
|
||||||
|
|
||||||
@@ -463,21 +462,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
val executor = ContextCompat.getMainExecutor(this)
|
val executor = ContextCompat.getMainExecutor(this)
|
||||||
val cacheDir = cacheDir
|
val cacheDir = cacheDir
|
||||||
val photoFile = File.createTempFile(
|
val photoFile = File.createTempFile(
|
||||||
"IMG_${System.currentTimeMillis()}",
|
"IMG_${System.currentTimeMillis()}", ".jpg", cacheDir
|
||||||
".jpg",
|
|
||||||
cacheDir
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val cacheOutputOptions =
|
val cacheOutputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
||||||
ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
|
||||||
// 执行拍照
|
// 执行拍照
|
||||||
imageCapture?.takePicture(
|
imageCapture?.takePicture(
|
||||||
cacheOutputOptions,
|
cacheOutputOptions, executor, object : ImageCapture.OnImageSavedCallback {
|
||||||
executor,
|
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
|
||||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
val savedUri = outputFileResults.savedUri
|
val savedUri = outputFileResults.savedUri
|
||||||
Timber.d("takePhoto savedUri = $savedUri")
|
log("takePhoto savedUri = $savedUri")
|
||||||
if (savedUri != null) {
|
if (savedUri != null) {
|
||||||
successCallback(savedUri)
|
successCallback(savedUri)
|
||||||
}
|
}
|
||||||
@@ -486,14 +480,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(exception: ImageCaptureException) {
|
override fun onError(exception: ImageCaptureException) {
|
||||||
Timber.e("拍照失败: ${exception.message}")
|
log("takePhoto拍照失败: ${exception.message}")
|
||||||
failureCallback()
|
failureCallback()
|
||||||
//ToastUtils.showToast("拍照失败: ${exception.message}")
|
//ToastUtils.showToast("拍照失败: ${exception.message}")
|
||||||
// 恢复图像分析
|
// 恢复图像分析
|
||||||
isAnalyzing = true
|
isAnalyzing = true
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSecondaryDisplay(): Display? {
|
private fun getSecondaryDisplay(): Display? {
|
||||||
@@ -522,7 +515,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
).apply {
|
).apply {
|
||||||
setStepChangeCallback { step ->
|
setStepChangeCallback { step ->
|
||||||
if (step == 1) {
|
if (step == 1) {
|
||||||
Timber.d("setStepChangeCallback${step}")
|
log("setStepChangeCallback${step}")
|
||||||
updateCurrentFood(null)
|
updateCurrentFood(null)
|
||||||
bottomSheetDialog?.dismiss()
|
bottomSheetDialog?.dismiss()
|
||||||
}
|
}
|
||||||
@@ -560,12 +553,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
} else {
|
} else {
|
||||||
//副屏未显示重新加载
|
//副屏未显示重新加载
|
||||||
isRecognitionFood = true
|
isRecognitionFood = true
|
||||||
|
log("onResume-00001")
|
||||||
if (presentation == null || !presentation!!.isShowing) {
|
if (presentation == null || !presentation!!.isShowing) {
|
||||||
|
log("onResume-00002,副屏未显示,加载副屏")
|
||||||
lastWeight = 0.0
|
lastWeight = 0.0
|
||||||
setupSecondaryDisplay()
|
setupSecondaryDisplay()
|
||||||
presentation?.step1FoodRecognizing()
|
presentation?.step1FoodRecognizing()
|
||||||
} else {
|
} else {
|
||||||
|
log("onResume-00003,副屏显示,更新副屏, isRefreshPage=$isRefreshPage")
|
||||||
if (isRefreshPage) {
|
if (isRefreshPage) {
|
||||||
|
isRefreshPage = false
|
||||||
//已显示副屏,则进行更新
|
//已显示副屏,则进行更新
|
||||||
val weight = lastWeight
|
val weight = lastWeight
|
||||||
lastWeight = 0.0
|
lastWeight = 0.0
|
||||||
@@ -576,7 +573,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
recognizeByWeight(weight, true)
|
recognizeByWeight(weight, true)
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
isRefreshPage = !isRefreshPage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +623,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
notEnoughOneBlock: () -> Unit = {},
|
notEnoughOneBlock: () -> Unit = {},
|
||||||
block: () -> Unit
|
block: () -> Unit
|
||||||
) {
|
) {
|
||||||
Timber.tag(TAG).d("foodWeight=$foodWeight,eatWeight=$eatWeight")
|
log("foodWeight=$foodWeight,eatWeight=$eatWeight")
|
||||||
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
|
||||||
// if (pickupMode == 0) {
|
// if (pickupMode == 0) {
|
||||||
// if (foodWeight <= 5) {
|
// if (foodWeight <= 5) {
|
||||||
@@ -735,10 +731,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
private var isRefreshPage = false
|
private var isRefreshPage = false
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
// @Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onClickBackEvent(event: ClickBackEvent) {
|
// fun onClickBackEvent(event: ClickBackEvent) {
|
||||||
isRefreshPage = false
|
// isRefreshPage = false
|
||||||
}
|
// }
|
||||||
|
|
||||||
private fun clickPayButton() {
|
private fun clickPayButton() {
|
||||||
if (checkedItem == null) {
|
if (checkedItem == null) {
|
||||||
@@ -761,7 +757,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
//余量计量
|
//余量计量
|
||||||
eatWeight = (recognizeWeight - lastWeight) * 1000
|
eatWeight = (recognizeWeight - lastWeight) * 1000
|
||||||
foodWeight = recognizeWeight * 1000
|
foodWeight = recognizeWeight * 1000
|
||||||
Timber.d("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, lastWeight=$lastWeight")
|
log("eatWeight=$eatWeight, recognizeWeight=$recognizeWeight, lastWeight=$lastWeight")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//不计费
|
//不计费
|
||||||
@@ -782,7 +778,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
eatNum = eatNum,
|
eatNum = eatNum,
|
||||||
userId = null,
|
userId = null,
|
||||||
) {
|
) {
|
||||||
// TODO: 暂不关闭首页副屏
|
isRefreshPage = false
|
||||||
//presentation?.dismiss()
|
//presentation?.dismiss()
|
||||||
startActivity(Intent(this, PayActivity::class.java).apply {
|
startActivity(Intent(this, PayActivity::class.java).apply {
|
||||||
putExtra(PayActivity.FOOD_INFO, checkedItem!!)
|
putExtra(PayActivity.FOOD_INFO, checkedItem!!)
|
||||||
@@ -814,13 +810,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
* 上传菜品信息
|
* 上传菜品信息
|
||||||
*/
|
*/
|
||||||
private suspend fun uploadCollectFoodPics(
|
private suspend fun uploadCollectFoodPics(
|
||||||
foodInfo: FoodInfo,
|
foodInfo: FoodInfo, imageFile: File?, imageVector: FloatArray?
|
||||||
imageFile: File?,
|
|
||||||
imageVector: FloatArray?
|
|
||||||
) {
|
) {
|
||||||
if (imageFile == null || imageVector == null) {
|
if (imageFile == null || imageVector == null) {
|
||||||
Timber.tag(TAG)
|
log("是否null判断,imageFile == null:${imageFile == null},imageVector == null:${imageVector == null}")
|
||||||
.d("是否null判断,imageFile == null:${imageFile == null},imageVector == null:${imageVector == null}")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val foodId = foodInfo.foodId
|
val foodId = foodInfo.foodId
|
||||||
@@ -834,53 +827,45 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
val vectors = listOf(imageVector)
|
val vectors = listOf(imageVector)
|
||||||
val foodVectorList = vectors.map {
|
val foodVectorList = vectors.map {
|
||||||
it.joinToString(
|
it.joinToString(
|
||||||
separator = ",",
|
separator = ",", prefix = "[", postfix = "]"
|
||||||
prefix = "[",
|
|
||||||
postfix = "]"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val foodVectorJson =
|
val foodVectorJson =
|
||||||
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
|
||||||
Timber.tag(TAG).d("json=$foodVectorJson")
|
log("json=$foodVectorJson")
|
||||||
params["foodVector"] = foodVectorJson.toRequestBody()
|
params["foodVector"] = foodVectorJson.toRequestBody()
|
||||||
val idList = viewModel.uploadCollectFoodPics(files, params)
|
val idList = viewModel.uploadCollectFoodPics(files, params)
|
||||||
if (idList.isNullOrEmpty()) {
|
if (idList.isNullOrEmpty()) {
|
||||||
loadRemindDialog("上传失败,未返回向量数据id")
|
loadRemindDialog("上传失败,未返回向量数据id")
|
||||||
Timber.tag(TAG).d("idList为空")
|
log("idList为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Timber.tag(TAG)
|
val all = ObjectBox.getAll()
|
||||||
.d("uploadCollectFoodPics-已采集向量总数:${box.all.filter { it.isDel.not() }.size}条")
|
val size = all.filter { it.isDel.not() }.size
|
||||||
val filterList = box.all.filter { it.collectId == idList[0] }
|
log("uploadCollectFoodPics-已采集向量总数:${size}条")
|
||||||
|
val filterList = all.filter { it.collectId == idList[0] }
|
||||||
if (filterList.isNotEmpty()) {
|
if (filterList.isNotEmpty()) {
|
||||||
loadRemindDialog("返回的id:${idList[0]}已存在")
|
loadRemindDialog("返回的id:${idList[0]}已存在")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Thread {
|
ObjectBox.put(
|
||||||
box.put(
|
Food(
|
||||||
Food(
|
collectId = idList[0],
|
||||||
collectId = idList[0],
|
foodId = foodId,
|
||||||
foodId = foodId,
|
foodName = foodName,
|
||||||
foodName = foodName,
|
foodVector = imageVector,
|
||||||
foodVector = imageVector,
|
version = foodModelVersion
|
||||||
version = foodModelVersion
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}.start()
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRemindDialog(msg: String) {
|
private fun loadRemindDialog(msg: String) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
RemindDialog(
|
RemindDialog(
|
||||||
context = context,
|
context = context, content = msg, confirmBlock = {}).show()
|
||||||
content = msg,
|
|
||||||
confirmBlock = {}
|
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var box: Box<Food>
|
|
||||||
|
|
||||||
private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) {
|
private fun uri2File(uri: Uri, block: (File?, FloatArray?) -> Unit) {
|
||||||
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
||||||
val imageVector = try {
|
val imageVector = try {
|
||||||
@@ -892,7 +877,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
val imageFile = BitmapSaver.saveToAppFilesDir(
|
val imageFile = BitmapSaver.saveToAppFilesDir(
|
||||||
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
)
|
)
|
||||||
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}")
|
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${imageFile?.absolutePath}")
|
||||||
block(imageFile, imageVector)
|
block(imageFile, imageVector)
|
||||||
if (bitmap.isRecycled.not()) {
|
if (bitmap.isRecycled.not()) {
|
||||||
bitmap.recycle()
|
bitmap.recycle()
|
||||||
@@ -913,8 +898,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
// private val initialDelay = 5 * 60 * 1000L
|
// private val initialDelay = 5 * 60 * 1000L
|
||||||
// private val dealyMillis = 10 * 60 * 1000L
|
// private val dealyMillis = 10 * 60 * 1000L
|
||||||
private val initialDelay = 60 * 1000L
|
private val initialDelay = 30 * 1000L
|
||||||
private val dealyMillis = 30 * 1000L
|
private val dealyMillis = 60 * 1000L
|
||||||
private var taskPageNo = 1
|
private var taskPageNo = 1
|
||||||
fun startFaceTask() {
|
fun startFaceTask() {
|
||||||
faceTaskJob =
|
faceTaskJob =
|
||||||
@@ -936,8 +921,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
updateFaceData(list)
|
updateFaceData(list)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -952,9 +936,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
} else {
|
} else {
|
||||||
//保存数据
|
//保存数据
|
||||||
val faceEntity = FaceEntity(
|
val faceEntity = FaceEntity(
|
||||||
model.userId,
|
model.userId, null, Base64.decode(model.faceFeatureStr)
|
||||||
null,
|
|
||||||
Base64.decode(model.faceFeatureStr)
|
|
||||||
).also {
|
).also {
|
||||||
it.userType = "1"
|
it.userType = "1"
|
||||||
}
|
}
|
||||||
@@ -988,66 +970,71 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
private fun onTakePhotoSuccess(photoUri: Uri) {
|
private fun onTakePhotoSuccess(photoUri: Uri) {
|
||||||
lastPhotoUri = photoUri
|
lastPhotoUri = photoUri
|
||||||
Timber.tag(TAG).d("main,takePhoto耗时:${System.currentTimeMillis() - startTime}")
|
log("registerDataChange main,takePhoto耗时:${System.currentTimeMillis() - startTime}")
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
Timber.d("registerDataChange photoUri = ${photoUri.path}")
|
log("registerDataChange photoUri = ${photoUri.path}")
|
||||||
// viewModel.getIdentifiedFoodList()
|
// viewModel.getIdentifiedFoodList()
|
||||||
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap ->
|
lifecycleScope.launch {
|
||||||
Timber.tag(TAG).d("main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}")
|
ImageUtil.uriToBitmap(this@MainActivity, photoUri)?.let { bitmap ->
|
||||||
startTime = System.currentTimeMillis()
|
queryFoodData(bitmap)
|
||||||
Timber.d("registerDataChange photoUri 拿到bitmap")
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun queryFoodData(bitmap: Bitmap) {
|
||||||
|
log("registerDataChange main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}")
|
||||||
|
startTime = System.currentTimeMillis()
|
||||||
|
log("registerDataChange photoUri 拿到bitmap")
|
||||||
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
||||||
Timber.d("registerDataChange photoUri bitmap裁剪完成")
|
log("registerDataChange registerDataChange photoUri bitmap裁剪完成")
|
||||||
val file = BitmapSaver.saveToAppFilesDir(
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
)
|
)
|
||||||
Timber.tag(TAG)
|
log("registerDataChange main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}")
|
||||||
.d("main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}")
|
startTime = System.currentTimeMillis()
|
||||||
startTime = System.currentTimeMillis()
|
log("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
||||||
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
//val nameList = FoodModule.queryFood(bitmap)
|
||||||
//val nameList = FoodModule.queryFood(bitmap)
|
val scoreList = FoodModule.getFoodScoreList(bitmap)
|
||||||
val scoreList = FoodModule.getFoodScoreList(bitmap)
|
val foodName = if (scoreList.isNotEmpty()) {
|
||||||
val recDataJson = GsonUtils.toJson(scoreList)
|
val recDataJson = GsonUtils.toJson(scoreList)
|
||||||
Timber.tag(TAG).d("main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
|
log("registerDataChange main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
Timber.d("registerDataChange photoUri 拿到识别数据:$recDataJson")
|
log("registerDataChange photoUri 拿到识别数据:$recDataJson")
|
||||||
val nameList = scoreList.map { it.name }
|
val nameList = scoreList.map { it.name }
|
||||||
val foodName = nameList.joinToString(separator = ",")
|
nameList.joinToString(separator = ",")
|
||||||
|
} else ""
|
||||||
|
|
||||||
|
hideWaitingDialog()
|
||||||
|
log("registerDataChange photoUri 识别数据名称:$foodName")
|
||||||
|
runOnUiThread {
|
||||||
|
binding.flPay.run {
|
||||||
|
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
||||||
|
//0-计费,1-不计费
|
||||||
|
if (chargeMode == 0) visible() else gone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(foodName)) {
|
||||||
|
LightManager.closeGreenLight()
|
||||||
|
LightManager.closeRedLight()
|
||||||
|
//binding.layoutRescan.visibility = View.VISIBLE
|
||||||
|
|
||||||
hideWaitingDialog()
|
|
||||||
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.flPay.run {
|
binding.tvToSearch.let {
|
||||||
val chargeMode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0)
|
it.text = "未识别到,手动搜索"
|
||||||
//0-计费,1-不计费
|
it.visible()
|
||||||
if (chargeMode == 0) visible() else gone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(foodName)) {
|
} else {
|
||||||
LightManager.closeGreenLight()
|
//binding.layoutRescan.visibility = View.GONE
|
||||||
LightManager.closeRedLight()
|
showWaitingDialog("正在查询菜品信息,请稍后……")
|
||||||
//binding.layoutRescan.visibility = View.VISIBLE
|
viewModel.getFoodInfo(foodName) { list ->
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
binding.tvToSearch.let {
|
hideWaitingDialog()
|
||||||
it.text = "未识别到,手动搜索"
|
log("registerDataChange main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}")
|
||||||
it.visible()
|
startTime = System.currentTimeMillis()
|
||||||
}
|
updateFoodInfo(list.toMutableList(), scoreList)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//binding.layoutRescan.visibility = View.GONE
|
|
||||||
showWaitingDialog("正在查询菜品信息,请稍后……")
|
|
||||||
viewModel.getFoodInfo(foodName) { list ->
|
|
||||||
runOnUiThread {
|
|
||||||
hideWaitingDialog()
|
|
||||||
Timber.tag(TAG)
|
|
||||||
.d("main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}")
|
|
||||||
startTime = System.currentTimeMillis()
|
|
||||||
updateFoodInfo(list.toMutableList(), scoreList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
|||||||
initBackDispatcher()
|
initBackDispatcher()
|
||||||
binding.include.let {
|
binding.include.let {
|
||||||
it.ivPageBack.setOnClickListener {
|
it.ivPageBack.setOnClickListener {
|
||||||
// val intent = Intent(this, MainActivity::class.java)
|
//// val intent = Intent(this, MainActivity::class.java)
|
||||||
// startActivity(intent)
|
//// startActivity(intent)
|
||||||
EventBus.getDefault().post(ClickBackEvent())
|
// EventBus.getDefault().post(ClickBackEvent())
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
it.tvPageTitle.text = "设置"
|
it.tvPageTitle.text = "设置"
|
||||||
@@ -73,13 +73,13 @@ class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
|||||||
private lateinit var backPressedCallback: OnBackPressedCallback
|
private lateinit var backPressedCallback: OnBackPressedCallback
|
||||||
private fun initBackDispatcher() {
|
private fun initBackDispatcher() {
|
||||||
// 创建回调,true表示初始启用状态
|
// 创建回调,true表示初始启用状态
|
||||||
backPressedCallback = object : OnBackPressedCallback(true) {
|
// backPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
// override fun handleOnBackPressed() {
|
||||||
EventBus.getDefault().post(ClickBackEvent())
|
//// EventBus.getDefault().post(ClickBackEvent())
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// 注册回调,使用lifecycleOwner确保生命周期安全
|
// // 注册回调,使用lifecycleOwner确保生命周期安全
|
||||||
onBackPressedDispatcher.addCallback(this, backPressedCallback)
|
// onBackPressedDispatcher.addCallback(this, backPressedCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import com.sw.dualscreen.activity.CollectedFoodActivity
|
|||||||
import com.sw.dualscreen.activity.SettingActivity
|
import com.sw.dualscreen.activity.SettingActivity
|
||||||
import com.sw.dualscreen.adapter.CollectFoodListAdapter
|
import com.sw.dualscreen.adapter.CollectFoodListAdapter
|
||||||
import com.sw.dualscreen.adapter.FoodCollectionAdapter
|
import com.sw.dualscreen.adapter.FoodCollectionAdapter
|
||||||
import com.sw.dualscreen.adapter.DialogSearchFoodAdapter
|
|
||||||
import com.sw.dualscreen.databinding.FragmentCollectBinding
|
import com.sw.dualscreen.databinding.FragmentCollectBinding
|
||||||
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
|
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
|
||||||
import com.sw.dualscreen.ext.clickWithDebounce
|
import com.sw.dualscreen.ext.clickWithDebounce
|
||||||
@@ -35,14 +34,11 @@ import com.sw.dualscreen.utils.Debouncer
|
|||||||
import com.sw.dualscreen.utils.ImageUploader
|
import com.sw.dualscreen.utils.ImageUploader
|
||||||
import com.sw.dualscreen.utils.ImageUtil
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import io.objectbox.Box
|
|
||||||
import io.objectbox.kotlin.boxFor
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.getValue
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
||||||
@@ -55,13 +51,6 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
private var selectedFoodId: String? = ""
|
private var selectedFoodId: String? = ""
|
||||||
private var selectedFoodName: String? = ""
|
private var selectedFoodName: String? = ""
|
||||||
|
|
||||||
private var box: Box<Food>? = null
|
|
||||||
fun initBox() {
|
|
||||||
if (box == null) {
|
|
||||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
|
private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
|
||||||
repeat(MAX_COUNT) {
|
repeat(MAX_COUNT) {
|
||||||
add(FoodCollectionBean(isShowCamera = true))
|
add(FoodCollectionBean(isShowCamera = true))
|
||||||
@@ -138,6 +127,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
settingActivity?.hideWaitingDialog()
|
settingActivity?.hideWaitingDialog()
|
||||||
ToastUtils.showToast("程序异常${e.message}")
|
ToastUtils.showToast("程序异常${e.message}")
|
||||||
|
settingActivity?.log("程序异常${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,13 +142,14 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
ToastUtils.showToast("操作失败")
|
ToastUtils.showToast("操作失败")
|
||||||
|
settingActivity?.log("操作失败:${e.message}")
|
||||||
settingActivity?.hideWaitingDialog()
|
settingActivity?.hideWaitingDialog()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val file = BitmapSaver.saveToAppFilesDir(
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
)
|
)
|
||||||
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
settingActivity?.log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||||
|
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
foodCollectionList[index].let {
|
foodCollectionList[index].let {
|
||||||
@@ -187,6 +178,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
settingActivity?.hideWaitingDialog()
|
settingActivity?.hideWaitingDialog()
|
||||||
if (cameraErrorCount >= 10) {
|
if (cameraErrorCount >= 10) {
|
||||||
ToastUtils.showToast("拍照异常,请重新操作")
|
ToastUtils.showToast("拍照异常,请重新操作")
|
||||||
|
settingActivity?.log("拍照异常,请重新操作:$errMsg")
|
||||||
cameraErrorCount = 0
|
cameraErrorCount = 0
|
||||||
return@takePhoto
|
return@takePhoto
|
||||||
}
|
}
|
||||||
@@ -305,8 +297,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
version = GlobalData.foodModelVersion
|
version = GlobalData.foodModelVersion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
initBox()
|
ObjectBox.putAll(foodList)
|
||||||
box?.put(foodList)
|
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
batch.forEach { it.isFinish = true }
|
batch.forEach { it.isFinish = true }
|
||||||
collectionAdapter.notifyDataSetChanged()
|
collectionAdapter.notifyDataSetChanged()
|
||||||
|
|||||||
@@ -54,27 +54,29 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
|||||||
override fun onHiddenChanged(hidden: Boolean) {
|
override fun onHiddenChanged(hidden: Boolean) {
|
||||||
super.onHiddenChanged(hidden)
|
super.onHiddenChanged(hidden)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//showSubScreen()
|
//showSubScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delayDismiss() {
|
private fun dismissSubScreen() {
|
||||||
presentation?.dismiss()
|
presentation?.let {
|
||||||
//binding.root.postDelayed({
|
if (it.isShowing) {
|
||||||
// presentation?.dismiss()
|
it.dismiss()
|
||||||
//}, 100)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSubScreen() {
|
private fun showSubScreen() {
|
||||||
// 查找副屏(通常索引为1)
|
// 查找副屏(通常索引为1)
|
||||||
if (displays.size > 1) {
|
if (displays.size > 1) {
|
||||||
|
dismissSubScreen()
|
||||||
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
||||||
presentation?.dismiss()
|
presentation?.dismiss()
|
||||||
}.also {
|
}.also {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
|||||||
// }
|
// }
|
||||||
payActivity.foodInfo?.let {
|
payActivity.foodInfo?.let {
|
||||||
foodName = it.foodName
|
foodName = it.foodName
|
||||||
payAmount = (it.vipPrice?:0.0) * payActivity.eatNum
|
payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
|
||||||
}
|
}
|
||||||
showSubScreen()
|
showSubScreen()
|
||||||
}
|
}
|
||||||
@@ -32,22 +32,23 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
|||||||
override fun onHiddenChanged(hidden: Boolean) {
|
override fun onHiddenChanged(hidden: Boolean) {
|
||||||
super.onHiddenChanged(hidden)
|
super.onHiddenChanged(hidden)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//showSubScreen()
|
//showSubScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delayDismiss() {
|
private fun dismissSubScreen() {
|
||||||
presentation?.dismiss()
|
presentation?.let {
|
||||||
//binding.root.postDelayed({
|
if (it.isShowing) {
|
||||||
// presentation?.dismiss()
|
it.dismiss()
|
||||||
//}, 100)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadBitmap(frame: Bitmap) {
|
fun loadBitmap(frame: Bitmap) {
|
||||||
@@ -57,6 +58,7 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
|||||||
private fun showSubScreen() {
|
private fun showSubScreen() {
|
||||||
// 查找副屏(通常索引为1)
|
// 查找副屏(通常索引为1)
|
||||||
if (displays.size > 1) {
|
if (displays.size > 1) {
|
||||||
|
dismissSubScreen()
|
||||||
presentation = FacePayPresentation(
|
presentation = FacePayPresentation(
|
||||||
activity = payActivity,
|
activity = payActivity,
|
||||||
display = displays[1],
|
display = displays[1],
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
package com.sw.dualscreen.activity.fragment.pay
|
package com.sw.dualscreen.activity.fragment.pay
|
||||||
|
|
||||||
import com.sw.dualscreen.GlobalKey
|
|
||||||
import com.sw.dualscreen.activity.PayActivity
|
import com.sw.dualscreen.activity.PayActivity
|
||||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||||
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
|
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
|
||||||
import com.sw.dualscreen.ext.format2String
|
import com.sw.dualscreen.ext.format2String
|
||||||
import com.sw.dualscreen.ext.hideKeyboard
|
import com.sw.dualscreen.ext.hideKeyboard
|
||||||
import com.sw.dualscreen.model.response.MemberInfo
|
|
||||||
import com.sw.dualscreen.presentation.pay.CashPayPresentation
|
import com.sw.dualscreen.presentation.pay.CashPayPresentation
|
||||||
import com.sw.dualscreen.utils.SPUtil
|
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
|
|
||||||
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||||
@@ -85,27 +82,29 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
|||||||
override fun onHiddenChanged(hidden: Boolean) {
|
override fun onHiddenChanged(hidden: Boolean) {
|
||||||
super.onHiddenChanged(hidden)
|
super.onHiddenChanged(hidden)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//showSubScreen()
|
//showSubScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delayDismiss() {
|
private fun dismissSubScreen() {
|
||||||
presentation?.dismiss()
|
presentation?.let {
|
||||||
//binding.root.postDelayed({
|
if (it.isShowing) {
|
||||||
// presentation?.dismiss()
|
it.dismiss()
|
||||||
//}, 100)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSubScreen() {
|
private fun showSubScreen() {
|
||||||
// 查找副屏(通常索引为1)
|
// 查找副屏(通常索引为1)
|
||||||
if (displays.size > 1) {
|
if (displays.size > 1) {
|
||||||
|
dismissSubScreen()
|
||||||
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
||||||
presentation?.dismiss()
|
presentation?.dismiss()
|
||||||
}.also {
|
}.also {
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
|||||||
private val intervalExecutor by lazy { IntervalExecutor() }
|
private val intervalExecutor by lazy { IntervalExecutor() }
|
||||||
private var payTaskJob: Job? = null
|
private var payTaskJob: Job? = null
|
||||||
fun paySuccessCallback(callback: () -> Unit) {
|
fun paySuccessCallback(callback: () -> Unit) {
|
||||||
payTaskJob = intervalExecutor.startIntervalTaskWithInitialDelay(5000, 10000) {
|
payTaskJob = intervalExecutor.startIntervalTaskWithInitialDelay(200, 500) {
|
||||||
payActivity?.queryOrderState { paySuccess ->
|
payActivity?.queryOrderState { paySuccess ->
|
||||||
if (paySuccess) {
|
if (paySuccess) {
|
||||||
callback()
|
callback()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.text.SpannedString
|
|||||||
import android.text.style.AbsoluteSizeSpan
|
import android.text.style.AbsoluteSizeSpan
|
||||||
import androidx.core.text.buildSpannedString
|
import androidx.core.text.buildSpannedString
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.sw.dualscreen.R
|
|
||||||
import com.sw.dualscreen.activity.PayActivity
|
import com.sw.dualscreen.activity.PayActivity
|
||||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||||
import com.sw.dualscreen.ext.load
|
import com.sw.dualscreen.ext.load
|
||||||
@@ -62,29 +61,31 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
|||||||
override fun onHiddenChanged(hidden: Boolean) {
|
override fun onHiddenChanged(hidden: Boolean) {
|
||||||
super.onHiddenChanged(hidden)
|
super.onHiddenChanged(hidden)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//showSubScreen()
|
//showSubScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
delayDismiss()
|
dismissSubScreen()
|
||||||
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
|
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
|
||||||
payTaskJob?.cancel()
|
payTaskJob?.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delayDismiss() {
|
private fun dismissSubScreen() {
|
||||||
presentation?.dismiss()
|
presentation?.let {
|
||||||
//binding.root.postDelayed({
|
if (it.isShowing) {
|
||||||
// presentation?.dismiss()
|
it.dismiss()
|
||||||
//}, 100)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSubScreen() {
|
private fun showSubScreen() {
|
||||||
// 查找副屏(通常索引为1)
|
// 查找副屏(通常索引为1)
|
||||||
if (displays.size > 1) {
|
if (displays.size > 1) {
|
||||||
|
dismissSubScreen()
|
||||||
presentation =
|
presentation =
|
||||||
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
|
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
|
||||||
presentation?.dismiss()
|
presentation?.dismiss()
|
||||||
@@ -123,7 +124,7 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
|||||||
private val intervalExecutor by lazy { IntervalExecutor() }
|
private val intervalExecutor by lazy { IntervalExecutor() }
|
||||||
private var payTaskJob: Job? = null
|
private var payTaskJob: Job? = null
|
||||||
fun paySuccessCallback(isVip: Boolean) {
|
fun paySuccessCallback(isVip: Boolean) {
|
||||||
payTaskJob = intervalExecutor.startIntervalTaskWithInitialDelay(5000, 10000) {
|
payTaskJob = intervalExecutor.startIntervalTaskWithInitialDelay(200, 500) {
|
||||||
payActivity.queryOrderState { paySuccess ->
|
payActivity.queryOrderState { paySuccess ->
|
||||||
if (paySuccess) {
|
if (paySuccess) {
|
||||||
payActivity.showPaySuccess(isVip)
|
payActivity.showPaySuccess(isVip)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import com.sw.dualscreen.utils.ImageUtil
|
|||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import io.objectbox.Box
|
import io.objectbox.Box
|
||||||
import io.objectbox.kotlin.boxFor
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.pytorch.IValue
|
import org.pytorch.IValue
|
||||||
import org.pytorch.Module
|
import org.pytorch.Module
|
||||||
import org.pytorch.torchvision.TensorImageUtils
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
@@ -25,8 +27,7 @@ object FoodModule {
|
|||||||
private const val THRESHOLD = 0.8
|
private const val THRESHOLD = 0.8
|
||||||
|
|
||||||
// private const val THRESHOLD = 0.0
|
// private const val THRESHOLD = 0.0
|
||||||
private var module: Module?=null
|
private var module: Module? = null
|
||||||
private var box: Box<Food>?=null
|
|
||||||
|
|
||||||
// private lateinit var embeddingsList: List<List<Float>>
|
// private lateinit var embeddingsList: List<List<Float>>
|
||||||
// private lateinit var labelsList: IntArray
|
// private lateinit var labelsList: IntArray
|
||||||
@@ -40,25 +41,21 @@ object FoodModule {
|
|||||||
private const val MODEL_INPUT_HEIGHT = 224
|
private const val MODEL_INPUT_HEIGHT = 224
|
||||||
|
|
||||||
@SuppressLint("SuspiciousIndentation")
|
@SuppressLint("SuspiciousIndentation")
|
||||||
fun init(context: Context, block: () -> Unit = {}) {
|
suspend fun init(context: Context, block: () -> Unit = {}) {
|
||||||
Thread {
|
// Thread {}.start()
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
val modelPath = copyAssetToCache(context, "best_embedding_model_mobile.pt")
|
||||||
// ToastUtils.showToast("modelPath==null:${modelPath == null}")
|
|
||||||
module = Module.load(modelPath)
|
module = Module.load(modelPath)
|
||||||
// ToastUtils.showToast("module==null:${module == null}")
|
|
||||||
box = ObjectBox.boxStore.boxFor(Food::class)
|
|
||||||
//初始化默认重新拉取数据,先清空本地数据
|
//初始化默认重新拉取数据,先清空本地数据
|
||||||
// ObjectBox.boxStore.runInTx {
|
val list = ObjectBox.getAll()
|
||||||
box?.run {
|
if (list.isNotEmpty()) {
|
||||||
if (all.isNotEmpty()) {
|
ObjectBox.removeAll()
|
||||||
removeAll()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//if (box.all.isEmpty()) {
|
//if (box.all.isEmpty()) {
|
||||||
// initDefFoodData(context)
|
// initDefFoodData(context)
|
||||||
//}
|
//}
|
||||||
block()
|
block()
|
||||||
}.start()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun uri2FloatArray(uri: Uri): FloatArray? {
|
// fun uri2FloatArray(uri: Uri): FloatArray? {
|
||||||
@@ -125,24 +122,21 @@ object FoodModule {
|
|||||||
// return queryFoodNameScore(floatArray, queryCount)
|
// return queryFoodNameScore(floatArray, queryCount)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fun queryFoodNameScore(floatArray: FloatArray?, queryCount: Int = 15): List<IdNameScore> {
|
suspend fun queryFoodNameScore(
|
||||||
|
floatArray: FloatArray?,
|
||||||
|
queryCount: Int = 15
|
||||||
|
): List<IdNameScore> {
|
||||||
if (floatArray == null) return emptyList()
|
if (floatArray == null) return emptyList()
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
Timber.tag(TAG)
|
val size = ObjectBox.getAll().filter { it.isDel.not() }.size
|
||||||
.d("queryFoodNameScore-已采集向量总数:${box?.all?.filter { it.isDel.not() }?.size}")
|
Timber.tag(TAG).d("queryFoodNameScore-已采集向量总数:${size}")
|
||||||
//box.store.runInTx { }
|
|
||||||
val query = box?.query()
|
|
||||||
?.equal(Food_.isDel, false)
|
|
||||||
?.and()
|
|
||||||
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
|
||||||
?.build()
|
|
||||||
//查询比较分数
|
//查询比较分数
|
||||||
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
||||||
// val idScoreList = query.findIdsWithScores()
|
// val idScoreList = query.findIdsWithScores()
|
||||||
val objScoreList = query?.findWithScores()
|
val objScoreList = ObjectBox.query(floatArray, queryCount)
|
||||||
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
|
Timber.tag(TAG).d("idScoreList:${GsonUtils.toJson(objScoreList)}")
|
||||||
val nameScoreList = mutableListOf<IdNameScore>()
|
val nameScoreList = mutableListOf<IdNameScore>()
|
||||||
objScoreList?.forEach {
|
objScoreList.forEach {
|
||||||
val food = it.get()
|
val food = it.get()
|
||||||
nameScoreList.add(
|
nameScoreList.add(
|
||||||
IdNameScore(
|
IdNameScore(
|
||||||
@@ -153,13 +147,11 @@ object FoodModule {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
val nameScoreData = GsonUtils.toJson(nameScoreList)
|
||||||
Timber.tag(TAG)
|
Timber.tag(TAG).d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
||||||
.d("queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
|
|
||||||
query?.close()
|
|
||||||
return nameScoreList
|
return nameScoreList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
val floatArray = bitmap2FloatArray(bitmap, false)
|
val floatArray = bitmap2FloatArray(bitmap, false)
|
||||||
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
|
Timber.tag(TAG).d("bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
package com.sw.dualscreen.objbox
|
package com.sw.dualscreen.objbox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.sw.plate.App
|
||||||
import io.objectbox.Box
|
import io.objectbox.Box
|
||||||
import io.objectbox.BoxStore
|
import io.objectbox.BoxStore
|
||||||
import io.objectbox.BoxStoreBuilder
|
import io.objectbox.BoxStoreBuilder
|
||||||
@@ -26,12 +28,22 @@ import io.objectbox.android.ObjectBoxLiveData
|
|||||||
import io.objectbox.config.DebugFlags
|
import io.objectbox.config.DebugFlags
|
||||||
import io.objectbox.exception.DbException
|
import io.objectbox.exception.DbException
|
||||||
import io.objectbox.exception.FileCorruptException
|
import io.objectbox.exception.FileCorruptException
|
||||||
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import io.objectbox.query.Query
|
||||||
import io.objectbox.sync.Sync
|
import io.objectbox.sync.Sync
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import kotlin.also
|
import kotlin.also
|
||||||
|
import kotlin.and
|
||||||
import kotlin.io.copyTo
|
import kotlin.io.copyTo
|
||||||
import kotlin.io.inputStream
|
import kotlin.io.inputStream
|
||||||
import kotlin.io.outputStream
|
import kotlin.io.outputStream
|
||||||
@@ -52,8 +64,7 @@ object ObjectBox {
|
|||||||
|
|
||||||
private const val TAG = "ObjectBox"
|
private const val TAG = "ObjectBox"
|
||||||
|
|
||||||
lateinit var boxStore: BoxStore
|
var boxStore: BoxStore? = null
|
||||||
private set
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If building the [boxStore] failed, contains the thrown error message.
|
* If building the [boxStore] failed, contains the thrown error message.
|
||||||
@@ -65,9 +76,9 @@ object ObjectBox {
|
|||||||
// On Android make sure to pass a Context when building the Store.
|
// On Android make sure to pass a Context when building the Store.
|
||||||
boxStore = try {
|
boxStore = try {
|
||||||
MyObjectBox.builder()
|
MyObjectBox.builder()
|
||||||
.androidContext(context.applicationContext)
|
.androidContext(context.applicationContext)
|
||||||
.debugFlags(DebugFlags.LOG_QUERY_PARAMETERS)
|
.debugFlags(DebugFlags.LOG_QUERY_PARAMETERS)
|
||||||
.build()
|
.build()
|
||||||
} catch (e: DbException) {
|
} catch (e: DbException) {
|
||||||
if (e.javaClass == DbException::class.java || e is FileCorruptException) {
|
if (e.javaClass == DbException::class.java || e is FileCorruptException) {
|
||||||
// Failed to build BoxStore due to database file issue, store message;
|
// Failed to build BoxStore due to database file issue, store message;
|
||||||
@@ -81,12 +92,12 @@ object ObjectBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if (BuildConfig.DEBUG) {
|
// if (BuildConfig.DEBUG) {
|
||||||
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
|
val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
|
||||||
Timber.tag(TAG)
|
Timber.tag(TAG)
|
||||||
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
|
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
|
||||||
// Enable ObjectBox Admin on debug builds.
|
// Enable ObjectBox Admin on debug builds.
|
||||||
// https://docs.objectbox.io/data-browser
|
// https://docs.objectbox.io/data-browser
|
||||||
Admin(boxStore).start(context.applicationContext)
|
Admin(boxStore).start(context.applicationContext)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,42 +125,166 @@ object ObjectBox {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 安全获取 Food 实体 foodVector 字段的向量索引(推荐写法)
|
// 获取指定实体的Box,自动关联BoxStore
|
||||||
// */
|
inline fun <reified T> getBox(): Box<T>? = boxStore?.boxFor()
|
||||||
// fun getFoodVectorIndex(box: Box<Food>): VectorIndex? {
|
|
||||||
// return try {
|
// 协程安全执行数据库操作(推荐所有操作使用此方法)
|
||||||
// // 核心修复:使用自动生成的 Food_.foodVector 而非字符串,避免拼写错误
|
suspend fun <T> safeDbOp(operation: suspend () -> T): T? {
|
||||||
// val foodVectorProperty = Food_.foodVector
|
return withContext(Dispatchers.IO) {
|
||||||
// // 获取向量索引(适配 ObjectBox 3.5+ 所有版本)
|
try {
|
||||||
// foodVectorProperty.vectorIndex
|
dbMutex.withLock { operation() }
|
||||||
// } catch (e: Exception) {
|
} catch (e: FileCorruptException) {
|
||||||
// // 容错处理:打印错误信息,避免崩溃
|
// 操作中触发损坏,尝试重建数据库
|
||||||
// e.printStackTrace()
|
//val context = boxStore.context
|
||||||
// null
|
//deleteDbFiles(context)
|
||||||
// }
|
init(App.getContext()!!)
|
||||||
// }
|
null
|
||||||
//
|
} catch (e: Exception) {
|
||||||
// // 刷新索引的调用示例(结合你之前的更新场景)
|
e.printStackTrace()
|
||||||
// fun refreshFoodVectorIndex(box: Box<Food>) {
|
null
|
||||||
// val vectorIndex = getFoodVectorIndex(box)
|
}
|
||||||
// vectorIndex?.run {
|
}
|
||||||
// // 同步刷新索引(解决查询异常问题)
|
}
|
||||||
// refresh()
|
|
||||||
// // 等待索引构建完成(超时 5 秒,避免无限等待)
|
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
|
||||||
// waitUntilBuilt(5000)
|
val query: Query<Food>? = getBox<Food>()?.query()
|
||||||
// }
|
?.equal(Food_.isDel, false)
|
||||||
// }
|
?.and()
|
||||||
//
|
?.nearestNeighbors(Food_.foodVector, floatArray, queryCount)
|
||||||
// // 更新 isDel 字段后的完整调用流程
|
?.build()
|
||||||
// fun markFoodAsDeleted(box: Box<Food>, foodId: Long) {
|
try {
|
||||||
// val food = box[foodId]
|
query?.findWithScores()
|
||||||
// if (food != null) {
|
} finally {
|
||||||
// food.isDel = true
|
// 先关闭Query,释放Cursor
|
||||||
// box.put(food)
|
query?.close()
|
||||||
// // 更新后刷新索引
|
}
|
||||||
// refreshFoodVectorIndex(box)
|
} ?: emptyList()
|
||||||
// }
|
|
||||||
// }
|
suspend fun get(id: Long) = safeDbOp {
|
||||||
|
getBox<Food>()?.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun put(entity: Food) = safeDbOp {
|
||||||
|
getBox<Food>()?.put(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun putAll(entities: List<Food>) = safeDbOp {
|
||||||
|
getBox<Food>()?.put(entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAll() = safeDbOp {
|
||||||
|
getBox<Food>()?.all
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
suspend fun filter(name: String?, isDistinct: Boolean = false) = safeDbOp {
|
||||||
|
val list = if (isDistinct) {
|
||||||
|
getBox<Food>()?.all?.distinctBy { it.foodName }
|
||||||
|
} else {
|
||||||
|
getBox<Food>()?.all
|
||||||
|
}
|
||||||
|
if (name.isNullOrBlank().not()) {
|
||||||
|
list?.filter { it.foodName?.contains(name) == true }
|
||||||
|
}
|
||||||
|
list
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
suspend fun removeAll() = safeDbOp {
|
||||||
|
getBox<Food>()?.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun remove(name: String) = safeDbOp {
|
||||||
|
getBox<Food>()?.run {
|
||||||
|
val filterIdList = all.filter { it.foodName == name }.map { it.id }
|
||||||
|
removeByIds(filterIdList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
|
||||||
|
private const val DB_DIR_NAME = "objectbox" // ObjectBox 默认数据库目录
|
||||||
|
private const val BACKUP_DIR_NAME = "objectbox_backup" // 备份目录
|
||||||
|
|
||||||
|
// 检查存储是否可读写(操作数据库前调用)
|
||||||
|
fun isStorageAvailable(context: Context): Boolean {
|
||||||
|
return try {
|
||||||
|
val state = Environment.getExternalStorageState()
|
||||||
|
val innerDir = context.filesDir
|
||||||
|
Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备份数据库到应用私有目录(无权限要求,推荐)
|
||||||
|
suspend fun backupDb(context: Context): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
if (!isStorageAvailable(context)) return@withContext false
|
||||||
|
val dbDir = File(context.filesDir, DB_DIR_NAME)
|
||||||
|
val backupDir = File(context.filesDir, BACKUP_DIR_NAME)
|
||||||
|
return@withContext copyDir(dbDir, backupDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从备份恢复数据库(恢复后会重建BoxStore)
|
||||||
|
suspend fun restoreDb(context: Context): Boolean = withContext(Dispatchers.IO) {
|
||||||
|
if (!isStorageAvailable(context)) return@withContext false
|
||||||
|
val dbDir = File(context.filesDir, DB_DIR_NAME)
|
||||||
|
val backupDir = File(context.filesDir, BACKUP_DIR_NAME)
|
||||||
|
if (!backupDir.exists()) return@withContext false
|
||||||
|
// 先关闭旧的BoxStore,删除损坏文件,再恢复备份
|
||||||
|
boxStore?.close()
|
||||||
|
deleteDbFiles(context)
|
||||||
|
val isSuccess = copyDir(backupDir, dbDir)
|
||||||
|
// 重新初始化
|
||||||
|
init(context)
|
||||||
|
return@withContext isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归删除数据库文件
|
||||||
|
private fun deleteDbFiles(context: Context) {
|
||||||
|
val dbDir = File(context.filesDir, DB_DIR_NAME)
|
||||||
|
if (dbDir.exists()) deleteDirRecursively(dbDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归删除目录
|
||||||
|
private fun deleteDirRecursively(file: File) {
|
||||||
|
if (file.isDirectory) {
|
||||||
|
file.listFiles()?.forEach { deleteDirRecursively(it) }
|
||||||
|
}
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归复制目录(核心备份/恢复逻辑)
|
||||||
|
private fun copyDir(srcDir: File, destDir: File): Boolean {
|
||||||
|
return try {
|
||||||
|
if (!srcDir.exists()) return false
|
||||||
|
if (!destDir.exists()) destDir.mkdirs()
|
||||||
|
srcDir.listFiles()?.forEach { srcFile ->
|
||||||
|
val destFile = File(destDir, srcFile.name)
|
||||||
|
if (srcFile.isDirectory) {
|
||||||
|
copyDir(srcFile, destFile)
|
||||||
|
} else {
|
||||||
|
copyFile(srcFile, destFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制单个文件(使用NIO,高效稳定)
|
||||||
|
private fun copyFile(srcFile: File, destFile: File) {
|
||||||
|
FileInputStream(srcFile).channel.use { srcChannel ->
|
||||||
|
FileOutputStream(destFile).channel.use { destChannel ->
|
||||||
|
destChannel.transferFrom(srcChannel, 0, srcChannel.size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭BoxStore(应用退出时调用,可选)
|
||||||
|
fun close() {
|
||||||
|
boxStore?.close()
|
||||||
|
boxStore = null
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ class RemoteRepository constructor(
|
|||||||
suspend fun getRestInfoFoodsByType(
|
suspend fun getRestInfoFoodsByType(
|
||||||
foodName: String,
|
foodName: String,
|
||||||
pageNum: Int = 1,
|
pageNum: Int = 1,
|
||||||
pageSize:Int = 100
|
pageSize:Int = 50
|
||||||
): ApiResponse<List<FoodInfo>> {
|
): ApiResponse<List<FoodInfo>> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
apiService.getRestInfoFoodsByType(param = hashMapOf(
|
apiService.getRestInfoFoodsByType(param = hashMapOf(
|
||||||
|
|||||||
@@ -0,0 +1,697 @@
|
|||||||
|
package com.sw.dualscreen.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.sw.plate.App;
|
||||||
|
import com.sw.plate.utils.AppUtil;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FileUtil {
|
||||||
|
private static boolean isSaveLog = true;
|
||||||
|
public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
|
||||||
|
|
||||||
|
public static final int SIZETYPE_B = 1;//获取文件大小单位为B的double值
|
||||||
|
public static final int SIZETYPE_KB = 2;//获取文件大小单位为KB的double值
|
||||||
|
public static final int SIZETYPE_MB = 3;//获取文件大小单位为MB的double值
|
||||||
|
public static final int SIZETYPE_GB = 4;//获取文件大小单位为GB的double值
|
||||||
|
private static String urlNull = "原文件路径不存在";
|
||||||
|
private static String isFile = "原文件不是文件";
|
||||||
|
private static String canRead = "原文件不能读";
|
||||||
|
private static String copyFalse = "备份失败!";
|
||||||
|
private static String cFromFile = "创建原文件出错:";
|
||||||
|
private static String ctoFile = "创建备份文件出错:";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入文件
|
||||||
|
* FileUtil.byteWriteFile(getExternalFilesDir("123"),"test.jpg",nv21);
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @param fileName
|
||||||
|
* @param bytes
|
||||||
|
*/
|
||||||
|
public static void byteWriteFile(File filePath, String fileName, byte[] bytes) {
|
||||||
|
BufferedOutputStream bout = null;
|
||||||
|
try {
|
||||||
|
File file = new File(filePath, fileName);
|
||||||
|
if (file.exists() == false) {
|
||||||
|
if (file.getParentFile().exists() == false) {
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
}
|
||||||
|
file.createNewFile();
|
||||||
|
}
|
||||||
|
bout = new BufferedOutputStream(new FileOutputStream(file, false));
|
||||||
|
bout.write(bytes);
|
||||||
|
bout.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (bout != null) {
|
||||||
|
try {
|
||||||
|
bout.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件名
|
||||||
|
*
|
||||||
|
* @param path 路径
|
||||||
|
* @return 文件名
|
||||||
|
*/
|
||||||
|
public static String getFileName(String path) {
|
||||||
|
int index = path.lastIndexOf("/");
|
||||||
|
return path.substring(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件路径获取文件
|
||||||
|
*
|
||||||
|
* @param path 路径
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static File getFileByPath(String path) {
|
||||||
|
File file = new File(path);
|
||||||
|
if (file.exists()) {
|
||||||
|
return new File(path);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名文件
|
||||||
|
*
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @param newName 新名称
|
||||||
|
* @return {@code true}: 重命名成功<br>{@code false}: 重命名失败
|
||||||
|
*/
|
||||||
|
public static boolean rename(String filePath, String newName) {
|
||||||
|
return rename(getFileByPath(filePath), newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名文件
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @param newName 新名称
|
||||||
|
* @return {@code true}: 重命名成功<br>{@code false}: 重命名失败
|
||||||
|
*/
|
||||||
|
public static boolean rename(File file, String newName) {
|
||||||
|
// 文件为空返回false
|
||||||
|
if (file == null) return false;
|
||||||
|
// 文件不存在返回false
|
||||||
|
if (!file.exists()) return false;
|
||||||
|
// 新的文件名为空返回false
|
||||||
|
if (AppUtil.isEmpty(newName)) return false;
|
||||||
|
// 如果文件名没有改变返回true
|
||||||
|
if (newName.equals(file.getName())) return true;
|
||||||
|
File newFile = new File(file.getParent() + File.separator + newName);
|
||||||
|
// 如果重命名的文件已存在返回false
|
||||||
|
return !newFile.exists()
|
||||||
|
&& file.renameTo(newFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断文件是否存在
|
||||||
|
*
|
||||||
|
* @param path 文件的路径,含文件后缀和文件名
|
||||||
|
* @return 是否存在
|
||||||
|
*/
|
||||||
|
public static boolean isFileExists(String path) {
|
||||||
|
File file = new File(path);
|
||||||
|
if (file.exists()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断文件是否存在
|
||||||
|
*
|
||||||
|
* @return 是否存在
|
||||||
|
*/
|
||||||
|
public static boolean isFileExists(File file) {
|
||||||
|
if (file.exists()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件夹及文件夹下所有内容
|
||||||
|
*
|
||||||
|
* @param path 文件夹路径
|
||||||
|
* @return 返回是否删除成功
|
||||||
|
*/
|
||||||
|
public static boolean deleteFiles(String path) {
|
||||||
|
if (getFileByPath(path) != null) {
|
||||||
|
return deleteFiles(getFileByPath(path));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件夹及文件夹下所有内容
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @return 返回是否删除成功
|
||||||
|
*/
|
||||||
|
public static boolean deleteFiles(File file) {
|
||||||
|
try {
|
||||||
|
if (file.exists()) { // 判断文件是否存在
|
||||||
|
if (file.isFile()) { // 判断是否是文件
|
||||||
|
file.delete(); // delete()方法
|
||||||
|
} else if (file.isDirectory()) { // 否则如果它是一个目录
|
||||||
|
File files[] = file.listFiles(); // 声明目录下所有的文件 files[];
|
||||||
|
for (int i = 0; i < files.length; i++) { // 遍历目录下所有的文件
|
||||||
|
deleteFiles(files[i].getPath()); // 把每个文件 用这个方法进行迭代
|
||||||
|
}
|
||||||
|
file.delete();//删除目录
|
||||||
|
}
|
||||||
|
//file.delete();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件的Uri
|
||||||
|
*
|
||||||
|
* @param path 文件的路径
|
||||||
|
* @return 文件的Uri
|
||||||
|
*/
|
||||||
|
public static Uri getUriFromFile(String path) {
|
||||||
|
File file = new File(path);
|
||||||
|
return Uri.fromFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件指定文件的指定单位的大小
|
||||||
|
*
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @param sizeType 获取大小的类型1为B、2为KB、3为MB、4为GB
|
||||||
|
* @return double值的大小
|
||||||
|
*/
|
||||||
|
public static double getFileOrFilesSize(String filePath, int sizeType) {
|
||||||
|
File file = new File(filePath);
|
||||||
|
long blockSize = 0;
|
||||||
|
try {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
blockSize = getFileSizes(file);
|
||||||
|
} else {
|
||||||
|
blockSize = getFileSize(file);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
L.i("获取文件大小", "获取失败!");
|
||||||
|
}
|
||||||
|
return FormetFileSize(blockSize, sizeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用此方法自动计算指定文件或指定文件夹的大小
|
||||||
|
*
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @return 计算好的带B、KB、MB、GB的字符串
|
||||||
|
*/
|
||||||
|
public static String getAutoFileOrFilesSize(String filePath) {
|
||||||
|
File file = new File(filePath);
|
||||||
|
long blockSize = 0;
|
||||||
|
try {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
blockSize = getFileSizes(file);
|
||||||
|
} else {
|
||||||
|
blockSize = getFileSize(file);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
L.i("获取文件大小", "获取失败!");
|
||||||
|
}
|
||||||
|
return FormetFileSize(blockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定文件大小
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static long getFileSize(File file) throws Exception {
|
||||||
|
long size = 0;
|
||||||
|
if (file.exists()) {
|
||||||
|
FileInputStream fis = null;
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
size = fis.available();
|
||||||
|
} else {
|
||||||
|
// file.createNewFile();
|
||||||
|
L.i("获取文件大小", "文件不存在!");
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目录下指定文件名的文件包括子目录
|
||||||
|
* <p>大小写忽略</p>
|
||||||
|
*
|
||||||
|
* @param dirPath 目录路径
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return 文件链表
|
||||||
|
*/
|
||||||
|
public static List<File> searchFileInDir(String dirPath, String fileName) {
|
||||||
|
return searchFileInDir(getFileByPath(dirPath), fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目录下指定文件名的文件包括子目录
|
||||||
|
* <p>大小写忽略</p>
|
||||||
|
*
|
||||||
|
* @param dir 目录
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return 文件链表
|
||||||
|
*/
|
||||||
|
public static List<File> searchFileInDir(File dir, String fileName) {
|
||||||
|
if (dir == null || !dir.isDirectory()) return null;
|
||||||
|
List<File> list = new ArrayList<>();
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files != null && files.length != 0) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.getName().toUpperCase().equals(fileName.toUpperCase())) {
|
||||||
|
list.add(file);
|
||||||
|
}
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
list.addAll(searchFileInDir(file, fileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定文件夹
|
||||||
|
*
|
||||||
|
* @param f
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private static long getFileSizes(File f) throws Exception {
|
||||||
|
long size = 0;
|
||||||
|
File flist[] = f.listFiles();
|
||||||
|
for (int i = 0; i < flist.length; i++) {
|
||||||
|
if (flist[i].isDirectory()) {
|
||||||
|
size = size + getFileSizes(flist[i]);
|
||||||
|
} else {
|
||||||
|
size = size + getFileSize(flist[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换文件大小
|
||||||
|
*
|
||||||
|
* @param fileS
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String FormetFileSize(long fileS) {
|
||||||
|
DecimalFormat df = new DecimalFormat("#.00");
|
||||||
|
String fileSizeString = "";
|
||||||
|
String wrongSize = "0B";
|
||||||
|
if (fileS == 0) {
|
||||||
|
return wrongSize;
|
||||||
|
}
|
||||||
|
if (fileS < 1024) {
|
||||||
|
fileSizeString = df.format((double) fileS) + "B";
|
||||||
|
} else if (fileS < 1048576) {
|
||||||
|
fileSizeString = df.format((double) fileS / 1024) + "KB";
|
||||||
|
} else if (fileS < 1073741824) {
|
||||||
|
fileSizeString = df.format((double) fileS / 1048576) + "MB";
|
||||||
|
} else {
|
||||||
|
fileSizeString = df.format((double) fileS / 1073741824) + "GB";
|
||||||
|
}
|
||||||
|
return fileSizeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换文件大小,指定转换的类型
|
||||||
|
*
|
||||||
|
* @param fileS
|
||||||
|
* @param sizeType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static double FormetFileSize(long fileS, int sizeType) {
|
||||||
|
DecimalFormat df = new DecimalFormat("#.00");
|
||||||
|
double fileSizeLong = 0;
|
||||||
|
switch (sizeType) {
|
||||||
|
case SIZETYPE_B:
|
||||||
|
fileSizeLong = Double.valueOf(df.format((double) fileS));
|
||||||
|
break;
|
||||||
|
case SIZETYPE_KB:
|
||||||
|
fileSizeLong = Double.valueOf(df.format((double) fileS / 1024));
|
||||||
|
break;
|
||||||
|
case SIZETYPE_MB:
|
||||||
|
fileSizeLong = Double.valueOf(df.format((double) fileS / 1048576));
|
||||||
|
break;
|
||||||
|
case SIZETYPE_GB:
|
||||||
|
fileSizeLong = Double.valueOf(df.format((double) fileS / 1073741824));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return fileSizeLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制文件
|
||||||
|
*
|
||||||
|
* @param fromFilePath 旧文件地址和名称
|
||||||
|
* @param toFilePath 新文件地址和名称
|
||||||
|
* @return 返回备份文件的信息,ok是成功,其它就是错误
|
||||||
|
*/
|
||||||
|
public static File copyFile(String fromFilePath, String toFilePath) {
|
||||||
|
File fromFile = null;
|
||||||
|
File toFile = null;
|
||||||
|
try {
|
||||||
|
fromFile = new File(fromFilePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
L.i(cFromFile + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
toFile = new File(toFilePath);
|
||||||
|
if (toFile.isDirectory()) {
|
||||||
|
toFile = new File(toFilePath.endsWith("/") ? toFilePath + fromFile.getName() : toFilePath + "/" + fromFile.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
L.i(ctoFile + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fromFile.exists()) {
|
||||||
|
L.i(urlNull);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!fromFile.isFile()) {
|
||||||
|
L.i(isFile);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!fromFile.canRead()) {
|
||||||
|
L.i(canRead);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到的路径如果不存在就创建
|
||||||
|
if (!toFile.getParentFile().exists()) {
|
||||||
|
toFile.getParentFile().mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toFile.exists()) {
|
||||||
|
toFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toFile.canWrite()) {
|
||||||
|
//return notWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileInputStream fosfrom = new FileInputStream(
|
||||||
|
fromFile);
|
||||||
|
FileOutputStream fosto = new FileOutputStream(toFile);
|
||||||
|
byte bt[] = new byte[1024];
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while ((c = fosfrom.read(bt)) > 0) {
|
||||||
|
fosto.write(bt, 0, c); // 将内容写到新文件当中
|
||||||
|
}
|
||||||
|
//关闭数据流
|
||||||
|
fosfrom.close();
|
||||||
|
fosto.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
L.i(copyFalse + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建文件
|
||||||
|
*
|
||||||
|
* @param path 文件路径
|
||||||
|
* @return 创建的文件
|
||||||
|
*/
|
||||||
|
public static synchronized File createNewFile(String path) {
|
||||||
|
File file = new File(path);
|
||||||
|
String parentPath = path.substring(0, path.lastIndexOf("/") + 1);
|
||||||
|
File parentFile = new File(parentPath);
|
||||||
|
if (!parentFile.exists()) {
|
||||||
|
parentFile.mkdirs();
|
||||||
|
}
|
||||||
|
if (!file.exists()) {
|
||||||
|
try {
|
||||||
|
file.createNewFile();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 根据文件后缀回去MIME类型
|
||||||
|
****/
|
||||||
|
|
||||||
|
private static String getMIMEType(File file) {
|
||||||
|
String type = "*/*";
|
||||||
|
String fName = file.getName();
|
||||||
|
//获取后缀名前的分隔符"."在fName中的位置。
|
||||||
|
int dotIndex = fName.lastIndexOf(".");
|
||||||
|
if (dotIndex < 0) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
/* 获取文件的后缀名*/
|
||||||
|
String end = fName.substring(dotIndex, fName.length()).toLowerCase();
|
||||||
|
if (end == "") return type;
|
||||||
|
//在MIME和文件类型的匹配表中找到对应的MIME类型。
|
||||||
|
for (int i = 0; i < MIME_MapTable.length; i++) { //MIME_MapTable??在这里你一定有疑问,这个MIME_MapTable是什么?
|
||||||
|
if (end.equals(MIME_MapTable[i][0]))
|
||||||
|
type = MIME_MapTable[i][1];
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全路径中的文件拓展名
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @return 文件拓展名
|
||||||
|
*/
|
||||||
|
public static String getFileExtension(File file) {
|
||||||
|
if (file == null) return null;
|
||||||
|
return getFileExtension(file.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全路径中的文件拓展名
|
||||||
|
*
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @return 文件拓展名
|
||||||
|
*/
|
||||||
|
public static String getFileExtension(String filePath) {
|
||||||
|
if (AppUtil.isEmpty(filePath)) return filePath;
|
||||||
|
int lastPoi = filePath.lastIndexOf('.');
|
||||||
|
int lastSep = filePath.lastIndexOf(File.separator);
|
||||||
|
if (lastPoi == -1 || lastSep >= lastPoi) return "";
|
||||||
|
return filePath.substring(lastPoi + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用系统应用打开文件
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param file 文件
|
||||||
|
*/
|
||||||
|
public static void openFile(Context context, File file) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
//设置intent的Action属性
|
||||||
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
|
//获取文件file的MIME类型
|
||||||
|
String type = getMIMEType(file);
|
||||||
|
//设置intent的data和Type属性。
|
||||||
|
intent.setDataAndType(Uri.fromFile(file), type);
|
||||||
|
//跳转
|
||||||
|
try {
|
||||||
|
context.startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
L.i("找不到打开此文件的应用!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存文本文件
|
||||||
|
* FileUtil.saveStrFile("测试", "log.txt", FILE_STR_PATH, true);
|
||||||
|
*
|
||||||
|
* @param res 写入内容
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @param filePath 路径
|
||||||
|
* @param append true新增 false替换
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean saveStrFile(String res, String fileName, String filePath, boolean append) {
|
||||||
|
boolean flag = true;
|
||||||
|
BufferedReader bufferedReader = null;
|
||||||
|
BufferedWriter bufferedWriter = null;
|
||||||
|
try {
|
||||||
|
File file = new File(filePath, fileName);
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
file.createNewFile();
|
||||||
|
}
|
||||||
|
bufferedReader = new BufferedReader(new StringReader(res));
|
||||||
|
bufferedWriter = new BufferedWriter(new FileWriter(file, append));
|
||||||
|
char buf[] = new char[1024]; //字符缓冲区
|
||||||
|
int len;
|
||||||
|
while ((len = bufferedReader.read(buf)) != -1) {
|
||||||
|
bufferedWriter.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
bufferedWriter.flush();
|
||||||
|
bufferedReader.close();
|
||||||
|
bufferedWriter.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
flag = false;
|
||||||
|
return flag;
|
||||||
|
} finally {
|
||||||
|
if (bufferedReader != null) {
|
||||||
|
try {
|
||||||
|
bufferedReader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveLog(String... logs) {
|
||||||
|
if (!isSaveLog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
super.run();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String log : logs) {
|
||||||
|
sb.append("===")
|
||||||
|
.append((AppUtil.formatDateGetCurrentTime()))
|
||||||
|
.append("===")
|
||||||
|
.append("\n")
|
||||||
|
.append(log)
|
||||||
|
.append("\n");
|
||||||
|
}
|
||||||
|
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
|
||||||
|
FileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[][] MIME_MapTable = {
|
||||||
|
// {后缀名,MIME类型}
|
||||||
|
{".3gp", "video/3gpp"},
|
||||||
|
{".apk", "application/vnd.android.package-archive"},
|
||||||
|
{".asf", "video/x-ms-asf"},
|
||||||
|
{".avi", "video/x-msvideo"},
|
||||||
|
{".bin", "application/octet-stream"},
|
||||||
|
{".bmp", "image/bmp"},
|
||||||
|
{".c", "text/plain"},
|
||||||
|
{".class", "application/octet-stream"},
|
||||||
|
{".conf", "text/plain"},
|
||||||
|
{".cpp", "text/plain"},
|
||||||
|
{".doc", "application/msword"},
|
||||||
|
{".docx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
|
||||||
|
{".xls", "application/vnd.ms-excel"},
|
||||||
|
{".xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
|
||||||
|
{".exe", "application/octet-stream"},
|
||||||
|
{".gif", "image/gif"},
|
||||||
|
{".gtar", "application/x-gtar"},
|
||||||
|
{".gz", "application/x-gzip"},
|
||||||
|
{".h", "text/plain"},
|
||||||
|
{".htm", "text/html"},
|
||||||
|
{".html", "text/html"},
|
||||||
|
{".jar", "application/java-archive"},
|
||||||
|
{".java", "text/plain"},
|
||||||
|
{".jpeg", "image/jpeg"},
|
||||||
|
{".jpg", "image/jpeg"},
|
||||||
|
{".js", "application/x-javascript"},
|
||||||
|
{".log", "text/plain"},
|
||||||
|
{".m3u", "audio/x-mpegurl"},
|
||||||
|
{".m4a", "audio/mp4a-latm"},
|
||||||
|
{".m4b", "audio/mp4a-latm"},
|
||||||
|
{".m4p", "audio/mp4a-latm"},
|
||||||
|
{".m4u", "video/vnd.mpegurl"},
|
||||||
|
{".m4v", "video/x-m4v"},
|
||||||
|
{".mov", "video/quicktime"},
|
||||||
|
{".mp2", "audio/x-mpeg"},
|
||||||
|
{".mp3", "audio/x-mpeg"},
|
||||||
|
{".mp4", "video/mp4"},
|
||||||
|
{".mpc", "application/vnd.mpohun.certificate"},
|
||||||
|
{".mpe", "video/mpeg"},
|
||||||
|
{".mpeg", "video/mpeg"},
|
||||||
|
{".mpg", "video/mpeg"},
|
||||||
|
{".mpg4", "video/mp4"},
|
||||||
|
{".mpga", "audio/mpeg"},
|
||||||
|
{".msg", "application/vnd.ms-outlook"},
|
||||||
|
{".ogg", "audio/ogg"},
|
||||||
|
{".pdf", "application/pdf"},
|
||||||
|
{".png", "image/png"},
|
||||||
|
{".pps", "application/vnd.ms-powerpoint"},
|
||||||
|
{".ppt", "application/vnd.ms-powerpoint"},
|
||||||
|
{".pptx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
||||||
|
{".prop", "text/plain"}, {".rc", "text/plain"},
|
||||||
|
{".rmvb", "audio/x-pn-realaudio"}, {".rtf", "application/rtf"},
|
||||||
|
{".sh", "text/plain"}, {".tar", "application/x-tar"},
|
||||||
|
{".tgz", "application/x-compressed"}, {".txt", "text/plain"},
|
||||||
|
{".wav", "audio/x-wav"}, {".wma", "audio/x-ms-wma"},
|
||||||
|
{".wmv", "audio/x-ms-wmv"},
|
||||||
|
{".wps", "application/vnd.ms-works"}, {".xml", "text/plain"},
|
||||||
|
{".z", "application/x-compress"},
|
||||||
|
{".zip", "application/x-zip-compressed"}, {"", "*/*"}};
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.sw.dualscreen.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log统一管理类
|
||||||
|
*/
|
||||||
|
public class L {
|
||||||
|
|
||||||
|
private L() {
|
||||||
|
/* cannot be instantiated */
|
||||||
|
throw new UnsupportedOperationException("cannot be instantiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDebug = true;// 是否需要打印bug,可以在application的onCreate函数里面初始化
|
||||||
|
private static final String TAG = "mzf";
|
||||||
|
|
||||||
|
// 下面四个是默认tag的函数
|
||||||
|
public static void i(String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.i(TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.d(TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.e(TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void v(String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.v(TAG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下面是传入自定义tag的函数
|
||||||
|
public static void i(String tag, String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.i(tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(String tag, String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.d(tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(String tag, String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.e(tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void v(String tag, String msg) {
|
||||||
|
if (isDebug)
|
||||||
|
android.util.Log.v(tag, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
android:contentDescription="食物图预览"
|
android:contentDescription="食物图预览"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:strokeColor="@android:color/transparent"
|
||||||
app:shapeAppearance="@style/round10dpCornerStyle" />
|
app:shapeAppearance="@style/round10dpCornerStyle" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:layout_marginHorizontal="32dp"
|
android:layout_marginHorizontal="32dp"
|
||||||
android:layout_marginBottom="20dp"
|
android:layout_marginBottom="20dp"
|
||||||
android:background="@drawable/bg_btn_save2"
|
android:background="@drawable/bg_btn_confirm"
|
||||||
android:text="已支付确认"
|
android:text="已支付确认"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
android:layout_width="120dp"
|
android:layout_width="120dp"
|
||||||
android:layout_height="120dp"
|
android:layout_height="120dp"
|
||||||
android:src="@drawable/ic_avatar_default"
|
android:src="@drawable/ic_avatar_default"
|
||||||
|
app:strokeColor="@android:color/transparent"
|
||||||
app:shapeAppearance="@style/CircleStyle"
|
app:shapeAppearance="@style/CircleStyle"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|||||||
@@ -93,6 +93,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
|
app:strokeColor="@android:color/transparent"
|
||||||
app:shapeAppearance="@style/round10dpCornerStyle"
|
app:shapeAppearance="@style/round10dpCornerStyle"
|
||||||
tools:src="@mipmap/ic_launcher"/>
|
tools:src="@mipmap/ic_launcher"/>
|
||||||
|
|
||||||
@@ -102,6 +103,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:src="@mipmap/ic_launcher_round"
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
|
app:strokeColor="@android:color/transparent"
|
||||||
app:shapeAppearance="@style/round10dpCornerStyle"
|
app:shapeAppearance="@style/round10dpCornerStyle"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,7 @@
|
|||||||
android:src="@drawable/ic_avatar_default"
|
android:src="@drawable/ic_avatar_default"
|
||||||
android:layout_marginTop="28dp"
|
android:layout_marginTop="28dp"
|
||||||
app:shapeAppearance="@style/CircleStyle"
|
app:shapeAppearance="@style/CircleStyle"
|
||||||
|
app:strokeColor="@android:color/transparent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user