diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3b24cb5..095ded5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -111,6 +111,11 @@ android:theme="@style/Theme.DishMatch.NoSplash" android:screenOrientation="portrait" tools:ignore="DiscouragedApi,LockedOrientationActivity" /> + (mu b.root.layoutParams = (b.root.layoutParams as ViewGroup.MarginLayoutParams).also { it.width = cellSize it.height = cellSize - it.setMargins(margin, margin, margin, margin) + // 第1行:顶部间距为0,底部间距2dp,左右保持原有 margin + if (viewType == 0) it.setMargins(margin, 0, margin, 2.dp) + else it.setMargins(margin, margin, margin, margin) } val baseSp = (cellSize / parent.resources.displayMetrics.density).toInt() b.tvAddr.textSize = (baseSp * 0.16f).coerceIn(9f, 14f) diff --git a/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt b/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt index fbc5be1..741a0f1 100644 --- a/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt @@ -26,6 +26,7 @@ import com.shuwei.dish.match.entity.SeasoningEntity import com.shuwei.dish.match.net.NetViewModel import com.shuwei.dish.match.ui.InitActivity import com.shuwei.dish.match.utils.ActivityManager +import com.shuwei.dish.match.utils.ext.dp import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.invisible import com.shuwei.dish.match.utils.ext.visible @@ -65,6 +66,10 @@ open class BaseActivity : AppCompatActivity() { // systemBarsBehavior = // WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 滑动时临时显示 // } + // 主设备垂直 padding 30dp,子设备 15dp + val verticalPadding = if (GlobalData.deviceRole == DeviceRole.MASTER) 30.dp else 10.dp + binding.tvLeftTime.setPadding(0, verticalPadding, 0, verticalPadding) + binding.tvRightTime.setPadding(0, verticalPadding, 0, verticalPadding) binding.llTitleBar.gone() binding.ivBack.setOnClickListener { finish() } } diff --git a/app/src/main/java/com/shuwei/dish/match/dialog/CommonDialog.kt b/app/src/main/java/com/shuwei/dish/match/dialog/CommonDialog.kt index 5776e83..365115f 100644 --- a/app/src/main/java/com/shuwei/dish/match/dialog/CommonDialog.kt +++ b/app/src/main/java/com/shuwei/dish/match/dialog/CommonDialog.kt @@ -7,7 +7,11 @@ import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.View import android.view.Window +import android.widget.LinearLayout import androidx.annotation.StyleRes +import androidx.core.view.updateLayoutParams +import com.shuwei.dish.match.base.DeviceRole +import com.shuwei.dish.match.base.GlobalData import com.shuwei.dish.match.databinding.DialogCommonBinding import com.shuwei.dish.match.utils.ext.dp import java.lang.ref.WeakReference @@ -55,12 +59,17 @@ open class CommonDialog( binding = DialogCommonBinding.inflate(layoutInflater) setContentView(binding.root) window?.run { + val screenWidth = context.resources.displayMetrics.widthPixels + val isMaster = GlobalData.deviceRole == DeviceRole.MASTER attributes = attributes.apply { - width = 660.dp + width = if (isMaster) (screenWidth * 0.72f).toInt() else (screenWidth * 0.60f).toInt() } setBackgroundDrawable(ColorDrawable()) setCancelable(false) } + val verticalMargin = if (GlobalData.deviceRole == DeviceRole.MASTER) 35.dp else 20.dp + binding.tvTitle.updateLayoutParams { topMargin = verticalMargin } + binding.tvContent.updateLayoutParams { bottomMargin = verticalMargin } binding.btnLeft.setOnClickListener { negativeClick?.invoke() dismiss() diff --git a/app/src/main/java/com/shuwei/dish/match/dialog/LoadingDialog.kt b/app/src/main/java/com/shuwei/dish/match/dialog/LoadingDialog.kt index d004dcb..f893ce8 100644 --- a/app/src/main/java/com/shuwei/dish/match/dialog/LoadingDialog.kt +++ b/app/src/main/java/com/shuwei/dish/match/dialog/LoadingDialog.kt @@ -66,10 +66,10 @@ class LoadingDialog( setCancelable(true) window?.run { setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) - setFlags( - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - ); +// setFlags( +// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, +// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE +// ); } setOnDismissListener { onDismiss() diff --git a/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt new file mode 100644 index 0000000..fc0df2b --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt @@ -0,0 +1,213 @@ +package com.shuwei.dish.match.ui + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.recyclerview.widget.GridLayoutManager +import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter +import com.shuwei.dish.match.base.BaseActivity +import com.shuwei.dish.match.databinding.ActivityFoodRecognizeBinding +import com.shuwei.dish.match.dialog.FoodSearchDialog +import com.shuwei.dish.match.entity.CookFoodGoodsEntity +import com.shuwei.dish.match.utils.ActivityManager +import com.shuwei.dish.match.utils.AddressUtil +import com.shuwei.dish.match.utils.WeightUtil +import com.shuwei.dish.match.utils.ext.clickWithDebounce +import com.shuwei.dish.match.utils.ext.gone +import com.shuwei.dish.match.utils.ext.visible + +/** + * 食材识别结果页面 + * 展示识别到的食材照片、食材列表、选中食材名称及秤1重量 + */ +class FoodRecognizeActivity : BaseActivity() { + + companion object { + private const val TAG = "FoodRecognizeActivity" + private const val EXTRA_IMAGE_URI = "extra_image_uri" + const val EXTRA_SELECTED_ITEM = "extra_selected_item" + + /** 内存缓存食材列表,避免通过 Intent 序列化传递大数据 */ + private var pendingGoodsList: ArrayList? = null + + /** + * 启动本页面,通过 onResult 回调返回用户选中的食材 + * @param activity 宿主 Activity + * @param imageUri 食材照片路径(可为空) + * @param goodsList 识别到的食材列表 + * @param onResult 页面关闭后回调,携带用户选中的食材(未选中时为 null) + */ + fun start( + activity: BaseActivity, + imageUri: String? = null, + goodsList: ArrayList = arrayListOf(), + onResult: (CookFoodGoodsEntity?) -> Unit = {} + ) { + pendingGoodsList = goodsList + val intent = Intent(activity, FoodRecognizeActivity::class.java).apply { + putExtra(EXTRA_IMAGE_URI, imageUri) + } + activity.startActivity(intent) { resultIntent -> + @Suppress("DEPRECATION") + val item = resultIntent?.getSerializableExtra(EXTRA_SELECTED_ITEM) as? CookFoodGoodsEntity + onResult(item) + } + } + + /** + * 关闭当前识别页面,并将选中食材通过 Activity Result 回传 + * 由外部(如重量归零时)调用 + */ + fun close() { + ActivityManager.getActivityStack() + .filterIsInstance() + .lastOrNull() + ?.finishWithResult() + } + } + + private lateinit var binding: ActivityFoodRecognizeBinding + + private val list = mutableListOf() + + private val adapter = GoodsInfoSearchAdapter(list).apply { + setOnItemClickListener { _, _, position -> + // 更新选中状态 + list.forEachIndexed { i, item -> item.isClicked = (i == position) } + notifyDataSetChanged() + // 同步更新选中食材名称 + binding.tvSelectedFood.text = list[position].goodsName ?: "-" + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityFoodRecognizeBinding.inflate(layoutInflater) + setContentView(binding.root) + setHeaderBackground() + + // 显示标题栏 + setTitleBar(titleBarAction = { it.visible() }, titleAction = { it.text = "食材识别" }) + + initData() + initRecyclerView() + initClickListeners() + registerWeightListener() + } + + /** + * 从 Intent 读取数据并初始化页面 + */ + private fun initData() { + // 加载食材照片 + val imageUri = intent.getStringExtra(EXTRA_IMAGE_URI) + if (!imageUri.isNullOrBlank()) { + binding.ivFoodPhoto.setImageURI(Uri.parse(imageUri)) + } + + // 取出内存缓存的食材列表,取用后立即清空 + val goodsList = pendingGoodsList?.also { pendingGoodsList = null } + if (!goodsList.isNullOrEmpty()) { + list.addAll(goodsList) + // 默认选中第一项 + list[0].isClicked = true + binding.tvSelectedFood.text = list[0].goodsName ?: "-" + } + } + + /** + * 初始化 RecyclerView + */ + private fun initRecyclerView() { + binding.recyclerView.run { + layoutManager = GridLayoutManager(this@FoodRecognizeActivity, 2) + adapter = this@FoodRecognizeActivity.adapter + } + updateListVisibility() + } + + /** + * 根据列表是否有数据控制 RecyclerView 显隐 + */ + private fun updateListVisibility() { + if (list.isEmpty()) { + binding.recyclerView.gone() + } else { + binding.recyclerView.visible() + } + } + + /** + * 初始化按钮点击事件 + */ + private fun initClickListeners() { + // 更换食材:打开食材搜索弹窗 + binding.btnChangeFood.clickWithDebounce { + val currentName = if (list.isNotEmpty()) list.firstOrNull { it.isClicked }?.goodsName else null + FoodSearchDialog( + activity = this, + defGoodsName = currentName, + onItemSelected = { item -> + // 用搜索结果替换当前选中食材 + updateSelectedFood(item) + } + ).show() + } + + // 取消:关闭页面 + binding.btnCancel.setOnClickListener { finish() } + } + + /** + * 用新食材替换当前选中项(或追加到列表首位) + */ + @SuppressLint("NotifyDataSetChanged") + private fun updateSelectedFood(item: CookFoodGoodsEntity) { + item.isClicked = true + val clickedIndex = list.indexOfFirst { it.isClicked && it.goodsId != item.goodsId } + if (clickedIndex >= 0) { + list[clickedIndex].isClicked = false + } + // 若列表中已存在则直接选中,否则插入到首位 + val existIndex = list.indexOfFirst { it.goodsId == item.goodsId } + if (existIndex >= 0) { + list.forEachIndexed { i, e -> e.isClicked = (i == existIndex) } + } else { + list.add(0, item) + } + adapter.notifyDataSetChanged() + binding.tvSelectedFood.text = item.goodsName ?: "-" + updateListVisibility() + } + + /** + * 注册秤1重量监听 + */ + private fun registerWeightListener() { + WeightUtil.addWeightListener(TAG) { address, _, weight -> + if (address == AddressUtil.ONE) { + runOnUiThread { + binding.tvWeight.text = "$weight g" + } + } + } + } + + /** + * 携带当前选中食材关闭页面 + */ + private fun finishWithResult() { + val selected = list.firstOrNull { it.isClicked } + val intent = Intent().apply { + selected?.let { putExtra(EXTRA_SELECTED_ITEM, it) } + } + setResult(RESULT_OK, intent) + finish() + } + + override fun onDestroy() { + super.onDestroy() + WeightUtil.removeWeightListener(TAG) + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt index b63eda9..d3f9b54 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt @@ -12,7 +12,6 @@ import com.shuwei.dish.match.adapter.DishPartAdapter import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding import com.shuwei.dish.match.dialog.CommonDialog -import com.shuwei.dish.match.dialog.FoodRecognizeDialog import com.shuwei.dish.match.dialog.FoodSearchDialog import com.shuwei.dish.match.entity.CookFoodEntity import com.shuwei.dish.match.entity.CookFoodGoodsEntity @@ -158,6 +157,10 @@ class PrepareCookActivity : BaseActivity() { } private val isTakingPhoto = AtomicBoolean(false) + private var isRecognizeOpen = false + // 页面可见时允许识别,onPause 后禁止,onResume 后恢复 + private var isRecognizeEnabled = true + private var lastPhotoUri: Uri? = null private var lastWeight = 0.0 private var currentWeight = 0.0 @@ -168,10 +171,11 @@ class PrepareCookActivity : BaseActivity() { private fun recognizeFood(weight: Double) { this.currentWeight = weight if (weight < WEIGHT_RECOGNIZE_VALUE) { - if (recognizeDialog.isShowing) recognizeDialog.dismiss() + isRecognizeOpen = false + FoodRecognizeActivity.close() return } - if (isTakingPhoto.get() || recognizeDialog.isShowing || abs(lastWeight - weight) <= WEIGHT_CHANGE_VALUE) return + if (!isRecognizeEnabled || isTakingPhoto.get() || isRecognizeOpen || abs(lastWeight - weight) <= WEIGHT_CHANGE_VALUE) return isTakingPhoto.set(true) cameraUtils.takePhoto( succCallback = cameraSuccessCallback, @@ -194,9 +198,13 @@ class PrepareCookActivity : BaseActivity() { // materialType = isNewDishType = true isOriginalData = false + useWeight = lastWeight + isSetFinished = true }) - onFoodItemClick(list.size - 1) - binding.rvDishPartList.smoothScrollToPosition(list.size - 1) + val positon = list.size - 1 + dishPartAdapter.notifyItemInserted(positon) + onFoodItemClick(positon) + binding.rvDishPartList.smoothScrollToPosition(positon) } /** @@ -298,18 +306,24 @@ class PrepareCookActivity : BaseActivity() { private var firstReqSize = 0 private val list = mutableListOf() - private val recognizeDialog by lazy { - FoodRecognizeDialog( - activity = this, - onDismiss = { isTakingPhoto.set(false) }, - onItemSelected = foodSelectCallback - ) - } private val dishPartAdapter by lazy { DishPartAdapter(list).apply { setOnItemClickListener { _, _, positon -> onFoodItemClick(positon) } + addOnItemChildClickListener(R.id.ivClearIcon) { _, _, positon -> + if (list[positon].isOriginalData) { + list[positon].run { + isNewDishType = false + isOriginalData = false + useWeight = 0.0 + isSetFinished = false + } + notifyItemChanged(positon) + return@addOnItemChildClickListener + } + removeAt(positon) + } } } @@ -388,11 +402,14 @@ class PrepareCookActivity : BaseActivity() { override fun onResume() { super.onResume() + isRecognizeEnabled = true + isRecognizeOpen = false cameraUtils.bind() } override fun onPause() { super.onPause() + isRecognizeEnabled = false cameraUtils.unbind() } @@ -438,6 +455,7 @@ class PrepareCookActivity : BaseActivity() { */ private val cameraSuccessCallback: (Uri) -> Unit = { uri -> Log.d(TAG, "takePhoto success") + lastPhotoUri = uri lifecycleScope.launch { withContext(Dispatchers.IO) { val bitmap = ImageUtil.uriToBitmap(this@PrepareCookActivity, uri) @@ -485,7 +503,13 @@ class PrepareCookActivity : BaseActivity() { foodId = index.toString() )) } - recognizeDialog.loadData(foodList) + FoodRecognizeActivity.start( + activity = this, + imageUri = lastPhotoUri?.toString(), + goodsList = ArrayList(foodList), + onResult = { item -> item?.let { foodSelectCallback(it) } } + ) + isRecognizeOpen = true isTakingPhoto.set(false) lastWeight = currentWeight } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt index 175384c..8496e4b 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SlaveActivity.kt @@ -2,8 +2,10 @@ package com.shuwei.dish.match.ui import android.annotation.SuppressLint import android.os.Bundle +import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import android.widget.LinearLayout import androidx.activity.addCallback import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexDirection @@ -74,7 +76,11 @@ class SlaveActivity : BaseActivity() { val smallSize = usable / 9 val largeSize = (usable - smallSize * 6) / 2 val rvHeight = smallSize * 3 + 3 * 4.dp - binding.rvScaleList.layoutParams = binding.rvScaleList.layoutParams.also { it.height = rvHeight } + binding.rvScaleList.updateLayoutParams { + height = rvHeight + weight = 0f + topMargin = 20.dp + } binding.rvScaleList.layoutManager = FlexboxLayoutManager(this).apply { flexDirection = FlexDirection.COLUMN flexWrap = FlexWrap.WRAP @@ -86,6 +92,7 @@ class SlaveActivity : BaseActivity() { binding.rvScaleList.adapter = scale22Adapter } ScaleDeviceConfig.DEVICE_ID_18 -> { + binding.rvScaleList.updateLayoutParams { topMargin = 0 } binding.rvScaleList.layoutManager = GridLayoutManager(this, 6).apply { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int) = 1 @@ -97,6 +104,7 @@ class SlaveActivity : BaseActivity() { binding.rvScaleList.adapter = scale18Adapter } else -> { + binding.rvScaleList.updateLayoutParams { topMargin = 20.dp } binding.rvScaleList.layoutManager = LinearLayoutManager(this) linearAdapter.setOnItemClickListener { _, _, position -> val item = linearAdapter.items.getOrNull(position) ?: return@setOnItemClickListener diff --git a/app/src/main/res/drawable-xxxhdpi/ic_dish_clear.png b/app/src/main/res/drawable-xxxhdpi/ic_dish_clear.png new file mode 100644 index 0000000..970c57a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_dish_clear.png differ diff --git a/app/src/main/res/layout/activity_base.xml b/app/src/main/res/layout/activity_base.xml index 71ed615..cfb267c 100644 --- a/app/src/main/res/layout/activity_base.xml +++ b/app/src/main/res/layout/activity_base.xml @@ -21,8 +21,6 @@ android:layout_height="wrap_content" android:layout_marginStart="30dp" android:gravity="center_vertical" - android:paddingTop="30dp" - android:paddingBottom="30dp" android:textColor="@color/black" android:textSize="28sp" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/activity_food_recognize.xml b/app/src/main/res/layout/activity_food_recognize.xml new file mode 100644 index 0000000..6e3a14b --- /dev/null +++ b/app/src/main/res/layout/activity_food_recognize.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_slave.xml b/app/src/main/res/layout/activity_slave.xml index 3fe55c9..47b0e2a 100644 --- a/app/src/main/res/layout/activity_slave.xml +++ b/app/src/main/res/layout/activity_slave.xml @@ -4,20 +4,36 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:padding="12dp" + android:paddingHorizontal="5dp" tools:background="@color/bg_color"> - - + + android:orientation="horizontal" + android:gravity="center"> + + + + + + + + - - - + +