Author SHA1 Message Date
lvmeng d6a9c3562d refactor(net): 优化API请求错误处理和基础组件功能
- 添加JsonSyntaxException异常处理,提供更友好的错误提示
- 修复API响应判断逻辑,移除不必要的空值检查
- 优化HttpException转换为ApiException时的数据解析
- 移除BaseActivity中废弃的launch方法和相关变量
- 重构BaseActivity中的权限请求和页面跳转回调机制
- 更新Loading对话框的显示方法,支持自定义消息和内容更新
- 修复CollectFragment中的语法错误和日志记录功能
- 优化DishSamplingActivity中的页面跳转实现方式
- 完善SettingActivity中的相机权限检查逻辑
2026-03-30 15:28:52 +08:00
lvmeng 28c7628321 提交昨天的修改:优化多个模块和UI效果 2026-03-17 13:42:09 +08:00
lvmeng 3cf9818dad 优化SettingActivity底部菜单按钮效果:选中状态显示白色字体和圆角下划线 2026-03-17 13:40:54 +08:00
lvmeng c8eb2b8f24 ttyS4改为ttyS7,删除manifest中sharedUserId配置 2026-03-16 11:07:15 +08:00
lvmeng 795deae228 优化 2026-02-09 16:28:30 +08:00
lvmeng 873ae95c0b 优化 2026-02-06 17:31:52 +08:00
lvmeng a4f3f9527c 优化 2026-02-05 15:06:45 +08:00
lvmeng eaa1b1eb00 新接口调试 2026-01-30 18:01:07 +08:00
lvmeng a4905e7226 新接口调试 2026-01-29 18:51:42 +08:00
116 changed files with 5451 additions and 1898 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-09-10T07:28:30.961571900Z">
<DropdownSelection timestamp="2026-03-16T07:12:10.978585600Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.21:5555;connection=bc7af9a5" />
<DeviceId pluginId="Default" identifier="serial=192.168.1.115:5555;connection=8c23341a" />
</handle>
</Target>
</DropdownSelection>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="4ab8791:19ce5d9ffb1:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+4
View File
@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready
+57 -22
View File
@@ -1,8 +1,14 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("com.google.devtools.ksp") version "2.0.21-1.0.27"
// alias(libs.plugins.android.application)
// alias(libs.plugins.kotlin.android)
// id("com.google.devtools.ksp") version "2.0.21-1.0.27"
id("com.android.application")
id("org.jetbrains.kotlin.android")
// id("com.google.dagger.hilt.android")
id("com.google.devtools.ksp")
id("kotlin-parcelize")
kotlin("kapt")
}
android {
@@ -42,10 +48,12 @@ android {
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("debug_507")
// buildConfigField("boolean", "DEBUG", "false")
}
debug {
//507扫码秤主板在android.buildTypes{}内增加
signingConfig = signingConfigs.getByName("debug_507")
// buildConfigField("boolean", "DEBUG", "true")
}
}
compileOptions {
@@ -57,6 +65,7 @@ android {
}
buildFeatures {
viewBinding = true
buildConfig = true
}
packagingOptions {
pickFirst("lib/armeabi-v7a/libserial_port.so")
@@ -74,7 +83,9 @@ android {
}
// li
}
val isDebug = gradle.startParameter.taskNames.any {
it.contains("debug", ignoreCase = true)
}
dependencies {
implementation(fileTree("libs") { include("*.jar", "*.aar") })
implementation(libs.androidx.core.ktx)
@@ -86,30 +97,54 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.google.code.gson:gson:2.10.1")
// implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30")
implementation(libs.gson)
implementation(libs.okhttp)
implementation(libs.logging.interceptor)
implementation(libs.retrofit)
implementation(libs.converter.gson)
implementation("io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.2")
implementation(libs.baserecyclerviewadapterhelper4)
implementation("androidx.room:room-runtime:2.5.2")
ksp("androidx.room:room-compiler:2.5.2")
implementation("androidx.room:room-ktx:2.5.2") // Kotlin扩展
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.activity:activity-ktx:1.8.0")
// room
implementation(libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
// implementation("biz.laenger.android:vpbs:0.0.6")
//必须 1.0.0 以上
implementation("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha")
//核心必须依赖
implementation("io.github.scwang90:refresh-header-classics:3.0.0-alpha")
//经典刷新头
implementation("io.github.scwang90:refresh-header-radar:3.0.0-alpha")
implementation("io.github.scwang90:refresh-footer-classics:3.0.0-alpha")
//经典加载
// refresh-layout
implementation(libs.refresh.layout.kernel)
implementation(libs.refresh.header.classics)
implementation(libs.refresh.header.radar)
implementation(libs.refresh.footer.classics)
//implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
// glide
implementation(libs.glide)
annotationProcessor(libs.compiler)
// camerax
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.extensions)
//pytorch
implementation (libs.pytorch.android)
implementation (libs.pytorch.android.torchvision)
// objectbox
if (isDebug) {
debugImplementation(libs.objectbox.android.objectbrowser)
} else {
releaseImplementation(libs.objectbox.android)
}
}
apply(plugin = "io.objectbox")
+49
View File
@@ -0,0 +1,49 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "1:6465153867413579585",
"lastPropertyId": "4:3660727688686055762",
"name": "Food",
"properties": [
{
"id": "1:3688350375714699356",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:6503828433160216329",
"name": "name",
"type": 9
},
{
"id": "3:1726525909323356793",
"name": "foodIdx",
"type": 5
},
{
"id": "4:3660727688686055762",
"name": "foodVector",
"indexId": "1:5497952887792054610",
"type": 28,
"flags": 8
}
],
"relations": []
}
],
"lastEntityId": "1:6465153867413579585",
"lastIndexId": "1:5497952887792054610",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [],
"retiredRelationUids": [],
"version": 1
}
Binary file not shown.
Binary file not shown.
+10 -3
View File
@@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:sharedUserId="android.uid.system">
xmlns:tools="http://schemas.android.com/tools">
<!-- android:sharedUserId="android.uid.system"-->
<uses-feature android:name="android.hardware.usb.host" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
@@ -85,6 +89,9 @@
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.dish.match.ui.DeviceConfigActivity" />
<activity android:name="com.shuwei.dish.match.ui.SettingActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.dish.match.ui.SubmitFoodActivity" />
<receiver
Binary file not shown.
+272
View File
@@ -0,0 +1,272 @@
{
"class_names": [
"三黄鸡",
"上海青WP2026020200041",
"冰草WP2026020900054",
"刀豆",
"北豆腐WP2025111900007",
"味精WP2026013000034",
"圆生菜WP2026020900053",
"圆白菜[卷心菜,包菜]WP2025111000063",
"基围虾WP2025110700019",
"多宝鱼WP2025112600011",
"大白菜(代表值)WP2026022800638",
"大葱WP2026013000025",
"姜[黄姜]WP2026022800956",
"娃娃菜WP2026020200005",
"小白菜WP2026022800031",
"小米辣椒WP2026013000027",
"小葱WP2026013000029",
"山药(带皮)WP2026020200008",
"巨人笋",
"广东菜心WP2025120100014",
"木耳(干)[黑木耳,云耳]WP2026022801182",
"杏鲍菇WP2026022700030",
"松花蛋",
"松茸菌WP2025120100016",
"沙葱",
"海带(浸)[江白菜,昆布]WP2026022800035",
"牛腩WP2025110700043",
"猪五花肉WP2026020300048",
"猪腿肉WP2025110700040",
"玉米WP2025120100017",
"玉米糁(黄)WP2026022800038",
"番茄[西红柿]WP2026021000028",
"白吉饼WP2026013000024",
"笋",
"粉丝",
"紫甘蓝",
"红心萝卜[心里美]WP2026022800368",
"红提子WP2025111900006",
"红椒WP2026030300055",
"红洋葱WP2026030600054",
"红美人椒WP2026020300049",
"红薯",
"红薯粉条(袋)",
"羊排WP2025120300021",
"羊血WP2026013000038",
"胡萝卜WP2025110600008",
"芋头",
"花卷",
"芹菜WP2025111000065",
"苦菊",
"茄子(紫皮,长)WP2025110700024",
"茼蒿WP2026020200045",
"莲藕(带皮)",
"莴笋(鲜)[莴苣]WP2026020200009",
"菜花(白色)[花椰菜]",
"菠菜(鲜)[赤根菜]WP2026022800700",
"蒜WP2025111400003",
"蒜苗WP2026013000028",
"蒜薹(圆)",
"蒸鱼豉油WP2026013000032",
"薏米[薏仁米,苡米]WP2026022800037",
"西兰花[绿菜花]WP2025110700029",
"西葫芦",
"豆腐皮WP2025111000059",
"豆角",
"辣椒(红,小)WP2025111000066",
"辣椒(青,尖)WP2026022800504",
"醪糟",
"金针菇[(鲜)[智力菇]WP2026030600051",
"青线椒WP2026013000031",
"韭菜WP2026020200044",
"韭黄",
"馒头(代表值)WP2026030600056",
"香椿[香椿芽]WP2026022801086",
"香菇(鲜)[香蕈,冬菇]WP2026030600050",
"香菜WP2026013000030",
"马铃薯[土豆、洋芋]WP2026012900001",
"鸡全腿(带骨)",
"鸡翅(带骨)",
"鸡腿肉WP2025110700048",
"鸡腿(带骨)",
"鸡蛋WP2026013000039",
"麦芹",
"黄瓜(鲜)[胡瓜]WP2025110700031",
"黄豆芽WP2026020200042",
"黑袋子",
"黑鱼(整条鱼)WP2025110600012"
],
"class_to_idx": {
"三黄鸡": 0,
"上海青WP2026020200041": 1,
"冰草WP2026020900054": 2,
"刀豆": 3,
"北豆腐WP2025111900007": 4,
"味精WP2026013000034": 5,
"圆生菜WP2026020900053": 6,
"圆白菜[卷心菜,包菜]WP2025111000063": 7,
"基围虾WP2025110700019": 8,
"多宝鱼WP2025112600011": 9,
"大白菜(代表值)WP2026022800638": 10,
"大葱WP2026013000025": 11,
"姜[黄姜]WP2026022800956": 12,
"娃娃菜WP2026020200005": 13,
"小白菜WP2026022800031": 14,
"小米辣椒WP2026013000027": 15,
"小葱WP2026013000029": 16,
"山药(带皮)WP2026020200008": 17,
"巨人笋": 18,
"广东菜心WP2025120100014": 19,
"木耳(干)[黑木耳,云耳]WP2026022801182": 20,
"杏鲍菇WP2026022700030": 21,
"松花蛋": 22,
"松茸菌WP2025120100016": 23,
"沙葱": 24,
"海带(浸)[江白菜,昆布]WP2026022800035": 25,
"牛腩WP2025110700043": 26,
"猪五花肉WP2026020300048": 27,
"猪腿肉WP2025110700040": 28,
"玉米WP2025120100017": 29,
"玉米糁(黄)WP2026022800038": 30,
"番茄[西红柿]WP2026021000028": 31,
"白吉饼WP2026013000024": 32,
"笋": 33,
"粉丝": 34,
"紫甘蓝": 35,
"红心萝卜[心里美]WP2026022800368": 36,
"红提子WP2025111900006": 37,
"红椒WP2026030300055": 38,
"红洋葱WP2026030600054": 39,
"红美人椒WP2026020300049": 40,
"红薯": 41,
"红薯粉条(袋)": 42,
"羊排WP2025120300021": 43,
"羊血WP2026013000038": 44,
"胡萝卜WP2025110600008": 45,
"芋头": 46,
"花卷": 47,
"芹菜WP2025111000065": 48,
"苦菊": 49,
"茄子(紫皮,长)WP2025110700024": 50,
"茼蒿WP2026020200045": 51,
"莲藕(带皮)": 52,
"莴笋(鲜)[莴苣]WP2026020200009": 53,
"菜花(白色)[花椰菜]": 54,
"菠菜(鲜)[赤根菜]WP2026022800700": 55,
"蒜WP2025111400003": 56,
"蒜苗WP2026013000028": 57,
"蒜薹(圆)": 58,
"蒸鱼豉油WP2026013000032": 59,
"薏米[薏仁米,苡米]WP2026022800037": 60,
"西兰花[绿菜花]WP2025110700029": 61,
"西葫芦": 62,
"豆腐皮WP2025111000059": 63,
"豆角": 64,
"辣椒(红,小)WP2025111000066": 65,
"辣椒(青,尖)WP2026022800504": 66,
"醪糟": 67,
"金针菇[(鲜)[智力菇]WP2026030600051": 68,
"青线椒WP2026013000031": 69,
"韭菜WP2026020200044": 70,
"韭黄": 71,
"馒头(代表值)WP2026030600056": 72,
"香椿[香椿芽]WP2026022801086": 73,
"香菇(鲜)[香蕈,冬菇]WP2026030600050": 74,
"香菜WP2026013000030": 75,
"马铃薯[土豆、洋芋]WP2026012900001": 76,
"鸡全腿(带骨)": 77,
"鸡翅(带骨)": 78,
"鸡腿肉WP2025110700048": 79,
"鸡腿(带骨)": 80,
"鸡蛋WP2026013000039": 81,
"麦芹": 82,
"黄瓜(鲜)[胡瓜]WP2025110700031": 83,
"黄豆芽WP2026020200042": 84,
"黑袋子": 85,
"黑鱼(整条鱼)WP2025110600012": 86
},
"idx_to_class": {
"0": "三黄鸡",
"1": "上海青WP2026020200041",
"2": "冰草WP2026020900054",
"3": "刀豆",
"4": "北豆腐WP2025111900007",
"5": "味精WP2026013000034",
"6": "圆生菜WP2026020900053",
"7": "圆白菜[卷心菜,包菜]WP2025111000063",
"8": "基围虾WP2025110700019",
"9": "多宝鱼WP2025112600011",
"10": "大白菜(代表值)WP2026022800638",
"11": "大葱WP2026013000025",
"12": "姜[黄姜]WP2026022800956",
"13": "娃娃菜WP2026020200005",
"14": "小白菜WP2026022800031",
"15": "小米辣椒WP2026013000027",
"16": "小葱WP2026013000029",
"17": "山药(带皮)WP2026020200008",
"18": "巨人笋",
"19": "广东菜心WP2025120100014",
"20": "木耳(干)[黑木耳,云耳]WP2026022801182",
"21": "杏鲍菇WP2026022700030",
"22": "松花蛋",
"23": "松茸菌WP2025120100016",
"24": "沙葱",
"25": "海带(浸)[江白菜,昆布]WP2026022800035",
"26": "牛腩WP2025110700043",
"27": "猪五花肉WP2026020300048",
"28": "猪腿肉WP2025110700040",
"29": "玉米WP2025120100017",
"30": "玉米糁(黄)WP2026022800038",
"31": "番茄[西红柿]WP2026021000028",
"32": "白吉饼WP2026013000024",
"33": "笋",
"34": "粉丝",
"35": "紫甘蓝",
"36": "红心萝卜[心里美]WP2026022800368",
"37": "红提子WP2025111900006",
"38": "红椒WP2026030300055",
"39": "红洋葱WP2026030600054",
"40": "红美人椒WP2026020300049",
"41": "红薯",
"42": "红薯粉条(袋)",
"43": "羊排WP2025120300021",
"44": "羊血WP2026013000038",
"45": "胡萝卜WP2025110600008",
"46": "芋头",
"47": "花卷",
"48": "芹菜WP2025111000065",
"49": "苦菊",
"50": "茄子(紫皮,长)WP2025110700024",
"51": "茼蒿WP2026020200045",
"52": "莲藕(带皮)",
"53": "莴笋(鲜)[莴苣]WP2026020200009",
"54": "菜花(白色)[花椰菜]",
"55": "菠菜(鲜)[赤根菜]WP2026022800700",
"56": "蒜WP2025111400003",
"57": "蒜苗WP2026013000028",
"58": "蒜薹(圆)",
"59": "蒸鱼豉油WP2026013000032",
"60": "薏米[薏仁米,苡米]WP2026022800037",
"61": "西兰花[绿菜花]WP2025110700029",
"62": "西葫芦",
"63": "豆腐皮WP2025111000059",
"64": "豆角",
"65": "辣椒(红,小)WP2025111000066",
"66": "辣椒(青,尖)WP2026022800504",
"67": "醪糟",
"68": "金针菇[(鲜)[智力菇]WP2026030600051",
"69": "青线椒WP2026013000031",
"70": "韭菜WP2026020200044",
"71": "韭黄",
"72": "馒头(代表值)WP2026030600056",
"73": "香椿[香椿芽]WP2026022801086",
"74": "香菇(鲜)[香蕈,冬菇]WP2026030600050",
"75": "香菜WP2026013000030",
"76": "马铃薯[土豆、洋芋]WP2026012900001",
"77": "鸡全腿(带骨)",
"78": "鸡翅(带骨)",
"79": "鸡腿肉WP2025110700048",
"80": "鸡腿(带骨)",
"81": "鸡蛋WP2026013000039",
"82": "麦芹",
"83": "黄瓜(鲜)[胡瓜]WP2025110700031",
"84": "黄豆芽WP2026020200042",
"85": "黑袋子",
"86": "黑鱼(整条鱼)WP2025110600012"
},
"embedding_dim": 512,
"total_images": 2093,
"build_time": "2026-03-10T10:33:47.104848"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,58 @@
package com.shuwei.dish.match.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ListItemFoodCollectionBinding
import com.shuwei.dish.match.objbox.FoodCollectionBean
import com.shuwei.dish.match.utils.ext.load
class FoodCollectionAdapter (var list: MutableList<FoodCollectionBean>) :
BaseQuickAdapter<FoodCollectionBean, FoodCollectionAdapter.VH>(list) {
inner class VH(var binding: ListItemFoodCollectionBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemFoodCollectionBinding.inflate(inflater, parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: FoodCollectionBean?) {
val binding = holder.binding
item?.let {
binding.ivFinish.visibility = if (it.isFinish) View.VISIBLE else View.GONE
binding.ivDelete.visibility = if (it.isShowCamera) View.GONE else View.VISIBLE
binding.imageView.run {
if (it.isShowCamera) {
scaleType = ImageView.ScaleType.CENTER
setImageResource(R.drawable.ic_camera_default)
} else {
//scaleType = ImageView.ScaleType.FIT_CENTER
scaleType = ImageView.ScaleType.CENTER_CROP
//setImageURI(it.imageUri)
//setImageBitmap(it.bitmap)
if (it.imageUri!=null) {
setImageURI(it.imageUri)
} else {
load(it.imageFile)
}
// it.bitmap?.let { bitmap ->
// GlideUtils.loadRoundCornerWitBitmap(
// context,
// url = bitmap,
// imageView = this,
// radius = 8.dp
// )
// }
}
}
}
}
}
@@ -9,7 +9,6 @@ import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.databinding.ListItemDishBinding
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
import java.text.DecimalFormat
@@ -28,9 +27,9 @@ class SamplingAdapter(list: MutableList<FoodRecord>) :
holder.binding.run {
val isCooking = item!!.isCooking
clBlock.setBackgroundResource(R.drawable.shape_white_fb_15_corners)
val totalWeight = item!!.totalWeight ?: 0.toDouble()
val totalWeight = item.totalWeight ?: 0.toDouble()
tvDishName.run {
text = item!!.foodName
text = (item.foodName?:"").ifBlank { "--" }
setTextColor(
ContextCompat.getColor(
holder.itemView.context,
@@ -41,11 +40,16 @@ class SamplingAdapter(list: MutableList<FoodRecord>) :
tvDishCount.visible()
if (isCooking) {
tvDishCount.text = "制作统计:-kg"
tvShowState.text = "烹饪中"
tvShowState.text = "烹饪中"
} else {
val useWeight = if (totalWeight > 0.toDouble()) totalWeight else (item.foodWeight ?: 0.toDouble())
tvDishCount.text = "累计统计:${df.format(useWeight / 1000.0F)}kg(${item.count}次)"
tvShowState.text = "${df.format((item.foodWeight ?: 0.toDouble()) / 1000.0F)}kg"
val realTotalWeight = if (totalWeight > 0.toDouble()) totalWeight else (item.foodWeight
?: 0.toDouble())
val showTotalWeight =
if (realTotalWeight == 0.toDouble()) "-" else df.format(realTotalWeight / 1000.0F)
tvDishCount.text = "累计统计:${showTotalWeight}kg(${item.count ?: "-"}次)"
val foodWeight = item.foodWeight ?: 0.toDouble()
val showFoodWeight = if (foodWeight == 0.toDouble()) "-" else df.format(foodWeight / 1000.0F)
tvShowState.text = "${showFoodWeight}kg"
}
}
}
@@ -1,134 +0,0 @@
//package com.shuwei.dish.match.adapter
//
//import android.annotation.SuppressLint
//import android.widget.TextView
//import androidx.recyclerview.widget.GridLayoutManager
//import androidx.recyclerview.widget.RecyclerView
//import com.shuwei.dish.match.base.BaseActivity
//import com.shuwei.dish.match.dialog.BottomDialog2
//import com.shuwei.dish.match.entity.SeasoningEntity
//import com.shuwei.dish.match.ui.DeviceSettingActivity
//import com.shuwei.dish.match.ui.SubmitDishActivity
//import com.shuwei.dish.match.utils.DataUtil
//
//object SeasoningCellTool {
// private val TAG = "SeasoningCellTool"
//
// private val list = mutableListOf<SeasoningEntity>()
// private lateinit var activity: BaseActivity
// private var pageType: Int = 0
//
// @SuppressLint("StaticFieldLeak")
// private lateinit var cellAdapter: TextCellAdapter
//
// @SuppressLint("NotifyDataSetChanged")
// fun loadData(
// activity: BaseActivity,
// initList: MutableList<SeasoningEntity>? = null,
// recyclerView: RecyclerView,
// tvCell: TextView,
// pageType: Int = 0
// ) {
// this.activity = activity
// this.pageType = pageType
// list.clear()
// val tempList = buildData(initList, pageType)
// list.addAll(tempList)
// recyclerView.run {
// if (adapter == null) {
// layoutManager = GridLayoutManager(
// activity,
// 3,
// GridLayoutManager.VERTICAL,
// false
// )
// cellAdapter = TextCellAdapter(list).apply {
// onCellClick = { position ->
// val currentAddress = DataUtil.getWeighAddressArray().get(position)
// BottomDialog2(
// weighIndex = position,
// weighAddress = currentAddress,
// clickName = list[position].goodsName
// ).show(activity) { item ->
// list[position].let { current ->
// syncItem(current, item)
// //3-调料
// current.materialType = 3
// current.sort = position
// }
// notifyItemChanged(position)
// if (position == 14) {
// syncItem(list[10], list[14])
// syncItem(list[11], list[14])
// syncItem(list[13], list[14])
// TextCellAdapter.loadCell(tvCell, list[14])
// notifyItemChanged(10)
// notifyItemChanged(11)
// notifyItemChanged(13)
// notifyItemChanged(14)
// }
// if (activity is DeviceSettingActivity) {
// //activity.list = list
// activity.saveSeasoning(list[position])
// }
// }
// }
// }
// adapter = cellAdapter
// } else {
// cellAdapter.notifyDataSetChanged()
// }
// if (activity is SubmitDishActivity) {
// activity.updateSeasoningList(list)
// }
// }
// if (pageType == 1) {
// tvCell.setOnClickListener {
// cellAdapter.onCellClick?.invoke(14)
// }
// }
// TextCellAdapter.loadCell(tvCell, list[list.size - 1])
// }
//
// private fun buildData(
// initList: MutableList<SeasoningEntity>?,
// pageType: Int = 0
// ): MutableList<SeasoningEntity> {
// val list = mutableListOf<SeasoningEntity>()
// repeat(15) {
// list.add(SeasoningEntity().apply { this.pageType = pageType })
// }
// if (initList.isNullOrEmpty().not()) {
// initList.forEach {
// if (it.sort < list.size) {
// list[it.sort] = it.apply { this.pageType = pageType }
// }
// }
// var lastItem = initList.firstOrNull { it.sort == 14 }
// lastItem = lastItem ?: list[list.size - 1]
// list[10] = lastItem
// list[11] = lastItem
// list[13] = lastItem
// }
// return list
// }
//
// private fun syncItem(item: SeasoningEntity, newItem: SeasoningEntity) {
// item.run {
// goodsId = newItem.goodsId
// goodsName = newItem.goodsName
// goodsOrRelationCode = newItem.goodsOrRelationCode
// relateionType = newItem.relateionType
// materialType = newItem.materialType
// allEdible = newItem.allEdible
// useWeight = newItem.useWeight
// popularName = newItem.popularName
// canteenId = newItem.canteenId
// relateionType_dictText = newItem.relateionType_dictText
//
// foodId = newItem.foodId
// sort = newItem.sort
// }
// }
//
//}
@@ -4,7 +4,6 @@ import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
@@ -12,42 +11,32 @@ import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ActivityBaseBinding
import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.ui.HomeActivity.Companion.TAG
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
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.gone
import com.shuwei.dish.match.utils.ext.invisible
import com.shuwei.dish.match.utils.ext.put
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
import kotlin.getValue
@Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() {
private lateinit var binding: ActivityBaseBinding
private var launcher: ActivityResultLauncher<Intent>? = null
private var launchCallback: ((Intent?) -> Unit)? = null
public fun launch(cls: Class<*>, launchCallback: (Intent?) -> Unit) {
this.launchCallback = launchCallback
launcher?.launch(Intent(this, cls))
}
public fun launch(intent: Intent, launchCallback: (Intent?) -> Unit) {
this.launchCallback = launchCallback
launcher?.launch(intent)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -58,12 +47,6 @@ open class BaseActivity : AppCompatActivity() {
}
setContentView(binding.root)
statusBarDarkFont(enable = false)
launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
launchCallback?.invoke(result.data)
}
}
// window.setDecorFitsSystemWindows(false) // 启用 Edge-to-Edge
// window.insetsController?.apply {
@@ -95,8 +78,8 @@ open class BaseActivity : AppCompatActivity() {
fun statusBarDarkFont(enable: Boolean) {
//window.decorView.systemUiVisibility = if (enable) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0
val color = ContextCompat.getColor(this,
if (enable) R.color.black else R.color.white
val color = ContextCompat.getColor(
this, if (enable) R.color.black else R.color.white
)
binding.tvLeftTime.setTextColor(color)
binding.tvRightTime.setTextColor(color)
@@ -161,8 +144,11 @@ open class BaseActivity : AppCompatActivity() {
handler.removeCallbacks(updateTask)
}
fun showLoading() {
Loading.show(this)
fun showLoading(msg: String = "加载中……") {
Loading.show(
context = this,
message = msg
)
}
fun dismissLoading() {
@@ -172,9 +158,73 @@ open class BaseActivity : AppCompatActivity() {
fun delayDismissLoading() {
window.decorView.postDelayed({
Loading.dismiss()
},500)
}, 500)
}
val netViewModel: NetViewModel by viewModels()
fun queryGoodsList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.queryGoodsList(param, onSuccess, onFailure)
}
fun querySeasoningList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<SeasoningEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.querySeasoningList(param, onSuccess, onFailure)
}
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null
fun requestMultiplePermissions(
permissions: Array<String>,
callback: (isGranted: Boolean) -> Unit
) {
this.permissionCallback = callback
requestMultiplePermissionsLauncher.launch(permissions)
}
fun requestPermission(permission: String, callback: (isGranted: Boolean) -> Unit) {
this.permissionCallback = callback
requestPermissionLauncher.launch(permission)
}
val requestMultiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
var isGranted = true
permissions.entries.forEach {
if (!it.value) {
isGranted = false
}
}
permissionCallback?.invoke(isGranted)
}
// 权限请求回调
val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionCallback?.invoke(isGranted)
}
fun startActivity(intent: Intent, callback: (Intent?) -> Unit) {
this.activityCallback = callback
startActivityLauncher.launch(intent)
}
// activity页面返回的回调
private val startActivityLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
activityCallback?.invoke(it.data)
}
}
@@ -5,8 +5,13 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.util.Log
import com.shuwei.dish.match.R
import com.shuwei.dish.match.db.DatabaseProvider
import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.objbox.ObjectBox
import com.shuwei.dish.match.ui.InitActivity.Companion.TAG
import com.shuwei.dish.match.utils.AppUtil
import com.shuwei.dish.match.utils.BootReceiver
@@ -17,19 +22,23 @@ class BaseApp : Application() {
override fun onCreate() {
super.onCreate()
instance = this
val deviceId = AppUtil.getUDID(this)
Log.d(TAG, "onCreate: deviceId=$deviceId")
GlobalData.deviceId = deviceId
// GlobalData.deviceId = "39a7abdd06b3c7ab"
val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
registerReceiver(BootReceiver(), filter)
ObjectBox.init(this)
}
companion object {
// const val canteenId = "1678234139391512577"
var canteenId = "0"
var configUrl = ""
// var configUrl = ""
var token: String? = null
var deviceId: String? = null
// var token: String? = null
// var deviceId: String? = null
var appVersion: String = "1"
@Volatile
private var sharedPref: SharedPreferences? = null
@@ -18,8 +18,16 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): VB
abstract fun initialize()
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initialize()
}
}
@@ -0,0 +1,22 @@
package com.shuwei.dish.match.base
object GlobalData {
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
/**
* 设备id
*/
var deviceId: String = ""
var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
}
/**
*
*/
object GlobalKey {
const val KEY_EQUIPMENT_INFO = "equipmentInfo"
const val KEY_TOKEN = "tokenKey"
const val KEY_USER_INFO = "userInfoKey"
const val KEY_USER_NAME = "userNameKey"
}
@@ -3,7 +3,7 @@ package com.shuwei.dish.match.dialog
import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.isEmpty
@@ -15,23 +15,17 @@ import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogBottomSheetBinding
import com.shuwei.dish.match.databinding.DialogFoodSearchBinding
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.GoodsRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
/**
* @param goodsType 1-调料0-食材
* 食材搜索弹窗
*/
class BottomDialog(
private var goodsType: Int = 1,
private var defGoodsName:String? = null
class FoodSearchDialog(
private var defGoodsName: String? = null
) {
companion object {
const val TAG = "BottomDialog"
@@ -39,8 +33,8 @@ class BottomDialog(
private var list = mutableListOf<CookFoodGoodsEntity>()
private lateinit var binding: DialogBottomSheetBinding
private lateinit var goodsInfoSearchAdapter: GoodsInfoSearchAdapter
private lateinit var binding: DialogFoodSearchBinding
private lateinit var searchAdapter: GoodsInfoSearchAdapter
private var goodsName: String? = null
private var activity: BaseActivity? = null
@@ -50,11 +44,30 @@ class BottomDialog(
//直接使用BottomSheetDialog
var dialog: BottomSheetDialog? = null
val inflater = LayoutInflater.from(activity)
binding = DialogBottomSheetBinding.inflate(inflater)
binding.tvSheetName.text = if (goodsType == 1) "调料检索" else "食材检索"
binding.etSheetInput.hint = if (goodsType == 1) "输入调料名称" else "输入食材名称或速记码"
goodsInfoSearchAdapter = GoodsInfoSearchAdapter(list).apply {
binding = DialogFoodSearchBinding.inflate(inflater)
dialog = BottomSheetDialog(activity, R.style.BottomSheet).apply {
setContentView(binding.root)
setCancelable(true)
behavior.skipCollapsed = false // 跳过折叠状态
// behavior.peekHeight = 880.dp
// window?.setLayout(-1, 880.dp)
show()
setOnDismissListener {
activity.hideStatusBar()
}
binding.etSheetInput.requestFocus()
}
binding.tvSheetName.text = "食材检索"
binding.etSheetInput.run {
hint = "输入食材名称或速记码"
addTextChangedListener(afterTextChanged = { it: Editable? ->
if (it.isNullOrBlank()) {
list.clear()
searchAdapter.notifyDataSetChanged()
}
})
}
searchAdapter = GoodsInfoSearchAdapter(list).apply {
setOnItemClickListener { _, _, position ->
list[position].isClicked = true
notifyItemChanged(position)
@@ -66,7 +79,7 @@ class BottomDialog(
}
binding.recyclerView.run {
layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false)
adapter = goodsInfoSearchAdapter
adapter = searchAdapter
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(rv: RecyclerView, newState: Int) {
super.onScrollStateChanged(rv, newState);
@@ -80,17 +93,6 @@ class BottomDialog(
}
})
}
dialog = BottomSheetDialog(activity, R.style.BottomSheet).apply {
setContentView(binding.root)
setCancelable(true)
behavior.skipCollapsed = false // 跳过折叠状态
// behavior.peekHeight = 880.dp
// window?.setLayout(-1, 880.dp)
show()
setOnDismissListener {
activity.hideStatusBar()
}
}
binding.refreshLayout.setOnRefreshListener {
pageNo = 1
getGoodsList()
@@ -136,65 +138,107 @@ class BottomDialog(
}
private var pageNo = 1
private val pageSize = 120
private val pageSize = 50
/**
*/
@SuppressLint("NotifyDataSetChanged")
private fun getGoodsList() {
// val pathSegmentList = UrlConfig.QUERY_GOODS_LIST.split(delimiters = arrayOf("/"), limit = 1)
val queryParams = mutableMapOf(
"goodsType" to "$goodsType",
"canteenId" to BaseApp.canteenId,
"pageNo" to "$pageNo",
"pageSize" to "$pageSize"
// val queryParams = mutableMapOf(
// "goodsType" to "0",
// "canteenId" to BaseApp.canteenId,
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize"
// )
// if (goodsName.isNullOrBlank().not()) {
// queryParams.put("name", goodsName!!)
// }
// val sb = StringBuilder().apply {
// append(UrlConfig.QUERY_GOODS_LIST)
// append("?")
// queryParams.forEach { (key, value) ->
// append("$key=$value&")
// }
// }
// sb.deleteCharAt(sb.length - 1)
// Log.d(TAG, "getGoodsList: url = $sb")
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = {
// val json = it.toJsonString()
// Log.d(TAG, "getGoodsList: json=$json")
// val recordBean: GoodsRecordBean? = json.toObject<GoodsRecordBean>()
// if (recordBean == null || recordBean.records.isNullOrEmpty()) {
// activity?.toast("暂未搜索到食材信息")
// return@get
// }
// val records = recordBean.records
// if (pageNo == 1) {
// list.clear()
// }
// list.addAll(records!!)
// searchAdapter.notifyDataSetChanged()
// if (pageNo == 1) {
// binding.refreshLayout.finishRefresh(1200)
// } else {
// binding.refreshLayout.finishLoadMore(1200)
// }
// val isLoadMoreEnable = records.size >= pageSize
// binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
// if (isLoadMoreEnable) {
// pageNo++
// }
// }, doFailure = { code, msg ->
// activity?.toast(msg)
// if (pageNo == 1) {
// binding.refreshLayout.finishRefresh(1200)
// } else {
// binding.refreshLayout.finishLoadMore(1200)
// }
// })
val param = mutableMapOf<String, Any>(
"goodsType" to "0",
"placeId" to BaseApp.canteenId,
"pageNum" to pageNo,
"pageSize" to pageSize
)
if (goodsName.isNullOrBlank().not()) {
queryParams.put("name", goodsName!!)
param.put("goodsName", goodsName!!)
}
val sb = StringBuilder().apply {
append(UrlConfig.QUERY_GOODS_LIST)
append("?")
queryParams.forEach { (key, value) ->
append("$key=$value&")
}
activity?.queryGoodsList(param = param, onSuccess = {
loadGoodsList(it)
}, onFailure = { code, msg ->
activity?.toast(msg)
finishRefresh()
})
}
private fun finishRefresh() {
if (pageNo == 1) {
binding.refreshLayout.finishRefresh()
} else {
binding.refreshLayout.finishLoadMore()
}
}
@SuppressLint("NotifyDataSetChanged")
private fun loadGoodsList(records: MutableList<CookFoodGoodsEntity>?) {
finishRefresh()
if (records.isNullOrEmpty()) {
activity?.toast("暂未搜索到食材信息")
return
}
if (pageNo == 1) {
list.clear()
}
list.addAll(records)
searchAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
sb.deleteCharAt(sb.length - 1)
Log.d(TAG, "getGoodsList: url = $sb")
HttpUtil.get(
url = sb.toString(),
doSuccess = {
val json = it.toJsonString()
Log.d(TAG, "getGoodsList: json=$json")
val recordBean: GoodsRecordBean? = json.toObject<GoodsRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
activity?.toast(if (goodsType == 1) "暂未搜索到调料信息" else "暂未搜索到食材信息")
return@get
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
goodsInfoSearchAdapter.notifyDataSetChanged()
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
} else {
binding.refreshLayout.finishLoadMore(1200)
}
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
}, doFailure = { code, msg ->
activity?.toast(msg)
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
} else {
binding.refreshLayout.finishLoadMore(1200)
}
})
}
}
@@ -13,14 +13,22 @@ object Loading {
private var dialog: LoadingDialog? = null
fun show(context: Context) {
fun isShowing(): Boolean {
return dialog != null && dialog!!.isShowing
}
fun updateContent(content: String) {
if (isShowing().not()) return
dialog!!.updateContent(content)
}
fun show(context: Context, message: String = "加载中……", onDismiss: () -> Unit = {}) {
try {
if (dialog?.isShowing == true) {
dialog?.dismiss()
}
if (dialog == null) {
dialog = LoadingDialog(context)
dialog = LoadingDialog(context = context, message = message, onDismiss = onDismiss)
}
dialog?.show()
} catch (e: Exception) {
@@ -45,11 +53,14 @@ object Loading {
class LoadingDialog(
context: Context,
message: String = "加载中……",
private var onDismiss: () -> Unit = {}
message: String,
private var onDismiss: () -> Unit
) : Dialog(context, R.style.LoadingDialog) {
private val binding: DialogLoadingBinding by lazy {
DialogLoadingBinding.inflate(LayoutInflater.from(context))
}
init {
val binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
binding.tvMessage.text = message
setContentView(binding.root)
setCancelable(true)
@@ -64,6 +75,10 @@ class LoadingDialog(
onDismiss()
}
}
fun updateContent(content: String) {
binding.tvMessage.text = content
}
}
//class LoadingDialog3(
@@ -4,14 +4,18 @@ import android.annotation.SuppressLint
import android.graphics.Typeface
import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.core.graphics.toColorInt
import androidx.core.view.isEmpty
import androidx.core.widget.addTextChangedListener
@@ -22,11 +26,8 @@ import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.SeasoningSearchAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.DialogBottomSheet2Binding
import com.shuwei.dish.match.databinding.DialogSeasoningSearchBinding
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.SeasoningRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
@@ -34,15 +35,12 @@ import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.roundedDecimalPlace
import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
/**
* @param goodsType 1-调料0-食材
* 调料搜索弹窗
*/
class BottomDialog2(
class SeasoningSearchDialog(
private var weighIndex: Int,
private var weighAddress: Int,
private var clickName: String? = null
@@ -51,10 +49,9 @@ class BottomDialog2(
const val TAG = "BottomDialog2"
}
private val goodsType: Int = 1
private var list = mutableListOf<SeasoningEntity>()
private lateinit var binding: DialogBottomSheet2Binding
private lateinit var binding: DialogSeasoningSearchBinding
private lateinit var searchAdapter: SeasoningSearchAdapter
private var goodsName: String? = null
@@ -65,10 +62,17 @@ class BottomDialog2(
//直接使用BottomSheetDialog
var dialog: BottomSheetDialog? = null
val inflater = LayoutInflater.from(activity)
binding = DialogBottomSheet2Binding.inflate(inflater)
binding = DialogSeasoningSearchBinding.inflate(inflater)
//binding.tvSheetName.text = if (goodsType == 1) "调料检索" else "食材检索"
binding.etSheetInput.hint = if (goodsType == 1) "输入调料名称" else "输入食材名称或速记码"
binding.etSheetInput.run {
hint = "输入调料名称"
addTextChangedListener(afterTextChanged = { it: Editable? ->
if (it.isNullOrBlank()) {
list.clear()
searchAdapter.notifyDataSetChanged()
}
})
}
searchAdapter = SeasoningSearchAdapter(list).apply {
setOnItemClickListener { _, _, position ->
list[position].isClicked = true
@@ -99,12 +103,45 @@ class BottomDialog2(
setContentView(binding.root)
setCancelable(true)
behavior.skipCollapsed = false // 跳过折叠状态
// behavior.peekHeight = 880.dp
// window?.setLayout(-1, 880.dp)
//// behavior.peekHeight = 880.dp
//// window?.setLayout(-1, 880.dp)
// window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
//or WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
//or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING // 禁用额外平移
window?.setGravity(Gravity.BOTTOM)
window?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
window?.setWindowAnimations(R.style.DialogSoftInputAnimation)
window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
show()
// val onGlobalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
// val r = Rect()
// binding.root.getWindowVisibleDisplayFrame(r)
// val screenHeight = binding.root.rootView.height
// val keyboardHeight = screenHeight - r.bottom
//
// // 软键盘弹出(高度>200dp
// if (keyboardHeight > 200.dp) {
// // 调整 Dialog 底部边距,避开软键盘
// val layoutParams = binding.root.layoutParams as ViewGroup.MarginLayoutParams
//// layoutParams.bottomMargin = keyboardHeight
// layoutParams.bottomMargin = 200.dp
// binding.root.layoutParams = layoutParams
// } else {
// // 软键盘收起,恢复边距
// val layoutParams = binding.root.layoutParams as ViewGroup.MarginLayoutParams
// layoutParams.bottomMargin = 0
// binding.root.layoutParams = layoutParams
// }
// }
setOnDismissListener {
activity.hideStatusBar()
// binding.root.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
// binding.root.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
}
binding.refreshLayout.setOnRefreshListener {
pageNo = 1
@@ -130,6 +167,7 @@ class BottomDialog2(
}
}
binding.refreshLayout.setEnableRefresh(false)
binding.refreshLayout.setEnableLoadMore(false)
binding.tvClear.setOnClickListener {
Log.d(TAG, "show: weighAddress=$weighAddress")
WeightUtil.tareTwo(weighAddress)
@@ -138,7 +176,7 @@ class BottomDialog2(
// WeightUtil.tareTwo(num+1)
// }
}
WeightUtil.addWeightListener (
WeightUtil.addWeightListener(
weightKey = TAG,
getWeight = { address, state, weight ->
if (weighAddress == address) {
@@ -167,71 +205,119 @@ class BottomDialog2(
private var currentWeight = 0.toDouble()
private var pageNo = 1
private val pageSize = 120
private val pageSize = 50
/**
*/
@SuppressLint("NotifyDataSetChanged")
private fun getGoodsList() {
val queryParams = mutableMapOf(
"goodsType" to "$goodsType",
"canteenId" to BaseApp.canteenId,
"pageNo" to "$pageNo",
"pageSize" to "$pageSize"
// val queryParams = mutableMapOf(
// "goodsType" to "1",
// "canteenId" to BaseApp.canteenId,
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize"
// )
// if (goodsName.isNullOrBlank().not()) {
// queryParams.put("name", goodsName!!)
// }
// val sb = StringBuilder().apply {
// append(UrlConfig.QUERY_GOODS_LIST)
// append("?")
// queryParams.forEach { (key, value) ->
// append("$key=$value&")
// }
// }
// sb.deleteCharAt(sb.length - 1)
// Log.d(TAG, "getGoodsList: url = $sb")
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = {
// val json = it.toJsonString()
// Log.d(TAG, "getGoodsList: json=$json")
// val recordBean: SeasoningRecordBean? = json.toObject<SeasoningRecordBean>()
// if (recordBean == null || recordBean.records.isNullOrEmpty()) {
// activity?.toast( "暂未搜索到调料信息")
// return@get
// }
// val records = recordBean.records
// if (pageNo == 1) {
// list.clear()
// }
// list.addAll(records!!)
// searchAdapter.notifyDataSetChanged()
// if (pageNo == 1) {
// binding.refreshLayout.finishRefresh(1200)
// } else {
// binding.refreshLayout.finishLoadMore(1200)
// }
// val isLoadMoreEnable = records.size >= pageSize
// binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
// if (isLoadMoreEnable) {
// pageNo++
// }
// }, doFailure = { code, msg ->
// activity?.toast(msg)
// if (pageNo == 1) {
// binding.refreshLayout.finishRefresh(1200)
// } else {
// binding.refreshLayout.finishLoadMore(1200)
// }
// })
val param = mutableMapOf<String, Any>(
"goodsType" to "1",
"placeId" to BaseApp.canteenId,
"pageNum" to pageNo,
"pageSize" to pageSize
)
if (goodsName.isNullOrBlank().not()) {
queryParams.put("name", goodsName!!)
param.put("goodsName", goodsName!!)
}
val sb = StringBuilder().apply {
append(UrlConfig.QUERY_GOODS_LIST)
append("?")
queryParams.forEach { (key, value) ->
append("$key=$value&")
}
}
sb.deleteCharAt(sb.length - 1)
Log.d(TAG, "getGoodsList: url = $sb")
HttpUtil.get(
url = sb.toString(),
doSuccess = {
val json = it.toJsonString()
Log.d(TAG, "getGoodsList: json=$json")
val recordBean: SeasoningRecordBean? = json.toObject<SeasoningRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
activity?.toast(if (goodsType == 1) "暂未搜索到调料信息" else "暂未搜索到食材信息")
return@get
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
searchAdapter.notifyDataSetChanged()
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
} else {
binding.refreshLayout.finishLoadMore(1200)
}
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
}, doFailure = { code, msg ->
activity?.querySeasoningList(
param = param,
onSuccess = {
loadGoodsList(it)
},
onFailure = { code, msg ->
activity?.toast(msg)
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
} else {
binding.refreshLayout.finishLoadMore(1200)
}
})
finishRefresh()
}
)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadGoodsList(records: MutableList<SeasoningEntity>?) {
finishRefresh()
if (records.isNullOrEmpty()) {
activity?.toast("暂未搜索到调料信息")
return
}
if (pageNo == 1) {
list.clear()
}
list.addAll(records)
searchAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
}
private fun finishRefresh() {
if (pageNo == 1) {
binding.refreshLayout.finishRefresh()
} else {
binding.refreshLayout.finishLoadMore()
}
}
fun getTextSpan(weight: Double): SpannableStringBuilder {
var topWeight = "$weight"
var bottomUnit = ""
if (weight >= 1000) {
topWeight = "${(weight/1000).roundedDecimalPlace(3)}"
topWeight = "${(weight / 1000).roundedDecimalPlace(3)}"
bottomUnit = "千克"
}
return buildSpannableString {
@@ -23,6 +23,10 @@ data class CookFoodEntity(
) : BaseEntity {
@Ignore
var stFoodInfoConstituteList: MutableList<CookFoodGoodsEntity>? = null
@Ignore
var matchingConstituteInfoList: MutableList<CookFoodGoodsEntity>? = null
@Ignore
var foodConstituteList: MutableList<CookFoodGoodsEntity>? = null
constructor() : this(foodId = "") // 必需的空构造
}
@@ -43,10 +43,14 @@ class CookFoodGoodsEntity(
*/
var materialType: Int = 0,
// /**
// * 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
// */
// var allEdible: Int = 1,
/**
* 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
* 物品 是否全部可食:true-全部可食,false-部分可食,默认全部
*/
var allEdible: Int = 1,
var allEdible: Boolean = true,
/**
* 物品 用料重量
@@ -78,4 +82,8 @@ class CookFoodGoodsEntity(
@Ignore
var isClicked: Boolean = false
//true-原始数据,false-非原始数据
@Ignore
var isOriginalData: Boolean = true
}
@@ -13,7 +13,8 @@ data class FoodRecord(
var cookMode: Int = 0,
//1-早餐,2-午餐,3-晚餐
var dinnerType: String = "0",
//true-原始数据,false-非原始数据
var isOriginalData: Boolean = true,
var sort: Int = 0
) : Serializable
@@ -36,11 +36,14 @@ data class SeasoningEntity(
*/
var materialType: Int = 3,
// /**
// * 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
// */
// var allEdible: Int = 1,
/**
* 物品 是否全部可食:1-全部可食,0-部分可食,默认全部
* 物品 是否全部可食:true-全部可食,false-部分可食,默认全部
*/
var allEdible: Int = 1,
var allEdible: Boolean = true,
/**
* 物品 用料重量
*/
@@ -54,9 +57,9 @@ data class SeasoningEntity(
var sort: Int = 9999,
/**
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
*/
var isDel: Int = 0,
* isDel表示数据是否已删除,1-已删除,为无效数据,0-正常使用,有效数据
*/
var isDel: Int = 0,
var createTime: String = DateTimeUtil.formatDateTime(LocalDateTime.now())
) : BaseEntity {
@Ignore
@@ -1,70 +1,70 @@
package com.shuwei.dish.match.http
import android.annotation.SuppressLint
import android.util.Log
import okhttp3.Call
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.util.concurrent.TimeUnit
import javax.net.ssl.*
import java.security.SecureRandom
import java.security.cert.X509Certificate
class HttpClient private constructor() {
private val client: OkHttpClient by lazy {
OkHttpClient.Builder()
.apply {
connectTimeout(15, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
writeTimeout(15, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true }
//if (BuildConfig.Debug) {
addInterceptor(LoggingInterceptor())
// }
}
.build()
}
companion object {
val instance by lazy { HttpClient() }
}
fun newCall(request: Request): Call = client.newCall(request)
inner class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 打印请求日志
Log.d("OkHttp", "--> ${request.method} ${request.url}")
val response = chain.proceed(request)
// 打印响应日志
Log.d("OkHttp", "<-- ${response.code} ${response.request.url}")
return response
}
}
// 信任所有证书的TrustManager实现
@SuppressLint("CustomX509TrustManager")
class TrustAllCerts : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
// 创建信任所有证书的SSLSocketFactory
fun createSSLSocketFactory(): SSLSocketFactory {
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(TrustAllCerts()), SecureRandom())
}
return sslContext.socketFactory
}
}
//package com.shuwei.dish.match.http
//
//import android.annotation.SuppressLint
//import android.util.Log
//import okhttp3.Call
//import okhttp3.Interceptor
//import okhttp3.OkHttpClient
//import okhttp3.Request
//import okhttp3.Response
//import java.util.concurrent.TimeUnit
//
//import javax.net.ssl.*
//import java.security.SecureRandom
//import java.security.cert.X509Certificate
//
//class HttpClient private constructor() {
// private val client: OkHttpClient by lazy {
// OkHttpClient.Builder()
// .apply {
// connectTimeout(15, TimeUnit.SECONDS)
// readTimeout(30, TimeUnit.SECONDS)
// writeTimeout(15, TimeUnit.SECONDS)
// sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
// hostnameVerifier { _, _ -> true }
// //if (BuildConfig.Debug) {
// addInterceptor(LoggingInterceptor())
// // }
// }
// .build()
// }
//
// companion object {
// val instance by lazy { HttpClient() }
// }
//
// fun newCall(request: Request): Call = client.newCall(request)
//
// inner class LoggingInterceptor : Interceptor {
// override fun intercept(chain: Interceptor.Chain): Response {
// val request = chain.request()
// // 打印请求日志
// Log.d("OkHttp", "--> ${request.method} ${request.url}")
//
// val response = chain.proceed(request)
// // 打印响应日志
// Log.d("OkHttp", "<-- ${response.code} ${response.request.url}")
//
// return response
// }
// }
//
// // 信任所有证书的TrustManager实现
// @SuppressLint("CustomX509TrustManager")
// class TrustAllCerts : X509TrustManager {
// @SuppressLint("TrustAllX509TrustManager")
// override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
// @SuppressLint("TrustAllX509TrustManager")
// override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
// override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
// }
//
// // 创建信任所有证书的SSLSocketFactory
// fun createSSLSocketFactory(): SSLSocketFactory {
// val sslContext = SSLContext.getInstance("TLS").apply {
// init(null, arrayOf(TrustAllCerts()), SecureRandom())
// }
// return sslContext.socketFactory
// }
//
//}
@@ -1,199 +1,199 @@
package com.shuwei.dish.match.http
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.gson.reflect.TypeToken
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.BaseReq
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.toType
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
object HttpUtil {
var loopGetToken = true
fun runMainThread(action: () -> Unit) {
Handler(Looper.getMainLooper()).post {
action()
}
}
// GET请求(HTTPS
fun get(
url: String,
header: Map<String, String>?=null,
doSuccess: (data: Any) -> Unit,
doFailure: (code: Int?, msg: String?) -> Unit
) {
val request = Request.Builder()
.url(if (url.isHttpApi()) url else UrlConfig.BASE_URL + url)
.apply {
if (header != null) {
header.forEach {
addHeader(it.key, it.value)
}
} else {
val token = getToken()
if (token.isNotBlank()) {
addHeader("X-Access-Token", token)
}
}
}
.get()
.build()
HttpClient.instance.newCall(request).enqueue(CallbackImpl(doSuccess, doFailure))
}
//package com.shuwei.dish.match.http
//
//import android.os.Handler
//import android.os.Looper
//import android.util.Log
//import com.google.gson.reflect.TypeToken
//import com.shuwei.dish.match.base.BaseApp
//import com.shuwei.dish.match.base.BaseReq
//import com.shuwei.dish.match.utils.SpTool
//import com.shuwei.dish.match.utils.ext.toType
//import okhttp3.Call
//import okhttp3.Callback
//import okhttp3.MediaType.Companion.toMediaTypeOrNull
//import okhttp3.Request
//import okhttp3.RequestBody.Companion.toRequestBody
//import okhttp3.Response
//import java.io.IOException
//import java.util.concurrent.Executors
//import java.util.concurrent.TimeUnit
//
//
//object HttpUtil {
//
// var loopGetToken = true
//
// fun runMainThread(action: () -> Unit) {
// Handler(Looper.getMainLooper()).post {
// action()
// }
// }
//
// // GET请求(HTTPS
// fun get(
// isHttps: Boolean = false,
// host: String = "vip.shuziweidao.com",
// pathSegmentList: List<String>,
// queryParams: Map<String, String>,
// url: String,
// header: Map<String, String>?=null,
// doSuccess: (data: Any) -> Unit,
// doFailure: (code: Int?, msg: String?) -> Unit
// ) {
// val token = getToken()
// val url = HttpUrl.Builder()
// .scheme(if (isHttps) "https" else "http")
// .host(host)
// .apply {
// pathSegmentList.forEach { addPathSegment(it) }
// queryParams.forEach { (key, value) -> addQueryParameter(key, value) }
// }
// .build()
// val request = Request.Builder()
// .url(url)
// .url(if (url.isHttpApi()) url else UrlConfig.BASE_URL + url)
// .apply {
// if (token.isNotBlank()) {
// addHeader("Authorization", token)
// if (header != null) {
// header.forEach {
// addHeader(it.key, it.value)
// }
// } else {
// val token = getToken()
// if (token.isNotBlank()) {
// addHeader("X-Access-Token", token)
// }
// }
// }
// .get()
// .build()
// HttpClient.instance.newCall(request).enqueue(CallbackImpl(doSuccess, doFailure))
// }
// POST表单(HTTPS
// fun postForm(url: String, params: Map<String, String>, callback: Callback) {
// val formBody = FormBody.Builder().apply {
// params.forEach { (k, v) -> add(k, v) }
// }.build()
////
//// fun get(
//// isHttps: Boolean = false,
//// host: String = "vip.shuziweidao.com",
//// pathSegmentList: List<String>,
//// queryParams: Map<String, String>,
//// doSuccess: (data: Any) -> Unit,
//// doFailure: (code: Int?, msg: String?) -> Unit
//// ) {
//// val token = getToken()
//// val url = HttpUrl.Builder()
//// .scheme(if (isHttps) "https" else "http")
//// .host(host)
//// .apply {
//// pathSegmentList.forEach { addPathSegment(it) }
//// queryParams.forEach { (key, value) -> addQueryParameter(key, value) }
//// }
//// .build()
//// val request = Request.Builder()
//// .url(url)
//// .apply {
//// if (token.isNotBlank()) {
//// addHeader("Authorization", token)
//// }
//// }
//// .get()
//// .build()
//// HttpClient.instance.newCall(request).enqueue(CallbackImpl(doSuccess, doFailure))
//// }
//
// // POST表单(HTTPS
//// fun postForm(url: String, params: Map<String, String>, callback: Callback) {
//// val formBody = FormBody.Builder().apply {
//// params.forEach { (k, v) -> add(k, v) }
//// }.build()
////
//// Request.Builder()
//// .url(url)
//// .post(formBody)
//// .build().let { HttpClient.instance.newCall(it).enqueue(callback) }
//// }
//
// // POST JSONHTTPS
// fun postJson(
// url: String,
// json: String,
// doSuccess: (data: Any) -> Unit,
// doFailure: (code: Int?, msg: String?) -> Unit
// ) {
// val body = json
// .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
// val token = getToken()
// Request.Builder()
// .url(url)
// .post(formBody)
// .build().let { HttpClient.instance.newCall(it).enqueue(callback) }
// .url(if (url.isHttpApi()) url else UrlConfig.BASE_URL + url)
// .apply {
// if (token.isNotBlank()) {
// addHeader("X-Access-Token", token)
// }
// }
// .post(body)
// .build()
// .let { HttpClient.instance.newCall(it).enqueue(CallbackImpl(doSuccess, doFailure)) }
// }
// POST JSONHTTPS
fun postJson(
url: String,
json: String,
doSuccess: (data: Any) -> Unit,
doFailure: (code: Int?, msg: String?) -> Unit
) {
val body = json
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
val token = getToken()
Request.Builder()
.url(if (url.isHttpApi()) url else UrlConfig.BASE_URL + url)
.apply {
if (token.isNotBlank()) {
addHeader("X-Access-Token", token)
}
}
.post(body)
.build()
.let { HttpClient.instance.newCall(it).enqueue(CallbackImpl(doSuccess, doFailure)) }
}
val executor = Executors.newSingleThreadScheduledExecutor()
var getTokenIsRunning = false
private fun exeTokenThread() {
if (getTokenIsRunning.not()) {
executor.scheduleWithFixedDelay(
task, 0, 1, TimeUnit.MINUTES
)
getTokenIsRunning = true
}
}
val task = Runnable {
if (BaseApp.token.isNullOrBlank()) {
getAppToken()
} else {
executor.shutdown()
}
}
fun getToken() = SpTool.getString(SpTool.TOKEN)
//BaseApp.instance?.token ?:""
//BaseApp.getSharedPref()?.getString("token", "") ?: ""
fun getAppToken() {
val url = "${UrlConfig.GET_TOKEN}?qrcodeId=${SpTool.getString(SpTool.DEVICE_ID)}"
// Log.d(TAG, "getToken: url = $url")
get(url = url, doSuccess = {
SpTool.put(SpTool.TOKEN, it)
BaseApp.token = it.toString()
}) { code, msg ->
//toast(msg)
}
}
class CallbackImpl(
private val doSuccess: (data: Any) -> Unit,
private val doFailure: (code: Int?, msg: String?) -> Unit
) :
Callback {
override fun onFailure(call: Call, e: IOException) {
runMainThread {
e.printStackTrace()
doFailure(-1, "服务异常,${e.message}")
}
}
override fun onResponse(call: Call, response: Response) {
val respData = response.body?.string()
runMainThread {
Log.d("HttpUtil", respData ?: "")
runCatching {
val typeToken = object : TypeToken<BaseReq<Any>>() {}
val baseReq = respData?.toType<BaseReq<Any>>(typeToken = typeToken)
if (baseReq == null) {
doFailure(-1, "查询数据失败")
return@runCatching
}
if (baseReq.code != 200) {
doFailure(baseReq.code, baseReq.message)
//baseReq.code == 500 ||
if (baseReq.code == 401 && loopGetToken) {
exeTokenThread()
}
return@runCatching
}
doSuccess(baseReq.result ?: "")
}.onFailure {
it.printStackTrace()
doFailure(-1, "解析异常")
}
}
}
}
}
fun String.isHttpApi() = this.startsWith("http://") || this.startsWith("https://")
//
//
// val executor = Executors.newSingleThreadScheduledExecutor()
// var getTokenIsRunning = false
// private fun exeTokenThread() {
// if (getTokenIsRunning.not()) {
// executor.scheduleWithFixedDelay(
// task, 0, 1, TimeUnit.MINUTES
// )
// getTokenIsRunning = true
// }
// }
//
// val task = Runnable {
// if (BaseApp.token.isNullOrBlank()) {
// getAppToken()
// } else {
// executor.shutdown()
// }
// }
//
// fun getToken() = SpTool.getString(SpTool.TOKEN)
////BaseApp.instance?.token ?:""
////BaseApp.getSharedPref()?.getString("token", "") ?: ""
//
// fun getAppToken() {
// val url = "${UrlConfig.GET_TOKEN}?qrcodeId=${SpTool.getString(SpTool.DEVICE_ID)}"
//// Log.d(TAG, "getToken: url = $url")
// get(url = url, doSuccess = {
// SpTool.put(SpTool.TOKEN, it)
// BaseApp.token = it.toString()
// }) { code, msg ->
// //toast(msg)
// }
// }
//
// class CallbackImpl(
// private val doSuccess: (data: Any) -> Unit,
// private val doFailure: (code: Int?, msg: String?) -> Unit
// ) :
// Callback {
// override fun onFailure(call: Call, e: IOException) {
// runMainThread {
// e.printStackTrace()
// doFailure(-1, "服务异常,${e.message}")
// }
// }
//
// override fun onResponse(call: Call, response: Response) {
// val respData = response.body?.string()
// runMainThread {
// Log.d("HttpUtil", respData ?: "")
// runCatching {
// val typeToken = object : TypeToken<BaseReq<Any>>() {}
// val baseReq = respData?.toType<BaseReq<Any>>(typeToken = typeToken)
// if (baseReq == null) {
// doFailure(-1, "查询数据失败")
// return@runCatching
// }
// if (baseReq.code != 200) {
// doFailure(baseReq.code, baseReq.message)
// //baseReq.code == 500 ||
// if (baseReq.code == 401 && loopGetToken) {
// exeTokenThread()
// }
// return@runCatching
// }
// doSuccess(baseReq.result ?: "")
// }.onFailure {
// it.printStackTrace()
// doFailure(-1, "解析异常")
// }
// }
// }
//
// }
//
//}
//
//fun String.isHttpApi() = this.startsWith("http://") || this.startsWith("https://")
@@ -1,36 +1,37 @@
package com.shuwei.dish.match.http
import com.shuwei.dish.match.base.BaseApp
//import com.shuwei.dish.match.base.BaseApp
object UrlConfig {
private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
/**
* device获取token
*/
const val DEVICE_TOKEN = "${DEVICE_BASE_URL}/sys/getEquipmentToken"
/**
*获取配置信息
*/
const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode"
// private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
// /**
// * device获取token
// */
// const val DEVICE_TOKEN = "${DEVICE_BASE_URL}/sys/getEquipmentToken"
// /**
// *获取配置信息
// */
// const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode"
// const val BASE_URL = "http://vip.shuziweidao.com/shuwei-zhct"
// const val DISH_DETAIL = "$BASE_URL/scales/goodsUseList?foodId={foodId}&foodWeight={foodWeight}"
// const val DISH_LIST = "$BASE_URL/scales/getRestInfoFoods?eaId=99&type=0&foodName"
// const val BASE_URL = "http://192.168.1.207:9102"
const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
// const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
const val BASE_URL = "https://yyjk.shuziweidao.com/gateway/"
// const val DISH_LIST = "$BASE_URL/scales/getRestInfoFoods?eaId=99&type=0&foodName"
var DISH_DETAIL = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryById"
// "$BASE_URL/scales/generateToken?deviceId=1111111111111111111111111111"
var GET_TOKEN = "${BaseApp.configUrl}/restaurant/equipment/stEquipment/getEquipmentToken"
var SUBMIT_DISH = "${BaseApp.configUrl}/food/stFoodInfoMatching/saveoredit"
var QUERY_GOODS_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryGoodsInfoList"
var QUERY_FOOD_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/list"
var SAMPLING_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryHistorical/goodsInfoList"
// var DISH_DETAIL = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryById"
//
// // "$BASE_URL/scales/generateToken?deviceId=1111111111111111111111111111"
//
// var GET_TOKEN = "${BaseApp.configUrl}/restaurant/equipment/stEquipment/getEquipmentToken"
// var SUBMIT_DISH = "${BaseApp.configUrl}/food/stFoodInfoMatching/saveoredit"
//
// var QUERY_GOODS_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryGoodsInfoList"
// var QUERY_FOOD_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/list"
// var SAMPLING_LIST = "${BaseApp.configUrl}/food/stFoodInfoMatching/queryHistorical/goodsInfoList"
}
@@ -0,0 +1,129 @@
package com.shuwei.dish.match.net
import com.google.gson.JsonSyntaxException
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toType
import org.apache.http.conn.ConnectTimeoutException
import retrofit2.HttpException
import java.io.IOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
///**
// * 通用网络请求封装,统一处理异常
// * @param onRequest 实际的 Retrofit 接口请求
// * @param onSuccess 成功回调
// * @param onFailure 自定义错误回调(可选,不传则用默认提示)
// */
//suspend fun <T> apiRequest(
// onRequest: suspend () -> T,
// onSuccess: (T) -> Unit,
// onFailure: ((ApiException) -> Unit)? = null
//) {
// try {
// // 执行实际的网络请求
// val result = onRequest()
// onSuccess(result)
// } catch (e: Exception) {
// val apiException = getApiException(e)
// // 优先执行自定义错误回调,没有则用默认提示
// if (onFailure != null) {
// onFailure(apiException)
// } else {
// // 默认的错误提示(可根据 ErrorType 定制)
// val tip = apiException.errorMsg
// // 切换到UI线程弹提示(用 MainScope 确保在主线程)
// MainScope().launch {
// BaseApp.instance?.toast(tip)
// }
// }
// }
//}
/**
* 通用网络请求封装,统一处理异常
* @param onRequest 实际的 Retrofit 接口请求
* @param onSuccess 成功回调
* @param onFailure 自定义错误回调(可选,不传则用默认提示)
*/
suspend fun <T> request(
onRequest: suspend () -> ApiResponse<T>,
onSuccess: (T) -> Unit,
onFailure: (String, String) -> Unit
) {
var response: ApiResponse<T>?
try {
// 执行实际的网络请求
response = onRequest()
} catch (e: Exception) {
val apiException = getApiException(e)
onFailure("-1", apiException.errorMsg)
return
}
if (response.isSuccess()) {
onSuccess(response.data as T)
} else {
onFailure(response.code, response.msg ?: "")
}
}
data class CodeMsg(
var code: String? = null,
var msg: String? = null
)
fun getApiException(e: Exception): ApiException {
// 捕获各类网络异常并转换为自定义异常
return when (e) {
is HttpException -> httpException2ApiException(e)
is JsonSyntaxException -> ApiException(
ErrorType.NETWORK_ERROR,
errorMsg = "服务器响应异常,请稍后重试"
)
is UnknownHostException -> ApiException(
ErrorType.NETWORK_ERROR,
errorMsg = "网络未连接,请检查网络"
)
is SocketTimeoutException -> ApiException(
ErrorType.TIMEOUT_ERROR,
errorMsg = "请求超时,请稍后重试"
)
is ConnectTimeoutException -> ApiException(
ErrorType.TIMEOUT_ERROR,
errorMsg = "连接超时,请检查网络"
)
is IOException -> ApiException(
ErrorType.NETWORK_ERROR,
errorMsg = "网络异常:${e.message ?: "未知IO错误"}"
)
is ApiException -> e
else -> ApiException(
ErrorType.UNKNOWN_ERROR,
errorMsg = "未知错误:${e.message ?: "未知"}",
throwable = e
)
}
}
fun httpException2ApiException(e: HttpException): ApiException {
val body = e.response()?.errorBody()?.string()
val codeMsg: CodeMsg? = try {
body?.toObject<CodeMsg>()
} catch (ex: Exception) {
null
}
return ApiException(
ErrorType.NETWORK_ERROR,
errorMsg = if (codeMsg == null) "数据解析异常(${e.code()})"
else codeMsg.msg ?: "服务器错误(${e.code()})"
)
}
@@ -0,0 +1,113 @@
package com.shuwei.dish.match.net
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.SeasoningEntity
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import retrofit2.http.Url
interface ApiService {
/**
* 查询菜品详情
*/
@GET
suspend fun getFoodDetail(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/getConstituteByFoodId",
@Query("foodId") foodId: String
): ApiResponse<CookFoodEntity?>
/**
* 提交制作菜品
*/
@POST
suspend fun submitCookFood(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/saveConstitute",
@Body param: CookFoodEntity
): ApiResponse<Any?>
/**
* 搜索菜品
*/
@POST
suspend fun searchFoodList(
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryPageList",
@Body param: MutableMap<String, Any>
): ApiResponse<MutableList<FoodRecord>?>
/**
* 采样数据
*/
@POST
suspend fun getSamplingList(
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryHistoryGoodsInfoList",
@Body param: MutableMap<String, Any>
): ApiResponse<MutableList<FoodRecord>?>
/**
* 物品信息
*/
@POST
suspend fun queryGoodsList(
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryGoodsInfoList",
@Body param: MutableMap<String, Any>
): ApiResponse<MutableList<CookFoodGoodsEntity>?>
/**
* 调料信息
*/
@POST
suspend fun querySeasoningList(
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/matching/app/queryGoodsInfoList",
@Body param: MutableMap<String, Any>
): ApiResponse<MutableList<SeasoningEntity>?>
// /**
// *获取配置信息
// */
// @GET("/equipment/stEquipment/queryByEquipmentCode")
// suspend fun getDeviceInfo(
// @Query("equipmentCode") equipmentCode: String,
// @Query("appVersion") appVersion: String = App.appVersion
// ): ApiResponse<DeviceConfigInfo>
//
// @GET
// suspend fun getAccessToken(
// @Url url: String = UrlConfig.GET_ACCESS_TOKEN,
// @Query("qrcodeId") qrcodeId: String
// ): ApiResponse<String?>
//
// @GET
// suspend fun getShelfList(
// @Url url: String = UrlConfig.GET_SHELF_LIST,
// @Query("deviceId") deviceId: String
// ): ApiResponse<ShelfResult>
//
// @GET
// suspend fun getGoodsList(
// @Url url: String = UrlConfig.GET_GOODS_LIST,
// @Query("canteenId") canteenId: String = "0",
// @Query("goodsName") goodsName: String? = null,
// @Query("pageNo") pageNo: Int = 1,
// @Query("pageSize") pageSize: Int = 50
// ): ApiResponse<MutableList<GoodsModel>?>
//
// @POST
// suspend fun saveShelfGoodsList(
// @Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST,
// @Body body: ShelfBody
// ): ApiResponse<Any?>
}
@@ -0,0 +1,19 @@
package com.shuwei.dish.match.net
// 1. 网络异常类型枚举
enum class ErrorType {
NETWORK_ERROR, // 网络错误(无网)
TIMEOUT_ERROR, // 超时
SERVER_ERROR, // 服务器错误(5xx
CLIENT_ERROR, // 客户端错误(4xx
PARSE_ERROR, // 数据解析错误
UNKNOWN_ERROR // 未知错误
}
// 2. 自定义网络异常类
class ApiException(
val errorType: ErrorType,
val errorCode: Int = -1,
val errorMsg: String,
val throwable: Throwable? = null
) : Exception(errorMsg, throwable)
@@ -0,0 +1,40 @@
//package com.shuwei.dish.match.net
//
//import android.annotation.SuppressLint
//import okhttp3.Interceptor
//import okhttp3.Response
//import org.apache.http.conn.ConnectTimeoutException
//import java.io.IOException
//import java.net.SocketTimeoutException
//import java.net.UnknownHostException
//
//class ExceptionInterceptor : Interceptor {
// @SuppressLint("SuspiciousIndentation")
// override fun intercept(chain: Interceptor.Chain): Response {
// val request = chain.request()
// try {
// val response = chain.proceed(request)
// // 拦截 HTTP 状态码异常(4xx/5xx
// if (!response.isSuccessful) {
// val e = ApiException(
// errorType = if (response.code >= 500) ErrorType.SERVER_ERROR else ErrorType.CLIENT_ERROR,
// errorCode = response.code,
// errorMsg = "HTTP错误:${response.code}"
// )
// throw e
// }
// return response
// } catch (e: Exception) {
// // 捕获各类网络异常并转换为自定义异常
// val apiException = when (e) {
// is UnknownHostException -> ApiException(ErrorType.NETWORK_ERROR, errorMsg = "网络未连接,请检查网络")
// is SocketTimeoutException -> ApiException(ErrorType.TIMEOUT_ERROR, errorMsg = "请求超时,请稍后重试")
// is ConnectTimeoutException -> ApiException(ErrorType.TIMEOUT_ERROR, errorMsg = "连接超时,请检查网络")
// is IOException -> ApiException(ErrorType.NETWORK_ERROR, errorMsg = "网络异常:${e.message ?: "未知IO错误"}")
// is ApiException -> e // 已转换的异常直接抛出
// else -> ApiException(ErrorType.UNKNOWN_ERROR, errorMsg = "未知错误:${e.message ?: "未知"}", throwable = e)
// }
// throw apiException
// }
// }
//}
@@ -0,0 +1,80 @@
package com.shuwei.dish.match.net
import android.annotation.SuppressLint
import android.util.Log
import com.shuwei.dish.match.http.UrlConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.net.ssl.*
import java.security.SecureRandom
import java.security.cert.X509Certificate
import kotlin.apply
import kotlin.jvm.java
val apiService: ApiService = Retrofit.Builder()
.baseUrl(UrlConfig.BASE_URL)
// .baseUrl(UrlConfig.DEVICE_BASE_URL)
.client(HttpManager.instance.client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
//val apiService2: ApiService2 = Retrofit.Builder()
// .baseUrl(UrlConfig.BASE_URL)
//// .baseUrl(UrlConfig.DEVICE_BASE_URL)
// .client(HttpManager.instance.client)
// .addConverterFactory(GsonConverterFactory.create())
// .build()
// .create(ApiService2::class.java)
class HttpManager private constructor() {
val client: OkHttpClient by lazy {
OkHttpClient.Builder()
.apply {
connectTimeout(15, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
writeTimeout(15, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true }
addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Log.d("HttpManager", "okhttp logger ==>${it}")
}).also {
it.level = HttpLoggingInterceptor.Level.BODY
})
addInterceptor(RequestInterceptor())
// addInterceptor(ExceptionInterceptor())
}
.build()
}
companion object {
val instance by lazy { HttpManager() }
}
// 信任所有证书的TrustManager实现
@SuppressLint("CustomX509TrustManager")
class TrustAllCerts : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
// 创建信任所有证书的SSLSocketFactory
fun createSSLSocketFactory(): SSLSocketFactory {
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(TrustAllCerts()), SecureRandom())
}
return sslContext.socketFactory
}
}
@@ -0,0 +1,111 @@
package com.shuwei.dish.match.net
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.SeasoningEntity
import kotlinx.coroutines.launch
class NetViewModel : ViewModel() {
fun submitCookFood(
entity: CookFoodEntity,
onSuccess: (Any?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch {
request(
onRequest = {
apiService.submitCookFood(param = entity)
},
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
fun getFoodDetail(
foodId: String,
onSuccess: (CookFoodEntity?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch {
request(
onRequest = {
apiService.getFoodDetail(foodId = foodId)
},
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
fun searchFoodList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch {
request(
onRequest = {
apiService.searchFoodList(param = param)
},
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
fun getSamplingList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch {
request(
onRequest = {
apiService.getSamplingList(param = param)
},
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
fun queryGoodsList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<CookFoodGoodsEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch {
request(
onRequest = {
apiService.queryGoodsList(param = param)
},
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
fun querySeasoningList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<SeasoningEntity>?) -> Unit,
onFailure: (String, String) -> Unit
) {
viewModelScope.launch {
request(
onRequest = {
apiService.querySeasoningList(param = param)
},
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
}
@@ -0,0 +1,22 @@
package com.shuwei.dish.match.net
import com.shuwei.dish.match.base.GlobalData
import okhttp3.Interceptor
import okhttp3.Response
/**
* 请求拦截器
*/
class RequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM")
.header("X-DEVICE-CODE", GlobalData.deviceId)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
val newRequest = requestBuilder.build()
return chain.proceed(newRequest)
}
}
@@ -0,0 +1,9 @@
package com.shuwei.dish.match.net
data class ApiResponse<T>(
val code: String,
val msg: String? = "",
val data: T? = null
) {
fun isSuccess() = code == "00000"
}
@@ -0,0 +1,68 @@
package com.shuwei.dish.match.objbox
import android.graphics.Bitmap
import android.net.Uri
import java.io.File
data class FoodClassInfo(
var class_names: List<String>,
var class_to_idx: Map<String, Int>,
var idx_to_class: Map<String, String>
)
data class FoodCollectionBean(
var imageUri: Uri? = null,
var bitmap: Bitmap? = null,
var imageFile: File? = null,
var imageVector: FloatArray? = null,
var isShowCamera: Boolean = false,
var isFinish:Boolean = false,
var uploadSuccess:Boolean = false
)
data class ReceiptGoodsInfo(
var goodsId: String? = "",
var goodsName: String? = "",
var procurementUnit: String? = "",
var procurementCount: Int? = 0,
var procurementPrice: Double? = 0.0,
var procurementAmount: Double? = 0.0,
var receiptCount: Int? = 0,
var goodsWeight: Int? = 0,
var goodsState: String? = "",
var isSelected: Boolean = false,
var isLocalGoods: Boolean = false
)
//data class DropdownInfo(
// var id: String,
// var name: String
//)
//data class RecognizeResult(
// var name: String? = null,
// var image: String? = null,
// var isSelected: Boolean = false
//)
//data class GoodsSearchInfo(
// var id: String? = null,
// var name: String? = null,
// var isSelected: Boolean = false
//)
data class OperateBean(
var obj: Any? = null,
var typeList: MutableList<OperateType> = mutableListOf(),
var isSelected: Boolean = false,
var isLocalGoods: Boolean = false,
var isReceiptPage: Boolean = true
)
data class OperateType(
var value: String = "",
var width: Int = 0,
var textColor: String = "#FF999999",
var isBoldFont: Boolean = false,
var isShowIcon: Boolean = false
)
@@ -0,0 +1,37 @@
package com.shuwei.dish.match.objbox
import io.objectbox.annotation.Entity
import io.objectbox.annotation.HnswIndex
import io.objectbox.annotation.Id
import io.objectbox.annotation.VectorDistanceType
@Entity
data class Food(
@Id var id: Long = 0,
var name: String? = null,
var foodIdx: Int = 0,
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
var foodVector: FloatArray? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Food
if (id != other.id) return false
if (foodIdx != other.foodIdx) return false
if (name != other.name) return false
if (!foodVector.contentEquals(other.foodVector)) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + foodIdx
result = 31 * result + (name?.hashCode() ?: 0)
result = 31 * result + (foodVector?.contentHashCode() ?: 0)
return result
}
}
@@ -0,0 +1,308 @@
package com.shuwei.dish.match.objbox
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import androidx.core.graphics.scale
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.shuwei.dish.match.utils.AssetsTool
import com.shuwei.dish.match.utils.LogSaveUtil
import com.shuwei.dish.match.utils.ext.toJsonString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.pytorch.IValue
import org.pytorch.Module
import org.pytorch.torchvision.TensorImageUtils
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
object FoodModule {
private lateinit var module_mobile: Module
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
// 1. 定义你的模型固定输入尺寸 (根据你的tflite模型修改,比如224x224)
private const val MODEL_INPUT_WIDTH = 300
private const val MODEL_INPUT_HEIGHT = 300
const val DEFAULT_FOOD_INDEX = -1
const val DEFAULT_QUERY_COUNT = 50
const val BAG_RATE = 0.05
suspend fun init(context: Context) {
withContext(Dispatchers.IO) {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
initDefFoodData(context)
}
}
// fun uri2FloatArray(uri: Uri): FloatArray? {
// return MyApp.instance?.let { context ->
// ImageUtil.uriToBitmap(context, uri)?.let {
// bitmap2FloatArray(it)
// }
// }
// }
fun bitmap2FloatArray(originBitmap: Bitmap, isRecycle: Boolean): FloatArray? {
var rgb565Bitmap: Bitmap? = null
try {
val scaledBitmap = originBitmap.scale(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT)
if (isRecycle) {
originBitmap.recycle()
}
rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false)
scaledBitmap.recycle()
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
rgb565Bitmap,
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
return outputTensor.dataAsFloatArray
} catch (e: OutOfMemoryError) {
e.printStackTrace()
logInfo("bitmap2FloatArray异常:${e.message}")
} finally {
if (rgb565Bitmap != null && rgb565Bitmap.isRecycled.not()) {
rgb565Bitmap.recycle()
}
//System.gc()
//System.runFinalization()
}
return null
}
// fun queryFood(uri: Uri, queryCount: Int = DEFAULT_QUERY_COUNT): List<String>? {
// return uri2FloatArray(uri)?.let {
// queryFood(it, queryCount)
// }
// }
// /**
// * 返回识别物品名称列表
// */
// fun queryFood(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<String> {
// val floatArray = bitmap2FloatArray(bitmap)
// return queryFood(floatArray, queryCount)
// }
// /**
// * 返回识别物品IdNameScore对象列表
// */
// fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<IdNameScore> {
// val floatArray = bitmap2FloatArray(bitmap)
// return queryFoodNameScore(floatArray, queryCount)
// }
suspend fun queryFoodNameScore(
floatArray: FloatArray?,
queryCount: Int = DEFAULT_QUERY_COUNT
): List<IdNameScore> {
if (floatArray == null) return emptyList()
// val query: Query<Food> = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
// //查询比较分数
//// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
// var idScoreList: List<IdWithScore>
// try {
// idScoreList = query.findIdsWithScores();
// } finally {
// // 先关闭Query,释放Cursor
// query.close()
// }
logInfo("queryFood向量:${floatArray.slice(0 until 50).toJsonString()}")
val idScoreList = ObjectBox.query(floatArray, queryCount)
// 批量加载所有实体,一次DB操作替代循环单条查询
val foodMap = ObjectBox.getByIds(idScoreList.map { it.id }).associateBy { it.id }
val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach {
val name = foodMap[it.id]?.name ?: ""
nameScoreList.add(IdNameScore(id = it.id, name = name, score = it.score))
}
logInfo("queryFood数据:${nameScoreList.toJsonString()}")
return nameScoreList
}
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<IdNameScore> {
val floatArray = bitmap2FloatArray(bitmap, false) ?: return emptyList()
logInfo("getFoodScoreList向量:${floatArray.toJsonString()}")
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
if (nameScoreList.isEmpty()) {
return emptyList()
}
//暂时不过滤
val maxScoreList = nameScoreList
//.filter { it.score < 0.15 }
.groupBy { it.name }
.map { (_, value) -> value.minByOrNull { it.score }!! }
.toMutableList()
// val map = mutableMapOf<String, Int>()
// nameScoreList.forEach {
// val key = it.name
// val count = map[key] ?: 0
// map[key] = count + 1
// }
// val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
// val firstFood = nameScoreList[0].name
// orderList.remove(firstFood)
// orderList.add(0, firstFood)
//
// val sortedScoreList = maxScoreList.sortedWith(compareBy {
// orderList.indexOf(it.name)
// })
val sortedScoreList = maxScoreList.sortedBy { it.score }
logInfo("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
return sortedScoreList
}
// suspend fun queryFood(floatArray: FloatArray, queryCount: Int = DEFAULT_QUERY_COUNT): List<String> {
// val map = mutableMapOf<String, Int>()
// val nameScoreList = queryFoodNameScore(floatArray, queryCount)
// nameScoreList.filter { it.score < 0.05 }.forEach {
// val count = map[it.name] ?: 0
// map[it.name] = count + 1
// }
// val list = map.entries.sortedByDescending { it.value }.map { it.key }
// return list
// }
data class IdNameScore(
val id: Long,
var name: String,
val score: Double
)
suspend fun initDefFoodData(context: Context, action: () -> Unit = {}) {
try {
val count = ObjectBox.countIndexField(DEFAULT_FOOD_INDEX)
if (count > 0) {
return
}
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
val embeddingsList: List<List<Float>> =
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
val labelsList: IntArray = Gson().fromJson(labelsJson, IntArray::class.java)
val classInfo: FoodClassInfo =
Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
val foodMap = classInfo.idx_to_class
val list = mutableListOf<Food>()
val size = embeddingsList.size
val size2 = labelsList.size
val size3 = foodMap.size
logInfo("initDefFoodData: $size,$size2,$size3")
embeddingsList.forEachIndexed { index, floatList ->
val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray()
// ObjectBox.boxStore.runInTx {
// box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
// }
val food = Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)
list.add(food)
}
// val tempList = list.chunked(10)
ObjectBox.putAll(list)
action()
} catch (e: Exception) {
e.printStackTrace()
logInfo("initDefFoodData: ---${e.message}--")
}
}
/**
* ,此方法的主要目的是:从assets 拷贝到 app的cache目录
* @param context
* @param fileName
* @return 例如是这样:/data/user/0/com.frizzle.pluginhookandroid9/cache/plugin-debug.apk
*
* 不可能反正SD
*/
// fun copyAssetToCache(context: Context, fileName: String): String? {
// // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
// val cacheDir = context.getCacheDir()
// if (!cacheDir.exists()) {
// cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
// }
// val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置
// if (outPath.exists()) {
// outPath.delete() // TODO 如果该文件已经存在,就删掉
// }
// var `is`: InputStream? = null // 读取
// var fos: FileOutputStream? = null // 写入
// try {
// // 创建文件,如果创建成功,就返回true
// val res = outPath.createNewFile()
// if (res) {
// `is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节
// fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
// val buf = ByteArray(`is`.available()) // 缓存区
// var byteCount: Int
//
// // 开始循环读取
// while ((`is`.read(buf).also { byteCount = it }) != -1) {
// fos.write(buf, 0, byteCount)
// }
// return outPath.getAbsolutePath()
// }
// } catch (e: IOException) {
// e.printStackTrace()
// } finally {
// try {
// // TODO 一定要记得关闭资源,为了不去性能的磨损
// fos?.flush()
// `is`?.close()
// fos?.close()
// } catch (e: IOException) {
// e.printStackTrace()
// }
// }
// return null
// }
fun copyAssetToCache(context: Context, fileName: String): String? {
val cacheFile = File(context.cacheDir, fileName)
val buffer = ByteArray(8 * 1024)
var inputStream: InputStream? = null
var outputStream: FileOutputStream? = null
try {
inputStream = context.assets.open(fileName)
outputStream = FileOutputStream(cacheFile)
var byteCount: Int
while (inputStream.read(buffer).also { byteCount = it } != -1) {
outputStream.write(buffer, 0, byteCount)
}
outputStream.channel.force(true) // 强制物理落盘,比flush更彻底
return cacheFile.absolutePath
} catch (e: IOException) {
e.printStackTrace()
logInfo("文件拷贝失败 fileName=$fileName, error=${e.message}")
// 拷贝失败时删除残缺文件,避免下次读取到损坏文件
if (cacheFile.exists()) {
cacheFile.delete()
}
} finally {
outputStream?.close()
inputStream?.close()
}
return null
}
private fun logInfo(msg:String) {
Log.d("FoodModule", msg)
LogSaveUtil.saveLogFile(msg)
}
}
@@ -0,0 +1,302 @@
/*
* Copyright 2024 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.shuwei.dish.match.objbox
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.utils.LogSaveUtil
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.BoxStoreBuilder
import io.objectbox.exception.DbException
import io.objectbox.exception.FileCorruptException
import io.objectbox.kotlin.boxFor
import io.objectbox.query.Query
import io.objectbox.sync.Sync
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.GZIPOutputStream
import com.shuwei.dish.match.BuildConfig
/**
* Singleton to keep BoxStore reference and provide current list of Notes Objects.
* Inserts demo data if no Objects are stored.
*/
object ObjectBox {
// 确保所有设备/版本使用相同的objectbox-models/default.json
// 避免直接修改自动生成的MyObjectBox类
// 跨版本升级时使用boxStore.runInTx执行数据迁移
private const val TAG = "ObjectBox"
// 添加 @Volatile 保证多线程下的可见性
@Volatile
var boxStore: BoxStore? = null
/**
* If building the [boxStore] failed, contains the thrown error message.
*/
var dbExceptionMessage: String? = null
private set
fun init(context: Context) {
if (boxStore != null && boxStore!!.isClosed.not()) return
// 优化:调大事务回收超时时间
System.setProperty("objectbox.finalizerTimeout", "30000");
// On Android make sure to pass a Context when building the Store.
boxStore = try {
MyObjectBox.builder()
.androidContext(context.applicationContext)
//.disableMultiProcess() // 开启多进程支持(按需关闭,单进程可删)
//.disableFileCompression() // 禁用文件压缩,避免存储异常
.build()
} catch (e: DbException) {
if (e.javaClass == DbException::class.java || e is FileCorruptException) {
// Failed to build BoxStore due to database file issue, store message;
// checked in NoteListActivity to notify user.
dbExceptionMessage = e.toString()
LogSaveUtil.saveLogFile("ObjectBox.init异常1:$dbExceptionMessage")
return
} else {
LogSaveUtil.saveLogFile("ObjectBox.init异常2:${e.message}")
// Failed to build BoxStore due to developer error.
throw e
}
}
if (BuildConfig.DEBUG) {
var syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
Log.d(
TAG,
"Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)"
)
// Enable ObjectBox Admin on debug builds.
// https://docs.objectbox.io/data-browser
io.objectbox.android.Admin(boxStore).start(context.applicationContext)
}
}
/**
* If the database file is not in use, compresses (GZIP) and copies it to the given [target].
* 增加 IOException 捕获,避免文件异常导致崩溃。
*/
fun copyAndGzipDatabaseFileTo(target: File, context: Context): Boolean {
if (BoxStore.isDatabaseOpen(context, null)) {
// Do not copy if database file is still in use.
// If it would be open, the copy will likely get corrupted
// as BoxStore may currently write data to the file.
Log.e(TAG, "Database file is still in use, can not copy.")
return false
}
// If a name was given when building BoxStore use that instead of the default below.
val dbName = BoxStoreBuilder.DEFAULT_NAME
return try {
File(context.filesDir, "objectbox/$dbName/data.mdb").inputStream().use { input ->
target.parentFile?.mkdirs()
GZIPOutputStream(target.outputStream()).use { output ->
input.copyTo(output, DEFAULT_BUFFER_SIZE)
}
}
true
} catch (e: IOException) {
Log.e(TAG, "备份数据库文件失败", e)
false
}
}
// 获取指定实体的Box,自动关联BoxStore
inline fun <reified T> getBox(): Box<T>? = boxStore?.boxFor()
// 协程安全执行数据库操作(推荐所有操作使用此方法)
suspend fun <T> safeDbOp(operation: suspend () -> T): T? {
return withContext(Dispatchers.IO) {
try {
dbMutex.withLock { operation() }
} catch (e: FileCorruptException) {
// 操作中触发损坏,尝试重建数据库
e.printStackTrace()
LogSaveUtil.saveLogFile("safeDbOp出现FileCorruptException异常:${e.message},${e.errorCode}")
// 使用安全调用替代 !! 断言,避免 NPE
val app = BaseApp.instance ?: return@withContext null
init(app)
null
} catch (e: Exception) {
e.printStackTrace()
LogSaveUtil.saveLogFile("safeDbOp出现其它异常:${e.message}")
null
} finally {
// 线程资源清理
boxStore?.closeThreadResources()
}
}
}
suspend fun query(floatArray: FloatArray, queryCount: Int) = safeDbOp {
val query: Query<Food>? = getBox<Food>()?.query(
Food_.foodVector
.nearestNeighbors(floatArray, queryCount)
)?.build()
try {
query?.findIdsWithScores()
} finally {
// 先关闭Query,释放Cursor
query?.close()
}
} ?: emptyList()
suspend fun get(id: Long) = safeDbOp {
getBox<Food>()?.get(id)
}
// 批量按ID加载,一次DB事务替代循环单条查询
suspend fun getByIds(ids: List<Long>): List<Food> = safeDbOp {
if (ids.isEmpty()) return@safeDbOp emptyList<Food>()
getBox<Food>()?.get(ids.toLongArray()) ?: emptyList<Food>()
} ?: emptyList()
suspend fun put(entity: Food) = safeDbOp {
getBox<Food>()?.put(entity)
}
suspend fun putAll(entities: List<Food>) = safeDbOp {
val box = getBox<Food>()
box?.put(entities)
}
// 使用 QueryBuilder 在数据库层计数,避免全量加载到内存
suspend fun countNameField(name: String) = safeDbOp {
getBox<Food>()?.query(Food_.name.equal(name))?.build()?.count()
} ?: 0L
suspend fun countIndexField(idx: Int) = safeDbOp {
getBox<Food>()?.query(Food_.foodIdx.equal(idx))?.build()?.count()
} ?: 0L
// 使用 QueryBuilder 在数据库层过滤,避免全量加载到内存
suspend fun filter(name: String?) = safeDbOp {
if (!name.isNullOrBlank()) {
getBox<Food>()?.query(Food_.name.contains(name))?.build()?.find()
?.distinctBy { it.name }
} else {
getBox<Food>()?.all?.distinctBy { it.name }
} ?: emptyList()
}
// 使用 QueryBuilder 在数据库层删除,避免全量加载到内存
suspend fun remove(name: String) = safeDbOp {
getBox<Food>()?.query(Food_.name.equal(name))?.build()?.remove()
}
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 {
context.filesDir.canRead() && context.filesDir.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
}
}
@@ -9,9 +9,7 @@ import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.util.Log
import android.util.SparseArray
import android.util.SparseIntArray
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.graphics.toColorInt
import androidx.core.view.forEach
import androidx.lifecycle.ViewModelProvider
@@ -34,10 +32,9 @@ import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.TextCellAdapter
import com.shuwei.dish.match.databinding.ActivityDeviceConfigBinding
import com.shuwei.dish.match.dialog.BottomDialog2
import com.shuwei.dish.match.dialog.SeasoningSearchDialog
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
@@ -46,7 +43,7 @@ class DeviceConfigActivity : BaseActivity() {
companion object {
const val TAG = "DeviceSettingActivity"
const val TAG = "DeviceConfigActivity"
const val COOK_MODE = "cookMode"
@@ -123,7 +120,9 @@ class DeviceConfigActivity : BaseActivity() {
binding.rbModeSampling.isChecked = false
loadCookSpan(isClicked = true)
loadSamplingSpan()
startActivity<SelectDishActivity> { }
startActivity<SelectDishActivity> {
putExtra(SelectDishActivity.IS_CONFIG_PAGE, true)
}
finish()
}
binding.rbModeSampling.setOnClickListener {
@@ -132,7 +131,8 @@ class DeviceConfigActivity : BaseActivity() {
binding.rbModeSampling.isChecked = true
loadCookSpan()
loadSamplingSpan(isClicked = true)
startActivity<SamplingListActivity> { }
startActivity<SamplingListActivity> {
putExtra(SamplingListActivity.IS_CONFIG_PAGE, true) }
finish()
}
addWeighListener()
@@ -250,7 +250,7 @@ class DeviceConfigActivity : BaseActivity() {
private fun clickGridItem(sort: Int, entity: SeasoningEntity) {
val currentAddress = AddressUtil.getWeighAddressArray().get(sort)
// val seasoningEntity = list.first{it.sort == sort}
BottomDialog2(
SeasoningSearchDialog(
weighIndex = sort,
weighAddress = currentAddress,
clickName = entity.goodsName
@@ -5,7 +5,9 @@ import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.DishPartAdapter
@@ -14,13 +16,15 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityDishSamplingBinding
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.BottomDialog
import com.shuwei.dish.match.dialog.FoodSearchDialog
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.clickWithDebounce
@@ -165,6 +169,13 @@ class DishSamplingActivity : BaseActivity() {
layoutManager =
LinearLayoutManager(this@DishSamplingActivity, LinearLayoutManager.VERTICAL, false)
adapter = dishAdapter
val itemTouchHelper = ItemTouchHelper(SwipeCallback(dishAdapter) { position ->
list.removeAt(position)
dishAdapter.notifyItemRemoved(position)
dishAdapter.notifyItemRangeChanged(position, list.size - position)
toast("已删除")
})
itemTouchHelper.attachToRecyclerView(this)
}
binding.etInputDishType.addOnActionSearchListener {
searchDishType()
@@ -227,8 +238,8 @@ class DishSamplingActivity : BaseActivity() {
}
val food = FoodRecord()
binding.etInputDish.let {
if (it.tag!=null && it.tag.toString() == it.text.toString()) {
food.foodId = foodId
if (it.tag != null && it.tag.toString() == it.text.toString()) {
food.foodId = foodId
}
}
food.foodName = showFoodName
@@ -248,17 +259,16 @@ class DishSamplingActivity : BaseActivity() {
}
private fun jumpSearch(v: View) {
launch(Intent(this, FoodSearchActivity::class.java).apply {
val intent = Intent(this, FoodSearchActivity::class.java).apply {
putExtra(FoodSearchActivity.FOOD_NAME, binding.etInputDish.text.toString().trim())
}) { intent ->
intent?.let {
foodId = it.getStringExtra(FoodSearchActivity.FOOD_ID)
foodName = it.getStringExtra(FoodSearchActivity.FOOD_NAME)
binding.etInputDish.run {
setText(foodName)
tag = foodName
setSelection(text.length)
}
}
startActivity(intent) {
foodId = it?.getStringExtra(FoodSearchActivity.FOOD_ID)
foodName = it?.getStringExtra(FoodSearchActivity.FOOD_NAME)
binding.etInputDish.run {
setText(foodName)
tag = foodName
setSelection(text.length)
}
}
KeyboardUtil.hideKeyboard(v.context, v)
@@ -266,7 +276,7 @@ class DishSamplingActivity : BaseActivity() {
private fun searchDishType() {
val inputText = binding.etInputDishType.text.toString().trim()
BottomDialog(goodsType = 0, defGoodsName = inputText).show(this@DishSamplingActivity) { item ->
FoodSearchDialog(defGoodsName = inputText).show(this@DishSamplingActivity) { item ->
val filterResult = list.firstOrNull { it.goodsName == item.goodsName }
if (filterResult != null) {
toast("不允许重复添加同一食材")
@@ -283,6 +293,7 @@ class DishSamplingActivity : BaseActivity() {
relateionType = item.relateionType ?: 0
goodsOrRelationCode = item.goodsOrRelationCode
isNewDishType = true
// TODO: 待定
//allEdible = item.allEdible
}
@@ -11,17 +11,11 @@ import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityFoodSearchBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import java.io.Serializable
@@ -40,7 +34,7 @@ class FoodSearchActivity : BaseActivity() {
private var list: MutableList<FoodRecord> = mutableListOf()
private var pageType = 0
private var dinnerType = "0"
private var dinnerType = "1"
private val recordAdapter by lazy {
FoodRecordAdapter(list).apply {
isStateViewEnable = true
@@ -68,7 +62,7 @@ class FoodSearchActivity : BaseActivity() {
binding = ActivityFoodSearchBinding.inflate(layoutInflater)
setContentView(binding.root)
pageType = intent.getIntExtra(PAGE_TYPE, 0)
dinnerType = intent.getStringExtra(DINNER_TYPE)?:"0"
dinnerType = intent.getStringExtra(DINNER_TYPE) ?: "0"
setHeaderBackground()
foodName = intent.getStringExtra(FOOD_NAME)
binding.etInputDish.setText(foodName)
@@ -134,7 +128,7 @@ class FoodSearchActivity : BaseActivity() {
}
private var pageNo = 1
private var pageSize = 100
private var pageSize = 10
fun getDinnerTypeText(): String {
return when (dinnerType) {
"1" -> "早餐"
@@ -143,53 +137,95 @@ class FoodSearchActivity : BaseActivity() {
else -> "早餐"
}
}
@SuppressLint("NotifyDataSetChanged")
private fun queryListInfo(input: String) {
showLoading()
val map = mutableMapOf(
// val map = mutableMapOf(
// "foodName" to input.trim(),
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize",
// "canteenId" to BaseApp.canteenId
// "dinnerType" to getDinnerTypeText()
// )
// val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
// append("?")
// map.forEach { (key, value) -> append("$key=$value&") }
// }
// sb.deleteCharAt(sb.length - 1)
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = {
// delayDismissLoading()
// finishRefresh()
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// val json = it.toJsonString()
// val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
// if (recordBean == null || recordBean.records.isNullOrEmpty()) {
//// toast("暂未搜索到相关菜品信息")
// if (pageNo == 1) {
// loadEmptyView()
// }
// return@get
// }
// val records = recordBean.records
// if (pageNo == 1) {
// list.clear()
// }
// list.addAll(records!!)
// recordAdapter.notifyDataSetChanged()
// val isLoadMoreEnable = records.size >= pageSize
// binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
// if (isLoadMoreEnable) {
// pageNo++
// }
// },
// doFailure = { code, msg ->
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// toast(msg)
// delayDismissLoading()
// finishRefresh()
// if (pageNo == 1) {
// loadEmptyView()
// }
// }
// )
val map = mutableMapOf<String, Any>(
"foodName" to input.trim(),
"pageNo" to "$pageNo",
"pageSize" to "$pageSize",
"canteenId" to BaseApp.canteenId
"pageNum" to pageNo,
"pageSize" to pageSize,
"placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText()
)
if (dinnerType.toInt() > 0) {
map["dinnerType"] = getDinnerTypeText()
}
val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
append("?")
map.forEach { (key, value) -> append("$key=$value&") }
}
sb.deleteCharAt(sb.length - 1)
HttpUtil.get(
url = sb.toString(),
doSuccess = {
netViewModel.searchFoodList(
param = map,
onSuccess = {
delayDismissLoading()
finishRefresh()
binding.refreshLayout.run {
setEnableRefresh(true)
}
val json = it.toJsonString()
val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
binding.refreshLayout.setEnableRefresh(true)
if (it.isNullOrEmpty()) {
// toast("暂未搜索到相关菜品信息")
if (pageNo == 1) {
loadEmptyView()
}
return@get
return@searchFoodList
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(it)
recordAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
val isLoadMoreEnable = it.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
if (isLoadMoreEnable) {
pageNo++
}
},
doFailure = { code, msg ->
}, onFailure = { code, msg ->
binding.refreshLayout.run {
setEnableRefresh(true)
}
@@ -199,9 +235,9 @@ class FoodSearchActivity : BaseActivity() {
if (pageNo == 1) {
loadEmptyView()
}
}
)
})
}
private fun finishRefresh() {
if (pageNo == 1) {
binding.refreshLayout.finishRefresh(1200)
@@ -211,12 +247,17 @@ class FoodSearchActivity : BaseActivity() {
}
private var emptyViewBinding: LayoutEmptyViewBinding? = null
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
recordAdapter.notifyDataSetChanged()
if (emptyViewBinding == null) {
emptyViewBinding = LayoutEmptyViewBinding.inflate(LayoutInflater.from(this), binding.rvDishSearchList, false)
emptyViewBinding = LayoutEmptyViewBinding.inflate(
LayoutInflater.from(this),
binding.rvDishSearchList,
false
)
}
emptyViewBinding!!.tvContent.text = "暂无数据"
emptyViewBinding!!.tvSubContent.text = "可以尝试换个名称重新搜索"
@@ -11,9 +11,7 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityHomeBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.entity.HomeModeBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.viewmodel.AppViewModel
@@ -3,32 +3,26 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.databinding.ActivityInitBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.utils.AppUtil
import com.shuwei.dish.match.utils.QRCodeUtil
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.invisible
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlin.toString
class InitActivity : BaseActivity() {
companion object {
@@ -45,18 +39,19 @@ class InitActivity : BaseActivity() {
// useBackground(false)
binding = ActivityInitBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch {
FoodModule.init(this@InitActivity)
}
// setHeaderBackground(isHomePage = true)
setHeaderBgVisible(false)
BaseApp.appVersion = AppUtil.getAppVersionCode(this).toString()
// 获取 ANDROID_ID
var androidId =
Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
androidId = "39a7abdd06b3c7ab"
BaseApp.deviceId = androidId
SpTool.put(SpTool.DEVICE_ID, androidId)
BaseApp.configUrl = UrlConfig.BASE_URL
// var androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
// androidId = "39a7abdd06b3c7ab"
// BaseApp.deviceId = androidId
// SpTool.put(SpTool.DEVICE_ID, androidId)
// BaseApp.configUrl = UrlConfig.BASE_URL
BaseApp.canteenId = "0"
// // TODO: 以上保存deviceId用于临时使用,后续改为下面注释方式
@@ -79,16 +74,16 @@ class InitActivity : BaseActivity() {
// return
// }
binding.ivQrCode.invisible()
binding.btnInit.invisible()
// binding.ivQrCode.invisible()
// binding.btnInit.invisible()
initViewModel()
WeightUtil.init()
WeightUtil.getWeight()
WeightUtil.startContinuousRead()
HttpUtil.getAppToken()
// HttpUtil.getAppToken()
HttpUtil.loopGetToken = true
// HttpUtil.loopGetToken = true
countDown()
}
@@ -157,67 +152,71 @@ class InitActivity : BaseActivity() {
}
}
private fun initConfig() {
binding.ivQrCode.setImageBitmap(
QRCodeUtil.generateQRCode(
content = BaseApp.deviceId?:"",
size = 200.dp
)
)
binding.btnInit.setOnClickListener {
HttpUtil.loopGetToken = false
getDeviceConfig()
}
}
// private fun initConfig() {
// binding.ivQrCode.setImageBitmap(
// QRCodeUtil.generateQRCode(
// content = BaseApp.deviceId ?: "",
// size = 200.dp
// )
// )
// binding.btnInit.setOnClickListener {
//// HttpUtil.loopGetToken = false
//// getDeviceConfig()
// }
// }
private fun getDeviceConfig() {
val tokenUrl = "${UrlConfig.DEVICE_TOKEN}?qrcodeId=${BaseApp.deviceId}&appVersion=${BaseApp.appVersion}"
HttpUtil.get(url = tokenUrl, doSuccess = { token ->
Log.d(TAG, "initConfig: $token")
getConfig(token.toString())
}, doFailure = { code, msg ->
Log.d(TAG, "initConfig: $code,$msg")
})
}
private fun getConfig(token: String) {
val deviceConfigUrl = "${UrlConfig.DEVICE_CONFIG}?equipmentCode=${BaseApp.deviceId}&&appVersion=${BaseApp.appVersion}"
HttpUtil.get(url = deviceConfigUrl, header = mutableMapOf(
"X-Access-Token" to token
), doSuccess = {
Log.d(TAG, "getDeviceConfig: $it")
val data = it.toJsonString()
val checkResult = checkConfigData(data)
if (checkResult.not()) {
toast("初始化设备失败,请稍后重试")
return@get
}
HttpUtil.loopGetToken = true
SpTool.put(SpTool.DEVICE_CONFIG_CACHE, data)
startActivity<HomeActivity>()
// finish()
}, doFailure = { code, msg ->
Log.d(TAG, "getDeviceConfig: $code,$msg")
toast("初始化设备失败,请稍后重试,code=${code},msg=${msg}")
})
}
// private fun getDeviceConfig() {
// val tokenUrl =
// "${UrlConfig.DEVICE_TOKEN}?qrcodeId=${BaseApp.deviceId}&appVersion=${BaseApp.appVersion}"
// HttpUtil.get(url = tokenUrl, doSuccess = { token ->
// Log.d(TAG, "initConfig: $token")
// getConfig(token.toString())
// }, doFailure = { code, msg ->
// Log.d(TAG, "initConfig: $code,$msg")
// })
// }
// private fun getConfig(token: String) {
// val deviceConfigUrl =
// "${UrlConfig.DEVICE_CONFIG}?equipmentCode=${BaseApp.deviceId}&&appVersion=${BaseApp.appVersion}"
// HttpUtil.get(
// url = deviceConfigUrl, header = mutableMapOf(
// "X-Access-Token" to token
// ), doSuccess = {
// Log.d(TAG, "getDeviceConfig: $it")
// val data = it.toJsonString()
// val checkResult = checkConfigData(data)
// if (checkResult.not()) {
// toast("初始化设备失败,请稍后重试")
// return@get
// }
// HttpUtil.loopGetToken = true
// SpTool.put(SpTool.DEVICE_CONFIG_CACHE, data)
// startActivity<HomeActivity>()
//// finish()
// }, doFailure = { code, msg ->
// Log.d(TAG, "getDeviceConfig: $code,$msg")
// toast("初始化设备失败,请稍后重试,code=${code},msg=${msg}")
// })
// }
data class DeviceConfigBean(
var appPackageUrl:String? = null,
var canteenId:String? = null
var appPackageUrl: String? = null,
var canteenId: String? = null
)
private fun checkConfigData(data: String): Boolean {
if (data.isBlank()) {
return false
}
val config = data.toObject<DeviceConfigBean?>()
if (config == null) {
return false
}
BaseApp.configUrl = config.appPackageUrl?:""
BaseApp.canteenId = config.canteenId?:""
return true
}
// private fun checkConfigData(data: String): Boolean {
// if (data.isBlank()) {
// return false
// }
// val config = data.toObject<DeviceConfigBean?>()
// if (config == null) {
// return false
// }
// BaseApp.configUrl = config.appPackageUrl ?: ""
// BaseApp.canteenId = config.canteenId ?: ""
// return true
// }
override fun onDestroy() {
WeightUtil.stopContinuousRead()
@@ -1,10 +1,10 @@
package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.R
@@ -12,13 +12,12 @@ 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.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.dialog.BottomDialog
import com.shuwei.dish.match.dialog.FoodSearchDialog
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.WeightUtil
@@ -26,10 +25,10 @@ import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import java.io.Serializable
import kotlin.getValue
@SuppressLint("NotifyDataSetChanged")
class PrepareCookActivity : BaseActivity() {
@@ -69,6 +68,9 @@ class PrepareCookActivity : BaseActivity() {
food = intent.extras?.getSerializable(FOOD_ITEM) as FoodRecord?
binding.tvDishName.text = food?.foodName ?: ""
addViewClickListener()
initRecyclerView()
getDishDetail()
}
@@ -84,55 +86,18 @@ class PrepareCookActivity : BaseActivity() {
binding.ivWeightClear.setOnClickListener {
WeightUtil.tareTwo(AddressUtil.ONE)
}
binding.ivWeightAdd.setOnClickListener {
if (clickIndex == -1) {
toast("请选择菜品构成")
return@setOnClickListener
}
val clickItem = list[clickIndex]
clickItem.run {
if (isNewDishType && materialType != 1 && materialType != 2) {
toast("请选择主辅材类型")
return@setOnClickListener
}
}
val weight = binding.tvDishPartWeight.text.toString().toDouble()
if (weight <= 0.toDouble()) {
toast("食材用量需要大于0")
return@setOnClickListener
}
Log.d(TAG, "addViewClickListener->useRealWeight=$weight")
clickItem.run {
useWeight = weight.toDouble()
isSetFinished = true
dishPartAdapter.notifyItemChanged(clickIndex)
}
}
binding.tvAddFood.clickWithDebounce {
BottomDialog(goodsType = 0).show(this) { item ->
val filterResult = list.firstOrNull { it.goodsId == item.goodsId }
if (filterResult != null) {
toast("不允许重复添加同一食材")
return@show
}
list.add(CookFoodGoodsEntity().apply {
goodsId = item.goodsId
goodsName = item.goodsName
// materialType =
isNewDishType = true
})
onItemClick(list.size - 1)
}
}
binding.ivWeightAdd.setOnClickListener { addWeight() }
binding.tvAddFood.clickWithDebounce { addFood() }
binding.btnCook.clickWithDebounce {
val count = list.count { it.isSetFinished.not() }
if (count > 0) {
showRemindDialog()
return@clickWithDebounce
}
goToSubmit()
openSubmitPage()
}
binding.rgCook.setOnCheckedChangeListener { group, checkedId ->
if (clickIndex < 0 || clickIndex >= list.size) return@setOnCheckedChangeListener
val tempType =
if (checkedId == R.id.rbDishTypeFirst) 1 else if (checkedId == R.id.rbDishTypeSecond) 2 else -1
list[clickIndex].materialType = tempType
@@ -143,14 +108,58 @@ class PrepareCookActivity : BaseActivity() {
}
}
private fun goToSubmit() {
private fun addWeight() {
if (clickIndex == -1) {
toast("请选择菜品构成")
return
}
val clickItem = list[clickIndex]
clickItem.run {
if (isNewDishType && materialType != 1 && materialType != 2) {
toast("请选择主辅材类型")
return
}
}
val weight = binding.tvDishPartWeight.text.toString().toDouble()
if (weight <= 0.toDouble()) {
toast("食材用量需要大于0")
return
}
Log.d(TAG, "addViewClickListener->useRealWeight=$weight")
clickItem.run {
useWeight = weight
isOriginalData = false
isSetFinished = true
dishPartAdapter.notifyItemChanged(clickIndex)
}
}
private fun addFood() {
FoodSearchDialog().show(this) { item ->
val filterResult = list.firstOrNull { it.goodsId == item.goodsId }
if (filterResult != null) {
toast("不允许重复添加同一食材")
return@show
}
list.add(CookFoodGoodsEntity().apply {
goodsId = item.goodsId
goodsName = item.goodsName
// materialType =
isNewDishType = true
isOriginalData = false
})
onItemClick(list.size - 1)
binding.rvDishPartList.smoothScrollToPosition(list.size - 1)
}
}
private fun openSubmitPage() {
getGoodsList()
if (goodsList.isNullOrEmpty()) {
toast("菜品构成信息未设置")
toast("菜品构成信息")
return
}
Log.d(TAG, "goToSubmit: goodsList:${goodsList?.toJsonString()}")
// startActivity<SubmitDishActivity> {
startActivity<SubmitFoodActivity> {
putExtra(SubmitFoodActivity.GOODS_LIST, goodsList as Serializable)
putExtra(SubmitFoodActivity.FOOD_ITEM, food as Serializable)
@@ -161,19 +170,17 @@ class PrepareCookActivity : BaseActivity() {
if (goodsList == null) {
goodsList = mutableListOf()
}
list.forEach {
if (it.isSetFinished) {
goodsList?.add(CookFoodGoodsEntity().apply {
foodId = food!!.foodId
goodsId = it.goodsId
relateionType = it.relateionType
allEdible = it.allEdible
goodsName = it.goodsName
materialType = it.materialType
useWeight = it.useWeight ?: 0.toDouble()
goodsOrRelationCode = it.goodsOrRelationCode
})
}
list.filter { it.isSetFinished }.forEach {
goodsList?.add(CookFoodGoodsEntity().apply {
foodId = food!!.foodId
goodsId = it.goodsId
relateionType = it.relateionType
allEdible = it.allEdible
goodsName = it.goodsName
materialType = it.materialType
useWeight = it.useWeight ?: 0.toDouble()
goodsOrRelationCode = it.goodsOrRelationCode
})
}
}
@@ -185,7 +192,7 @@ class PrepareCookActivity : BaseActivity() {
rightText = "确认无误"
onLeftClick = { dismiss() }
onRightClick = {
goToSubmit()
openSubmitPage()
dismiss()
}
onDismiss = { hideStatusBar() }
@@ -194,38 +201,52 @@ class PrepareCookActivity : BaseActivity() {
private fun getDishDetail() {
//649
val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}"
HttpUtil.get(
url = url,
doSuccess = {
val json = it.toJsonString()
Log.d(TAG, "getDishDetail: json:$json")
val detail: CookFoodEntity? = json.toObject<CookFoodEntity>()
// //649
// val url = "${UrlConfig.DISH_DETAIL}?foodId=${food?.foodId}"
// HttpUtil.get(
// url = url,
// doSuccess = {
// val json = it.toJsonString()
// Log.d(TAG, "getDishDetail: json:$json")
// val detail: CookFoodEntity? = json.toObject<CookFoodEntity>()
// if (detail == null) {
// toast("查询菜品信息为空")
// return@get
// }
// loadDishDetail(detail)
// }) { code, msg ->
// toast(msg)
// }
netViewModel.getFoodDetail(
food?.foodId ?: "",
onSuccess = { detail ->
if (detail == null) {
toast("查询菜品信息为空")
return@get
return@getFoodDetail
}
loadDishDetail(detail)
}) { code, msg ->
toast(msg)
}
}, onFailure = { code, msg ->
toast(msg)
})
}
private fun loadDishDetail(detail: CookFoodEntity) {
val voList = detail.stFoodInfoConstituteList
// val voList = detail.stFoodInfoConstituteList
val voList = detail.foodConstituteList
if (voList.isNullOrEmpty()) {
return
}
//筛选出主材和辅材
val tempDate = voList.filter { it.materialType == 1 || it.materialType == 2 }
val tempData = voList.filter { it.materialType == 1 || it.materialType == 2 }
.apply {
forEach { it.useWeight = 0.toDouble() }
}
list.clear()
list.addAll(tempDate)
firstReqSize = list.size
initRecyclerView()
list.addAll(tempData)
dishPartAdapter.notifyDataSetChanged()
}
private var firstReqSize = 0
@@ -245,8 +266,26 @@ class PrepareCookActivity : BaseActivity() {
text = it.goodsName
setTypeface(typeface, Typeface.BOLD)
}
binding.tvDishType.text =
if (it.materialType == 1) "主材" else if (it.materialType == 2) "辅材" else ""
var materialType: String
when (it.materialType) {
1 -> {
materialType = "主材"
binding.rgCook.check(R.id.rbDishTypeFirst)
}
2 -> {
materialType = "辅材"
binding.rgCook.check(R.id.rbDishTypeSecond)
}
else -> {
materialType = ""
binding.rgCook.clearCheck()
}
}
binding.tvDishType.text = materialType
list.forEach { bean -> bean.isItemClicked = false }
it.isItemClicked = true
dishPartAdapter.notifyDataSetChanged()
@@ -279,6 +318,9 @@ class PrepareCookActivity : BaseActivity() {
dishPartAdapter.notifyItemRemoved(position)
dishPartAdapter.notifyItemRangeChanged(position, list.size - position)
toast("已删除")
if (list.isEmpty()) {
binding.tvDishShowName.text = ""
}
})
itemTouchHelper.attachToRecyclerView(this)
}
@@ -288,17 +330,23 @@ class PrepareCookActivity : BaseActivity() {
@SuppressLint("GestureBackNavigation")
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (list.size > firstReqSize) {
//说明有新增的食材
saveDataRemindDialog()
return
}
val filterResult = list.firstOrNull { (it.useWeight ?: 0.toDouble()) > 0.toDouble() }
if (filterResult != null) {
//说明有称重的数据
val count = list.count { !it.isOriginalData }
if (count > 0) {
//说明有新增或者设置重量
saveDataRemindDialog()
return
}
// if (list.size > firstReqSize) {
// //说明有新增的食材
// saveDataRemindDialog()
// return
// }
// val filterResult = list.firstOrNull { (it.useWeight ?: 0.toDouble()) > 0.toDouble() }
// if (filterResult != null) {
// //说明有称重的数据
// saveDataRemindDialog()
// return
// }
super.onBackPressed()
}
@@ -3,9 +3,7 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@@ -19,15 +17,10 @@ import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.DialogTool
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.utils.DateTimeUtil
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
@@ -41,6 +34,7 @@ class SamplingListActivity : BaseActivity() {
companion object {
private const val TAG = "SamplingListActivity"
const val IS_COOKING = "isCooking"
const val IS_CONFIG_PAGE = "isConfigPage"
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -65,9 +59,7 @@ class SamplingListActivity : BaseActivity() {
it.visible()
it.setImageResource(R.drawable.ic_setting)
it.setOnClickListener {
startActivity<DeviceConfigActivity> {
putExtra(DeviceConfigActivity.COOK_MODE, 2)
}
startActivity<SettingActivity>()
}
}, backAction = {
it.gone()
@@ -80,10 +72,8 @@ class SamplingListActivity : BaseActivity() {
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
val factory = AppFactory(AppRepository(db.appDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
@@ -94,8 +84,12 @@ class SamplingListActivity : BaseActivity() {
appViewModel.deleteCookFoodAndGoods(cookMode = 1, foodId = foodId, onFinish = action)
}
override fun onNewIntent(intent: Intent?) {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val isConfigPage = intent?.getBooleanExtra(IS_CONFIG_PAGE, false) ?: false
if (isConfigPage) {
return
}
val isCooking = intent?.getBooleanExtra(IS_COOKING, false) ?: false
//toast("isCooking=$isCooking")
refreshPage(isCooking)
@@ -121,14 +115,13 @@ class SamplingListActivity : BaseActivity() {
}
}
private fun onItemClick(position:Int) {
private fun onItemClick(position: Int) {
val item = list[position]
if (item.isCooking) {
//烹饪中,跳到称熟重页面
startActivity<SubmitFoodActivity> {
putExtra(
SubmitFoodActivity.FOOD_ITEM,
item as Serializable
SubmitFoodActivity.FOOD_ITEM, item as Serializable
)
}
return
@@ -166,7 +159,7 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("ClickableViewAccessibility")
private fun addViewListener() {
binding.btnAddSampling.setOnClickListener {
judgeDeviceConfig{
judgeDeviceConfig {
startActivity<DishSamplingActivity>()
}
}
@@ -204,33 +197,53 @@ class SamplingListActivity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged")
fun getSamplingList() {
showLoading()
val map = mapOf(
// "foodName" to input,
"pageNo" to "$pageNo",
"pageSize" to "$pageSize",
"canteenId" to BaseApp.canteenId
)
val sb = StringBuilder(UrlConfig.SAMPLING_LIST).apply {
append("?")
map.forEach { (key, value) -> append("$key=$value&") }
}
sb.deleteCharAt(sb.length - 1)
HttpUtil.get(
url = sb.toString(),
doSuccess = {
// val map = mapOf(
//// "foodName" to input,
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize",
// "canteenId" to BaseApp.canteenId
// )
// val sb = StringBuilder(UrlConfig.SAMPLING_LIST).apply {
// append("?")
// map.forEach { (key, value) -> append("$key=$value&") }
// }
// sb.deleteCharAt(sb.length - 1)
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = {
// loadDishList(it)
// }, doFailure = { code, msg ->
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// toast(msg)
// finishRefresh()
// binding.refreshLayout.setEnableRefresh(true)
// delayDismissLoading()
// if (pageNo == 1) {
// loadEmptyView()
// }
// })
netViewModel.getSamplingList(
param = mutableMapOf(
"pageNum" to pageNo,
"pageSize" to pageSize,
"placeId" to BaseApp.canteenId
),
onSuccess = {
loadDishList(it)
}, doFailure = { code, msg ->
binding.refreshLayout.run {
setEnableRefresh(true)
}
},
onFailure = { code, msg ->
binding.refreshLayout.setEnableRefresh(true)
toast(msg)
finishRefresh()
binding.refreshLayout.setEnableRefresh(true)
delayDismissLoading()
if (pageNo == 1) {
loadEmptyView()
}
})
}
)
}
private fun finishRefresh() {
@@ -249,9 +262,7 @@ class SamplingListActivity : BaseActivity() {
dishAdapter.notifyDataSetChanged()
if (emptyViewBinding == null) {
emptyViewBinding = LayoutEmptyViewBinding.inflate(
LayoutInflater.from(this),
binding.rvSamplingList,
false
LayoutInflater.from(this), binding.rvSamplingList, false
)
}
emptyViewBinding!!.tvContent.text = "暂无数据"
@@ -302,8 +313,7 @@ class SamplingListActivity : BaseActivity() {
}
val tempList = mutableListOf<FoodRecord>()
cookFoodEntities
.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
cookFoodEntities.sortedByDescending { DateTimeUtil.convert(dateStr = it.createTime) }
.forEach { entity ->
tempList.add(FoodRecord().apply {
foodId = entity.foodId
@@ -332,26 +342,21 @@ class SamplingListActivity : BaseActivity() {
}
@SuppressLint("NotifyDataSetChanged")
private fun loadDishList(data: Any) {
binding.refreshLayout.run {
setEnableRefresh(true)
}
private fun loadDishList(records: MutableList<FoodRecord>?) {
binding.refreshLayout.setEnableRefresh(true)
delayDismissLoading()
finishRefresh()
val json = data.toJsonString()
val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
if (records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息")
if (pageNo == 1) {
loadEmptyView()
}
return
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
@@ -363,7 +368,7 @@ class SamplingListActivity : BaseActivity() {
private var configFinished = false
fun judgeDeviceConfig(block:()-> Unit) {
fun judgeDeviceConfig(block: () -> Unit) {
if (configFinished) {
block()
return
@@ -387,11 +392,8 @@ class SamplingListActivity : BaseActivity() {
rightBtnText = "去设置",
leftBtnClick = {},
rightBtnClick = {
startActivity<DeviceConfigActivity> {
putExtra(DeviceConfigActivity.COOK_MODE, 2)
}
}
)
startActivity<SettingActivity>()
})
}
private var isVisible = false
@@ -1,10 +1,9 @@
package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
import com.shuwei.dish.match.R
import com.shuwei.dish.match.base.BaseActivity
@@ -13,6 +12,9 @@ import com.shuwei.dish.match.databinding.ActivitySelectDishBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.DialogTool
import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.ui.fragment.DishListFragment
import com.shuwei.dish.match.utils.KeyboardUtil
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
@@ -22,12 +24,14 @@ import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlin.getValue
class SelectDishActivity : BaseActivity() {
companion object {
const val IS_COOKING = "isCooking"
const val DINNER_TYPE = "dinnerType"
const val IS_CONFIG_PAGE = "isConfigPage"
}
private lateinit var binding: ActivitySelectDishBinding
@@ -55,9 +59,7 @@ class SelectDishActivity : BaseActivity() {
it.visible()
it.setImageResource(R.drawable.ic_setting)
it.setOnClickListener {
startActivity<DeviceConfigActivity> {
putExtra(DeviceConfigActivity.COOK_MODE, 1)
}
startActivity<SettingActivity>()
}
}, backAction = {
it.gone()
@@ -69,10 +71,8 @@ class SelectDishActivity : BaseActivity() {
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory =
AppFactory(AppRepository(db.appDao()))
appViewModel =
ViewModelProvider(this, factory)[AppViewModel::class.java]
val factory = AppFactory(AppRepository(db.appDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
fun getCookFoodList(action: (MutableList<CookFoodEntity>?) -> Unit) {
@@ -105,9 +105,7 @@ class SelectDishActivity : BaseActivity() {
rightBtnText = "去设置",
leftBtnClick = {},
rightBtnClick = {
startActivity<DeviceConfigActivity> {
putExtra(DeviceConfigActivity.COOK_MODE, 1)
}
startActivity<SettingActivity>()
}
)
}
@@ -165,9 +163,13 @@ class SelectDishActivity : BaseActivity() {
appViewModel.deleteCookFoodAndGoods(cookMode = 0, foodId = foodId, onFinish = action)
}
override fun onNewIntent(intent: Intent?) {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
runCatching {
val isConfigPage = intent?.getBooleanExtra(IS_CONFIG_PAGE, false) ?: false
if (isConfigPage) {
return
}
val isCooking = intent?.getBooleanExtra(IS_COOKING, false) ?: false
val lastDinnerType = intent?.getStringExtra(DINNER_TYPE) ?: "0"
if (lastDinnerType == "1" || lastDinnerType == "2" || lastDinnerType == "3") {
@@ -202,4 +204,16 @@ class SelectDishActivity : BaseActivity() {
isVisible = false
}
public fun getFoodList(
param: MutableMap<String, Any>,
onSuccess: (MutableList<FoodRecord>?) -> Unit,
onFailure: (String, String) -> Unit
) {
netViewModel.searchFoodList(
param = param,
onSuccess = onSuccess,
onFailure = onFailure
)
}
}
@@ -0,0 +1,242 @@
package com.shuwei.dish.match.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import androidx.fragment.app.Fragment
import androidx.camera.view.PreviewView
import androidx.core.view.updateLayoutParams
import com.shuwei.dish.match.R
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.databinding.ActivitySettingBinding
import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding
import com.shuwei.dish.match.ui.fragment.CollectFragment
import com.shuwei.dish.match.ui.fragment.DeviceConfigFragment
import com.shuwei.dish.match.utils.CameraUtils
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
/**
* 设置Activity
* 功能:管理多个设置相关的Fragment,支持Fragment切换
*/
class SettingActivity : BaseActivity() {
companion object {
const val TAG = "SettingActivity"
}
private lateinit var binding: ActivitySettingBinding
private val cameraUtils: CameraUtils by lazy {
CameraUtils(this)
}
private lateinit var previewView: PreviewView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingBinding.inflate(layoutInflater)
setContentView(binding.root)
setHeaderBackground()
setTitleBar(titleBarAction = {
it.visible()
}, titleAction = {
it.text = "设置"
}, rightIconActon = {
it.gone()
})
// 默认显示设备配置Fragment
if (savedInstanceState == null) {
showDeviceConfigFragment()
}
initUI()
}
/**
* 初始化UI和事件监听
*/
private fun initUI() {
// 设备配置按钮点击事件
binding.btnDeviceConfig.setOnClickListener {
showDeviceConfigFragment()
updateButtonColors(isDeviceConfigSelected = true)
}
// 菜品采集按钮点击事件
binding.btnFoodCollect.setOnClickListener {
checkCameraPermissionAndShowFragment()
updateButtonColors(isDeviceConfigSelected = false)
}
// 初始化按钮颜色(默认设备配置为选中状态)
updateButtonColors(isDeviceConfigSelected = true)
}
/**
* 更新底部菜单按钮的颜色状态
* @param isDeviceConfigSelected 设备配置按钮是否被选中
*/
private fun updateButtonColors(isDeviceConfigSelected: Boolean) {
val selectedColor = "#FFFFFF".toColorInt() // 选中颜色(白色)
val unselectedColor = "#5E7585".toColorInt() // 未选中颜色(灰色)
if (isDeviceConfigSelected) {
// 设备配置按钮:选中状态
binding.btnDeviceConfig.apply {
setTextColor(selectedColor)
textSize = 28f
setTypeface(null, android.graphics.Typeface.BOLD)
background = ContextCompat.getDrawable(this@SettingActivity, R.drawable.bg_btn_underline)
}
// 采集食材按钮:未选中状态
binding.btnFoodCollect.apply {
setTextColor(unselectedColor)
textSize = 26f
setTypeface(null, android.graphics.Typeface.NORMAL)
background = null
}
} else {
// 设备配置按钮:未选中状态
binding.btnDeviceConfig.apply {
setTextColor(unselectedColor)
textSize = 26f
setTypeface(null, android.graphics.Typeface.NORMAL)
background = null
}
// 采集食材按钮:选中状态
binding.btnFoodCollect.apply {
setTextColor(selectedColor)
textSize = 28f
setTypeface(null, android.graphics.Typeface.BOLD)
background = ContextCompat.getDrawable(this@SettingActivity, R.drawable.bg_btn_underline)
}
}
}
/**
* 检查相机权限并显示 CollectFragment
*/
@SuppressLint("ObsoleteSdkInt")
private fun checkCameraPermissionAndShowFragment() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 6.0 以下,直接显示 Fragment
showFragment(fragmentList[1])
return
}
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
// 权限已授予,直接显示 Fragment
showFragment(fragmentList[1])
} else {
// 权限未授予,申请权限
requestPermission(Manifest.permission.CAMERA) { isGranted ->
if (isGranted) {
// 权限已授予,显示 CollectFragment
showFragment(fragmentList[1])
} else {
// 权限被拒绝
toast("暂无相机权限,无法使用菜品采集功能")
}
}
}
}
/**
* 初始化相机
* @param container 相机预览容器
*/
fun initCamera(container: ViewGroup) {
cameraUtils.initCamera()
val previewBinding =
LayoutCameraPreviewBinding.inflate(layoutInflater, container)
previewView = previewBinding.previewView.also {
it.updateLayoutParams {
width = 456.dp
height = 342.dp
}
}
cameraUtils.setPreviewController(previewView)
}
/**
* Activity 恢复时绑定相机
*/
override fun onResume() {
super.onResume()
cameraUtils.bind()
}
/**
* Activity 暂停时解绑相机
*/
override fun onPause() {
super.onPause()
cameraUtils.unbind()
}
/**
* 提供给 Fragment 调用的拍照方法
*/
fun takePhoto(succCallback: (Uri) -> Unit, failCallback: (msg: String) -> Unit = {}) {
cameraUtils.takePhoto(succCallback = succCallback, failCallback = failCallback)
}
/**
* 显示设备配置Fragment
*/
private fun showDeviceConfigFragment() {
// val fragment = DeviceConfigFragment.newInstance()
// supportFragmentManager.beginTransaction()
// .replace(binding.fragmentContainer.id, fragment)
// .addToBackStack(null)
// .commit()
showFragment(fragmentList[0])
}
private val fragmentList = mutableListOf<Fragment>().apply {
add(DeviceConfigFragment.newInstance())
add(CollectFragment())
}
/**
* 显示指定的 Fragment
* 使用 add/show/hide 方式管理 Fragment,避免重复创建
* 已添加的 Fragment 直接显示,未添加的 Fragment 先添加再显示
*/
private fun showFragment(fragment: Fragment) {
runCatching {
supportFragmentManager.beginTransaction().apply {
// 如果 Fragment 还未添加到容器中,则添加
if (!fragment.isAdded) {
add(binding.fragmentContainer.id, fragment)
} else {
// 已添加过,直接显示
show(fragment)
}
// 隐藏其他 Fragment
fragmentList.forEach { otherFragment ->
if (otherFragment != fragment && otherFragment.isAdded) {
hide(otherFragment)
}
}
commit()
}
}.onFailure {
it.printStackTrace()
}
}
}
@@ -6,7 +6,7 @@ import android.util.Log
import android.util.SparseArray
import android.util.SparseIntArray
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.viewModels
import androidx.core.util.forEach
import androidx.lifecycle.ViewModelProvider
import com.google.gson.reflect.TypeToken
@@ -21,8 +21,7 @@ import com.shuwei.dish.match.entity.CookFoodEntity
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.net.NetViewModel
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.clickWithDebounce
@@ -35,12 +34,13 @@ import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import kotlin.getValue
@SuppressLint("UseSparseArrays")
class SubmitFoodActivity : BaseActivity() {
companion object {
const val TAG = "SubmitDishActivity"
const val TAG = "SubmitFoodActivity"
const val FOOD_ITEM = "foodItem"
const val GOODS_LIST = "goodsList"
}
@@ -98,7 +98,10 @@ class SubmitFoodActivity : BaseActivity() {
}
private fun loadSeasoningFromLocal() {
appViewModel.getCookFoodGoodsList(foodId = food!!.foodId!!, cookMode = food!!.cookMode) { it ->
appViewModel.getCookFoodGoodsList(
foodId = food!!.foodId!!,
cookMode = food!!.cookMode
) { it ->
goodsList = mutableListOf()
val goodsIdList = mutableListOf<String>()
//设置主辅材数据
@@ -175,33 +178,34 @@ class SubmitFoodActivity : BaseActivity() {
WeightUtil.addWeightListener(
weightKey = TAG,
getWeight = { address, state, weight ->
if (address == AddressUtil.TWO) {
cookFoodEntity.foodWeight = weight.toDouble()
binding.tvTotalWeight.text = "${weight / 1000f}"
}
val firstWeight = firstGoodsArray.get(address, null)
if (firstWeight == null) {
//未设置数据
if (state != WeightUtil.STATE_STABLE) {
//首次记录数据需要稳定数据
if (address == AddressUtil.TWO) {
cookFoodEntity.foodWeight = weight.toDouble()
binding.tvTotalWeight.text = "${weight / 1000f}"
}
val firstWeight = firstGoodsArray.get(address, null)
if (firstWeight == null) {
//未设置数据
if (state != WeightUtil.STATE_STABLE) {
//首次记录数据需要稳定数据
return@addWeightListener
}
firstGoodsArray.put(address, weight)
return@addWeightListener
}
firstGoodsArray.put(address, weight)
return@addWeightListener
}
var realUseWeight = firstWeight - weight
realUseWeight = if (realUseWeight > 0) realUseWeight else 0.0
seasoningArray.put(address, realUseWeight.toDouble())
var realUseWeight = firstWeight - weight
realUseWeight = if (realUseWeight > 0) realUseWeight else 0.0
seasoningArray.put(address, realUseWeight.toDouble())
// refreshSeasoningWeight()
val item = seasoningItems.firstOrNull { address == addressArray[it.sort]}
item?.let {
val lastWeight = seasoningArray.get(address) ?: 0.toDouble()
it.useWeight = (lastWeight + getCookingSeasoning(address)).roundedOneDecimalPlace()
updateGridData(it)
}
})
val item = seasoningItems.firstOrNull { address == addressArray[it.sort] }
item?.let {
val lastWeight = seasoningArray.get(address) ?: 0.toDouble()
it.useWeight =
(lastWeight + getCookingSeasoning(address)).roundedOneDecimalPlace()
updateGridData(it)
}
})
}
// private fun refreshSeasoningWeight() {
@@ -217,17 +221,18 @@ class SubmitFoodActivity : BaseActivity() {
// setGridData(seasoningItems)
// }
private fun updateGridData(entity:SeasoningEntity) {
private fun updateGridData(entity: SeasoningEntity) {
val gridLayout = binding.include.root
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout , entity)
TextCellAdapter.loadLayout(frameLayout, entity)
}
private fun getCookingSeasoning(address: Int): Double {
if (isCooking.not()) return 0.toDouble()
if (seasoningItems.isEmpty()) return 0.toDouble()
val weight = seasoningCookingItems?.get(weightRelateArray2.get(address))?.useWeight ?: 0.toDouble()
val weight =
seasoningCookingItems?.get(weightRelateArray2.get(address))?.useWeight ?: 0.toDouble()
return weight
}
@@ -260,12 +265,12 @@ class SubmitFoodActivity : BaseActivity() {
return
}
val realSeasoningData = seasoningItems
//(binding.rvSeasoning.adapter as TextCellAdapter).list
//(binding.rvSeasoning.adapter as TextCellAdapter).list
if (realSeasoningData.isEmpty() && !isCooking) {
toast("未获取到调料信息")
return
}
if (realSeasoningData.isNotEmpty()&&realSeasoningData[0].goodsId.isNullOrBlank()) {
if (realSeasoningData.isNotEmpty() && realSeasoningData[0].goodsId.isNullOrBlank()) {
toast("还未设置调料信息,请去设置页面操作")
return
}
@@ -316,7 +321,7 @@ class SubmitFoodActivity : BaseActivity() {
toast("未获取到调料信息")
return
}
if (realSeasoningData.isNotEmpty()&&realSeasoningData[0].goodsId.isNullOrBlank()) {
if (realSeasoningData.isNotEmpty() && realSeasoningData[0].goodsId.isNullOrBlank()) {
toast("还未设置调料信息,请去设置页面操作")
return
}
@@ -335,8 +340,9 @@ class SubmitFoodActivity : BaseActivity() {
}
cookFoodEntity.let {
it.stFoodInfoConstituteList = this@SubmitFoodActivity.goodsList
it.dinnerType = when(it.dinnerType) {
// it.stFoodInfoConstituteList = this@SubmitFoodActivity.goodsList
it.matchingConstituteInfoList = this@SubmitFoodActivity.goodsList
it.dinnerType = when (it.dinnerType) {
"1" -> "早餐"
"2" -> "午餐"
"3" -> "晚餐"
@@ -352,14 +358,24 @@ class SubmitFoodActivity : BaseActivity() {
}
val json = cookFoodEntity.toJsonString()
Log.d(TAG, "submit: json=$json")
HttpUtil.postJson(
url = UrlConfig.SUBMIT_DISH,
json = json,
doSuccess = {
Log.d(TAG, "submit: postJson=$it")
// HttpUtil.postJson(
// url = UrlConfig.SUBMIT_DISH,
// json = json,
// doSuccess = {
// Log.d(TAG, "submit: postJson=$it")
// submitSuccess(isSamplingData)
// }, doFailure = { code, msg ->
// Log.d(TAG, "submit: postJson:code=$code,msg=$msg")
// toast(msg)
// dismissLoading()
// })
netViewModel.submitCookFood(
entity = cookFoodEntity,
onSuccess = {
submitSuccess(isSamplingData)
}, doFailure = { code, msg ->
Log.d(TAG, "submit: postJson:code=$code,msg=$msg")
},
onFailure = { code, msg ->
toast(msg)
dismissLoading()
})
@@ -417,7 +433,7 @@ class SubmitFoodActivity : BaseActivity() {
}
private var seasoningItems = mutableListOf<SeasoningEntity>()
private var seasoningCookingItems: List<SeasoningEntity> ?= null
private var seasoningCookingItems: List<SeasoningEntity>? = null
private fun loadTextCell(list: MutableList<SeasoningEntity>) {
seasoningItems.clear()
seasoningItems.addAll(list)
@@ -434,8 +450,8 @@ class SubmitFoodActivity : BaseActivity() {
}
private fun initConfigData() {
repeat(12) {num->
val firstOne = seasoningItems.firstOrNull {it.sort == num}
repeat(12) { num ->
val firstOne = seasoningItems.firstOrNull { it.sort == num }
if (firstOne == null) {
seasoningItems.add(SeasoningEntity().also { it.sort = num })
}
@@ -0,0 +1,368 @@
package com.shuwei.dish.match.ui.fragment
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.recyclerview.widget.GridLayoutManager
import com.shuwei.dish.match.R
import com.shuwei.dish.match.adapter.FoodCollectionAdapter
import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentCollectBinding
import com.shuwei.dish.match.dialog.Loading
import com.shuwei.dish.match.objbox.FoodCollectionBean
import com.shuwei.dish.match.objbox.FoodModule
import com.shuwei.dish.match.ui.SettingActivity
import com.shuwei.dish.match.utils.BitmapSaver
import com.shuwei.dish.match.utils.Debouncer
import com.shuwei.dish.match.utils.ImageUtil
import com.shuwei.dish.match.utils.LogSaveUtil
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.toast
@SuppressLint("NotifyDataSetChanged")
class CollectFragment : BaseFragment<FragmentCollectBinding>() {
companion object {
private const val TAG = "CollectFragment"
const val MAX_COUNT = 9
}
private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
repeat(MAX_COUNT) {
add(FoodCollectionBean(isShowCamera = true))
}
}
// private val searchFoodList = mutableListOf<FoodInfo>()
// private var settingActivity: SettingActivity? = null
// private var checkedItem: FoodInfo? = null
// private val searchFoodAdapter by lazy {
// CollectFoodListAdapter(searchFoodList).apply {
// setOnItemClickListener { adapter, view, position ->
// searchFoodList.forEachIndexed { index, item -> item.isChecked = index == position }
// checkedItem = searchFoodList[position]
// notifyDataSetChanged()
// selectedFoodId = checkedItem!!.foodId
// selectedFoodName = checkedItem!!.foodName
// }
// }
// }
private val debouncer = Debouncer(2000)
private lateinit var settingActivity: SettingActivity
private val collectionAdapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(foodCollectionList).apply {
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
foodCollectionList[position].let {
it.bitmap = null
it.imageVector = null
it.imageFile = null
it.imageUri = null
it.isShowCamera = true
it.isFinish = false
it.uploadSuccess = false
}
notifyItemChanged(position)
}
}
}
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentCollectBinding {
return FragmentCollectBinding.inflate(LayoutInflater.from(context))
}
//typealias CameraCallback = (Uri) -> Unit
private val cameraCallback: (Uri) -> Unit = { uri ->
try {
val index = foodCollectionList.indexOfFirst { it.imageFile == null }
if (index == -1) {
toast("每次只允许保存${MAX_COUNT}条数据")
hideWaitingDialog()
rerurn@ cameraCallback
}
activity?.runOnUiThread {
foodCollectionList[index].let {
it.imageVector = null
it.bitmap = null
it.isShowCamera = false
it.imageFile = null
it.imageUri = uri
}
collectionAdapter.notifyItemChanged(index)
}
// hideWaitingDialog()
Thread {
ImageUtil.uriToBitmap(requireActivity(), uri)?.let { bitmap ->
getImageVector(index, bitmap)
}
}.start()
} catch (e: Exception) {
e.printStackTrace()
hideWaitingDialog()
toast("程序异常${e.message}")
log("程序异常${e.message}")
}
}
private fun getImageVector(index: Int, bitmap: Bitmap) {
// val bitmap = BitmapCropper.cropCenter(
// original = srcBmp,
// targetWidth = 900, targetHeight = 900,
//// offsetX = 30, offsetY = 100
// )
val imageVector = try {
FoodModule.bitmap2FloatArray(bitmap, false)
} catch (e: Exception) {
e.printStackTrace()
toast("操作失败")
log("操作失败:${e.message}")
hideWaitingDialog()
return
}
val file = BitmapSaver.saveToAppFilesDir(
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
activity?.runOnUiThread {
foodCollectionList[index].let {
it.imageVector = imageVector
it.bitmap = null
it.isShowCamera = false
it.imageFile = file
}
collectionAdapter.notifyItemChanged(index)
}
if (bitmap.isRecycled.not()) {
bitmap.recycle()
}
hideWaitingDialog()
}
@SuppressLint("NotifyDataSetChanged")
private fun takePhoto() {
val count = foodCollectionList.count { it.bitmap != null }
if (count >= MAX_COUNT) {
toast("每次只允许保存${MAX_COUNT}条数据")
return
}
showWaitingDialog("采集中……")
settingActivity.takePhoto(succCallback = cameraCallback, failCallback = { errMsg ->
hideWaitingDialog()
// toast("拍照异常,请重新操作")
// if (cameraErrorCount >= 10) {
// toast("拍照异常,请重新操作")
// log("拍照异常,请重新操作:$errMsg")
// cameraErrorCount = 0
// return@takePhoto
// }
// cameraErrorCount++
// takePhoto()
})
}
override fun initialize() {
settingActivity = activity as SettingActivity
// 首次显示 CollectFragment 时初始化相机
settingActivity.initCamera(binding.flCameraPreview)
binding.rvFoodList.let {
it.layoutManager =
GridLayoutManager(requireActivity(), 3, GridLayoutManager.VERTICAL, false)
it.adapter = collectionAdapter
}
binding.btnFoodSearch.setOnClickListener {
searchFood()
}
binding.btnSave.setOnClickListener {
// if (checkedItem == null || checkedItem!!.isChecked.not()) {
// toast("请选择菜品名称")
// return@setOnClickListener
// }
// val count = foodCollectionList.count { it.imageFile != null }
// if (count == 0) {
// toast("请拍摄菜品照片")
// return@setOnClickListener
// }
// upload()
}
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchFood()
val imm =
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
true
} else {
false
}
}
binding.btnCollectedFood.setOnClickListener {
// startActivity(Intent(requireActivity(), CollectedFoodActivity::class.java))
}
binding.btnTakePhoto.clickWithDebounce {
val count = foodCollectionList.count { it.bitmap != null }
if (count >= MAX_COUNT) {
toast("每次只允许保存${MAX_COUNT}条数据")
return@clickWithDebounce
}
takePhoto()
}
binding.btnClearData.setOnClickListener { clearData() }
// binding.rvSearchFood.let {
// it.layoutManager = GridLayoutManager(context, 2)
// it.adapter = searchFoodAdapter
// }
searchFood()
}
@SuppressLint("NotifyDataSetChanged")
private fun upload() {
// lifecycleScope.launch {
// val totalFileCount = foodCollectionList.count { it.imageFile != null }
// showWaitingDialog2("图片上传中0/$totalFileCount")
// val params = HashMap<String, RequestBody>()
// //params["placeId"] = restId.toRequestBody()
// params["foodId"] = checkedItem!!.foodId.toRequestBody()
// params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
// params["version"] = GlobalData.foodModelVersion.toRequestBody()
//
// ImageUploader(totalList = foodCollectionList, uploadImage = { batch ->
// val files = batch.map { it.imageFile }
// val foodVectorList = batch.filter { it.imageVector != null }.map {
// it.imageVector!!.joinToString(
// separator = ",", prefix = "[", postfix = "]"
// )
// }
// val foodVectorJson =
// foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
// log("json=$foodVectorJson")
// params["foodVector"] = foodVectorJson.toRequestBody()
// viewModel?.uploadCollectFoodPics(files, params)
// }, onProgress = { count, batch, idList ->
// //batch.forEach {
// // it.uploadSuccess = true
// //}
// runBlocking {
// activity?.runOnUiThread {
// showWaitingDialog("图片上传中$count/$totalFileCount")
// }
// val foodList = batch.mapIndexed { index, it ->
// Food(
// collectId = if (index < idList.size) idList[index] else null,
// foodId = checkedItem!!.foodId,
// foodName = checkedItem!!.foodName,
// foodVector = it.imageVector,
// version = GlobalData.foodModelVersion
// )
// }
// ObjectBox.putAll(foodList)
// activity?.runOnUiThread {
// batch.forEach { it.isFinish = true }
// collectionAdapter.notifyDataSetChanged()
// }
// }
// }, onError = {
// activity?.runOnUiThread {
// binding.root.postDelayed({
// hideWaitingDialog()
// toast("上传失败,请稍后重试")
// }, 1000)
// }
// }, onComplete = {
// //vectorThread()
// binding.root.postDelayed({
// hideWaitingDialog()
// toast("上传成功")
// }, 1000)
// }).processUploads()
// }
}
@SuppressLint("NotifyDataSetChanged")
fun searchFood() {
debouncer.debounce {
searchByFoodName(binding.editFoodName.text.toString()) {
// searchFoodList.clear()
// searchFoodList.addAll(it)
// searchFoodAdapter.notifyDataSetChanged()
}
}
}
var clickIndex = -1
@SuppressLint("NotifyDataSetChanged")
fun clearData() {
foodCollectionList.forEach {
it.bitmap = null
it.imageVector = null
it.imageFile = null
it.imageUri = null
it.isShowCamera = true
it.isFinish = false
it.uploadSuccess = false
}
collectionAdapter.notifyDataSetChanged()
clickIndex = -1
binding.editFoodName.setText("")
searchFood()
}
private var isFirstOpen = true
override fun onResume() {
super.onResume()
if (isVisible.not()) return
binding.llCameraFlag.run {
visibility = View.VISIBLE
postDelayed({
visibility = View.GONE
}, 1000)
}
}
override fun onPause() {
super.onPause()
binding.llCameraFlag.visibility = View.VISIBLE
}
private fun log(msg: String) {
Log.d(TAG, "log: $msg")
LogSaveUtil.saveLogFile(msg)
}
private fun searchByFoodName(name: String, block: (String) -> Unit) {}
private fun hideWaitingDialog() {
settingActivity.dismissLoading()
}
private fun showWaitingDialog(msg: String) {
if (Loading.isShowing()) {
Loading.updateContent(msg)
return
}
settingActivity.showLoading(msg)
}
}
@@ -0,0 +1,365 @@
package com.shuwei.dish.match.ui.fragment
import android.graphics.Typeface
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import android.text.style.LineHeightSpan
import android.text.style.StyleSpan
import android.util.Log
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.graphics.toColorInt
import androidx.core.view.forEach
import androidx.lifecycle.ViewModelProvider
import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentDeviceConfigBinding
import com.shuwei.dish.match.db.AppRepository
import com.shuwei.dish.match.dialog.CommonDialog
import com.shuwei.dish.match.dialog.SeasoningSearchDialog
import com.shuwei.dish.match.entity.SeasoningEntity
import com.shuwei.dish.match.entity.ResetReasoningRecord
import com.shuwei.dish.match.adapter.TextCellAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.utils.AddressUtil
import com.shuwei.dish.match.utils.JsonAssetsLoader
import com.shuwei.dish.match.utils.MultiClickDetector
import com.shuwei.dish.match.utils.WeightUtil
import com.shuwei.dish.match.utils.ext.appendText
import com.shuwei.dish.match.utils.ext.buildSpannableString
import com.shuwei.dish.match.utils.ext.dp
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import com.shuwei.dish.match.viewmodel.AppViewModel
import com.shuwei.dish.match.viewmodel.factory.AppFactory
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
import com.shuwei.dish.match.ui.SamplingListActivity
import com.shuwei.dish.match.ui.SelectDishActivity
import com.shuwei.dish.match.utils.SpTool
import com.shuwei.dish.match.utils.ext.startActivity
/**
* 设备配置Fragment
* 功能:支持三种模式选择(制作、采样、品控),调料网格配置管理
*/
class DeviceConfigFragment : BaseFragment<FragmentDeviceConfigBinding>() {
companion object {
const val TAG = "DeviceConfigFragment"
const val COOK_MODE = "cookMode"
val modeList = listOf<ModeBean>(
ModeBean("制作模式", "菜品快速制作,记录熟重"),
ModeBean("采样模式", "菜品快速制作,记录熟重"),
ModeBean("品控模式", "菜品快速制作,记录熟重")
)
fun newInstance(cookMode: Int = 0): DeviceConfigFragment {
return DeviceConfigFragment().apply {
arguments = Bundle().apply {
putInt(COOK_MODE, cookMode)
}
}
}
}
data class ModeBean(
var modeName: String,
var modeDesc: String
)
private lateinit var appViewModel: AppViewModel
private var cookMode: Int = 0
private val weightArray = SparseArray<Double>()
private val addressArray = AddressUtil.getWeighAddressArray()
private var seasoningItems = mutableListOf<SeasoningEntity>()
private val detector = MultiClickDetector(targetCount = 10, intervalMs = 800)
override fun inflateBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentDeviceConfigBinding {
return FragmentDeviceConfigBinding.inflate(inflater, container, false)
}
override fun initialize(){
cookMode = arguments?.getInt(COOK_MODE, 0) ?: 0
initViewModel()
initUI()
addWeighListener()
addGridItemListener()
loadSeasoning()
}
/**
* 初始化ViewModel
*/
private fun initViewModel() {
val db = BaseApp.instance!!.database
val factory = AppFactory(AppRepository(db.appDao()))
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
}
/**
* 初始化UI
*/
private fun initUI() {
loadQualitySpan(isEnable = false)
when (cookMode) {
1 -> {
binding.rbModeCook.isChecked = true
binding.rbModeSampling.isChecked = false
loadCookSpan(isClicked = true)
loadSamplingSpan()
}
2 -> {
binding.rbModeCook.isChecked = false
binding.rbModeSampling.isChecked = true
loadCookSpan()
loadSamplingSpan(isClicked = true)
}
else -> {
binding.rbModeCook.isChecked = true
binding.rbModeSampling.isChecked = false
loadCookSpan(isClicked = true)
loadSamplingSpan()
}
}
binding.rbModeCook.setOnClickListener {
SpTool.put(SpTool.LAUNCH_PAGE_TYPE, 0)
binding.rbModeCook.isChecked = true
binding.rbModeSampling.isChecked = false
loadCookSpan(isClicked = true)
loadSamplingSpan()
activity?.startActivity<SelectDishActivity> {
putExtra(SelectDishActivity.IS_CONFIG_PAGE, true)
}
activity?.finish()
}
binding.rbModeSampling.setOnClickListener {
SpTool.put(SpTool.LAUNCH_PAGE_TYPE, 1)
binding.rbModeCook.isChecked = false
binding.rbModeSampling.isChecked = true
loadCookSpan()
loadSamplingSpan(isClicked = true)
activity?.startActivity<SamplingListActivity> {
putExtra(SamplingListActivity.IS_CONFIG_PAGE, true) }
activity?.finish()
}
}
/**
* 添加称重监听
*/
private fun addWeighListener() {
WeightUtil.addWeightListener(
weightKey = TAG,
getWeight = { address, state, weight ->
Log.d(TAG, "addWeighListener: address=$address,stat=$state,weight=$weight")
weightArray.put(address, weight)
val item = seasoningItems.firstOrNull { address == addressArray[it.sort] }
item?.let {
it.useWeight = weight.toDouble()
updateGridData(it)
}
Log.d(TAG, "addWeighListener: update-----------------------")
})
}
/**
* 添加网格项点击监听
*/
private fun addGridItemListener() {
binding.include.root.forEach { child ->
child.clickWithDebounce {
val sort = child.tag.toString().toInt()
val entity = seasoningItems.firstOrNull { it.sort == sort } ?: SeasoningEntity()
clickGridItem(sort, entity)
}
}
}
/**
* 加载调料数据
*/
private fun loadSeasoning() {
appViewModel.loadSeasoning {
seasoningItems.clear()
seasoningItems.addAll(it)
initConfigData()
setGridData(seasoningItems)
}
}
/**
* 初始化配置数据,确保有12个调料位置
*/
private fun initConfigData() {
repeat(12) { num ->
val firstOne = seasoningItems.firstOrNull { it.sort == num }
if (firstOne == null) {
seasoningItems.add(SeasoningEntity().also { it.sort = num })
}
}
}
/**
* 设置网格数据
*/
private fun setGridData(list: MutableList<SeasoningEntity>) {
val gridLayout = binding.include.root
list.forEach { entity ->
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
}
}
/**
* 更新网格数据
*/
private fun updateGridData(entity: SeasoningEntity) {
val gridLayout = binding.include.root
val tag = entity.sort.toString()
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
}
/**
* 点击网格项,打开调料搜索对话框
*/
private fun clickGridItem(sort: Int, entity: SeasoningEntity) {
val currentAddress = AddressUtil.getWeighAddressArray().get(sort)
SeasoningSearchDialog(
weighIndex = sort,
weighAddress = currentAddress,
clickName = entity.goodsName
).show(requireActivity() as BaseActivity) { item ->
syncItem(entity, item)
// 3-调料
entity.materialType = 3
entity.sort = sort
saveSeasoning(entity) {
val tag = sort.toString()
val gridLayout = binding.include.root
val frameLayout = gridLayout.findViewWithTag<FrameLayout>(tag)
TextCellAdapter.loadLayout(frameLayout, entity)
}
}
}
/**
* 同步调料项数据
*/
private fun syncItem(oldItem: SeasoningEntity, newItem: SeasoningEntity) {
oldItem.run {
goodsId = newItem.goodsId
goodsName = newItem.goodsName
goodsOrRelationCode = newItem.goodsOrRelationCode
relateionType = newItem.relateionType
materialType = newItem.materialType
allEdible = newItem.allEdible
useWeight = newItem.useWeight
popularName = newItem.popularName
canteenId = newItem.canteenId
relateionType_dictText = newItem.relateionType_dictText
foodId = newItem.foodId
sort = newItem.sort
}
}
/**
* 保存调料数据
*/
fun saveSeasoning(entity: SeasoningEntity, action: () -> Unit = {}) {
appViewModel.saveSeasoning(entity) {
action()
toast("保存成功")
}
}
/**
* 加载品控模式文本样式
*/
private fun loadQualitySpan(isClicked: Boolean = false, isEnable: Boolean = true) {
binding.rbModeQuality.text =
getModeTextSpan(
topText = modeList[2].modeName,
bottomText = modeList[2].modeDesc,
topColor = if (isEnable) if (isClicked) "#00BC71" else "#000000" else "#B4B4B4",
bottomColor = if (isEnable) "#8B8B8B" else "#B4B4B4"
)
}
/**
* 加载采样模式文本样式
*/
private fun loadSamplingSpan(isClicked: Boolean = false, isEnable: Boolean = true) {
binding.rbModeSampling.text =
getModeTextSpan(
topText = modeList[1].modeName,
bottomText = modeList[1].modeDesc,
topColor = if (isEnable) if (isClicked) "#00BC71" else "#000000" else "#B4B4B4",
bottomColor = if (isEnable) "#8B8B8B" else "#B4B4B4"
)
}
/**
* 加载制作模式文本样式
*/
private fun loadCookSpan(isClicked: Boolean = false, isEnable: Boolean = true) {
binding.rbModeCook.text =
getModeTextSpan(
topText = modeList[0].modeName,
bottomText = modeList[0].modeDesc,
topColor = if (isEnable) if (isClicked) "#00BC71" else "#000000" else "#B4B4B4",
bottomColor = if (isEnable) "#8B8B8B" else "#B4B4B4"
)
}
/**
* 生成模式文本样式
*/
private fun getModeTextSpan(
topText: String,
topColor: String,
bottomText: String,
bottomColor: String
): SpannableStringBuilder {
return buildSpannableString {
appendText(
topText,
ForegroundColorSpan(topColor.toColorInt()),
StyleSpan(Typeface.BOLD),
AbsoluteSizeSpan(36, true),
LineHeightSpan { text, start, end, spanstartv, v, fm ->
fm.descent += 10.dp // 增加行间距
}
)
append("\n")
appendText(
bottomText,
ForegroundColorSpan(bottomColor.toColorInt()),
AbsoluteSizeSpan(26, true)
)
}
}
override fun onDestroyView() {
super.onDestroyView()
// 移除称重监听
WeightUtil.removeWeightListener(TAG)
}
}
@@ -13,18 +13,12 @@ import com.shuwei.dish.match.base.BaseApp
import com.shuwei.dish.match.base.BaseFragment
import com.shuwei.dish.match.databinding.FragmentDishListBinding
import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding
import com.shuwei.dish.match.entity.FoodRecord
import com.shuwei.dish.match.entity.FoodRecordBean
import com.shuwei.dish.match.http.HttpUtil
import com.shuwei.dish.match.http.UrlConfig
import com.shuwei.dish.match.ui.PrepareCookActivity
import com.shuwei.dish.match.ui.SelectDishActivity
import com.shuwei.dish.match.ui.SubmitFoodActivity
import com.shuwei.dish.match.utils.SwipeCallback
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toJsonString
import com.shuwei.dish.match.utils.ext.toObject
import com.shuwei.dish.match.utils.ext.toast
import java.io.Serializable
@@ -90,13 +84,12 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
inflater: LayoutInflater,
container: ViewGroup?
): FragmentDishListBinding {
return FragmentDishListBinding.inflate(inflater, container, false)
return FragmentDishListBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun initialize() {
activity = requireActivity() as SelectDishActivity
dinnerType = arguments?.getString(DINNER_TYPE, "0")?:"0"
dinnerType = arguments?.getString(DINNER_TYPE, "0") ?: "0"
binding.rvDishList.run {
layoutManager =
LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
@@ -119,6 +112,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
pageNo = 1
activity.showLoading()
getDishList()
binding.refreshLayout.setEnableRefresh(true)
}
private fun addViewListener() {
@@ -134,6 +128,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
}
}
}
fun getDishList(pageNo: Int, dinnerType: String) {
this.pageNo = pageNo
this.dinnerType = dinnerType
@@ -153,40 +148,71 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@Suppress("unchecked_cast")
fun getDishList() {
activity.showLoading()
val map = mapOf(
// "foodName" to input,
"dinnerType" to getDinnerTypeText(),
"pageNo" to "$pageNo",
"pageSize" to "$pageSize",
"canteenId" to BaseApp.canteenId
)
val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
append("?")
map.forEach { (key, value) -> append("$key=$value&") }
}
sb.deleteCharAt(sb.length - 1)
HttpUtil.get(
url = sb.toString(),
doSuccess = {
loadDishList(it)
}, doFailure = { code, msg ->
try {
if (isAdded.not()) {
return@get
}
binding.refreshLayout.run {
setEnableRefresh(true)
}
toast(msg)
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
activity.delayDismissLoading()
} catch (e: Exception) {
e.printStackTrace()
activity.getFoodList(
param = mutableMapOf(
"pageNum" to pageNo,
"pageSize" to pageSize,
"placeId" to BaseApp.canteenId,
"dinnerType" to getDinnerTypeText()
),
onSuccess = { records ->
activity.delayDismissLoading()
finishRefresh()
loadDishList(records)
},
onFailure = { code, msg ->
if (isAdded.not()) {
return@getFoodList
}
})
toast(msg)
finishRefresh()
if (pageNo == 1) {
loadEmptyView()
}
activity.delayDismissLoading()
}
)
// val map = mapOf(
//// "foodName" to input,
// "dinnerType" to getDinnerTypeText(),
// "pageNo" to "$pageNo",
// "pageSize" to "$pageSize",
// "canteenId" to BaseApp.canteenId
// )
// val sb = StringBuilder(UrlConfig.QUERY_FOOD_LIST).apply {
// append("?")
// map.forEach { (key, value) -> append("$key=$value&") }
// }
// sb.deleteCharAt(sb.length - 1)
// HttpUtil.get(
// url = sb.toString(),
// doSuccess = { data ->
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// activity.delayDismissLoading()
// finishRefresh()
// val json = data.toJsonString()
// val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
// loadDishList(recordBean)
// }, doFailure = { code, msg ->
// try {
// if (isAdded.not()) {
// return@get
// }
// binding.refreshLayout.run {
// setEnableRefresh(true)
// }
// toast(msg)
// finishRefresh()
// if (pageNo == 1) {
// loadEmptyView()
// }
// activity.delayDismissLoading()
// } catch (e: Exception) {
// e.printStackTrace()
// }
// })
}
private var emptyViewBinding: LayoutEmptyViewBinding? = null
@@ -194,6 +220,8 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
try {
binding.refreshLayout.setEnableRefresh(true)
binding.refreshLayout.setEnableLoadMore(false)
list.clear()
dishAdapter.notifyDataSetChanged()
if (emptyViewBinding == null) {
@@ -233,7 +261,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
if (food != null) {
food.isCooking = true
food.sort = index
food.dinnerType = entity.dinnerType?:"0"
food.dinnerType = entity.dinnerType ?: "0"
tempList.add(food)
} else {
//当前查询到的list不包括本地数据id,可能数据在很多页以后了,暂时只能直接构造数据
@@ -242,7 +270,7 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
foodId = entity.foodId,
foodName = entity.foodName,
sort = index,
dinnerType = entity.dinnerType?:"0",
dinnerType = entity.dinnerType ?: "0",
isCooking = true
)
)
@@ -255,30 +283,22 @@ class DishListFragment : BaseFragment<FragmentDishListBinding>() {
}
@SuppressLint("NotifyDataSetChanged")
private fun loadDishList(data: Any) {
private fun loadDishList(records: MutableList<FoodRecord>?) {
try {
if (isAdded.not()) {
return
}
binding.refreshLayout.run {
setEnableRefresh(true)
}
activity.delayDismissLoading()
finishRefresh()
val json = data.toJsonString()
val recordBean: FoodRecordBean? = json.toObject<FoodRecordBean>()
if (recordBean == null || recordBean.records.isNullOrEmpty()) {
if (records.isNullOrEmpty()) {
//toast("暂未搜索到相关菜品信息")
if (pageNo == 1) {
loadEmptyView()
}
return
}
val records = recordBean.records
if (pageNo == 1) {
list.clear()
}
list.addAll(records!!)
list.addAll(records)
dishAdapter.notifyDataSetChanged()
val isLoadMoreEnable = records.size >= pageSize
binding.refreshLayout.setEnableLoadMore(isLoadMoreEnable)
@@ -0,0 +1,76 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
object AssetsTool {
// fun readJson(context: Context, fileName: String): List<String> {
// val json = readAssetsFile(context, fileName)
// val json2 = json.replace("[[", "[").replaceAfterLast("]]", "]")
// val list = json2.split("],")
// val resultList = mutableListOf<String>()
// var count = 0
// val tempList = mutableListOf<String>()
// list.forEachIndexed { index, text ->
// if (count >= 500) {
// val tempJson = "[${tempList.joinToString (",")}]"
// resultList.add(tempJson)
// tempList.clear()
// count = 0
// }
// val newText = if (index == list.size - 1) text else "${text}]"
// tempList.add(newText)
// count++
// }
// if (count < 500) {
// val tempJson = "[${tempList.joinToString(",")}]"
// resultList.add(tempJson)
// }
// return resultList
// }
fun readAssetsFile(context: Context, fileName: String): String {
val stringBuilder = StringBuilder()
try {
val bf = BufferedReader(InputStreamReader(context.assets.open(fileName)))
bf.useLines { lines -> lines.forEach { stringBuilder.append(it) } }
} catch (e: IOException) {
e.printStackTrace()
}
return stringBuilder.toString()
}
fun loadImagesFromAssets(context: Context, subPath: String): MutableList<Bitmap> {
val bitmaps: MutableList<Bitmap> = mutableListOf()
val assetManager = context.assets
try {
val files = assetManager.list(subPath)
files?.forEach { file ->
assetManager.open("$subPath/$file").use { `is` ->
val bitmap = BitmapFactory.decodeStream(`is`)
if (bitmap != null) {
bitmaps.add(bitmap)
}
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return bitmaps
}
fun loadImageBitmapFromAssets(context: Context, imagePath:String, action:(bmp: Bitmap)-> Unit) {
context.assets.open(imagePath).use { `is` ->
val bitmap = BitmapFactory.decodeStream(`is`)
action(bitmap)
}
}
}
@@ -0,0 +1,57 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
object BitmapSaver {
// 保存到公共目录(需WRITE_EXTERNAL_STORAGE权限)
fun saveToPublicDirectory(
bitmap: Bitmap,
folderName: String = Environment.DIRECTORY_PICTURES,
fileName: String,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,
quality: Int = 100
): File? {
val dir = Environment.getExternalStoragePublicDirectory(folderName)
if (!dir.exists()) dir.mkdirs()
return saveBitmap(bitmap, File(dir, fileName), format, quality)
}
// 保存到应用私有目录(无需权限)
fun saveToAppFilesDir(
bitmap: Bitmap,
context: Context,
fileName: String,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
quality: Int = 100
): File? {
//val dir = context.getExternalFilesDir(null)
val dir = File(context.cacheDir, "crop")
if (dir.exists().not()) {
dir.mkdirs()
}
return saveBitmap(bitmap, File(dir, fileName), format, quality)
}
private fun saveBitmap(
bitmap: Bitmap,
outputFile: File,
format: Bitmap.CompressFormat,
quality: Int
): File? {
return try {
FileOutputStream(outputFile).use { fos ->
bitmap.compress(format, quality, fos)
fos.flush()
}
outputFile
} catch (e: IOException) {
e.printStackTrace()
null
}
}
}
@@ -0,0 +1,84 @@
package com.shuwei.dish.match.utils
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.camera.core.CameraSelector
import androidx.camera.view.CameraController
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
class CameraUtils(private var activity: ComponentActivity) {
private var cameraController: LifecycleCameraController? = null
private var photoCaptureHelper: PhotoCaptureHelper? = null
private var failCallback: ((msg: String) -> Unit)? = null
// private var isCameraReady = false
fun takePhoto(succCallback: (Uri) -> Unit, failCallback: (msg: String) -> Unit = { }) {
this.failCallback = failCallback
cameraController?.let {
if (photoCaptureHelper == null) {
initCaptureHelper()
}
}
photoCaptureHelper?.let {
it.addSuccessCallback(succCallback)
it.bindCameraCallback {
bind()
}
it.takePhoto()
}
}
private fun initCaptureHelper() {
photoCaptureHelper = PhotoCaptureHelper(
context = activity,
cameraController = cameraController!!,
onSuccess = {},
onError = { msg ->
//toast(msg)
failCallback?.invoke(msg)
}
)
}
fun setPreviewController(previewView: PreviewView?) {
if (previewView?.controller == null) {
previewView?.controller = cameraController
}
}
fun initCamera() {
if (cameraController == null) {
cameraController = LifecycleCameraController(activity).apply {
// 必须设置有效的用例
setEnabledUseCases(
CameraController.IMAGE_CAPTURE
// or CameraController.VIDEO_CAPTURE
)
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
bind()
}
// if (isCameraReady.not()) {
// try {
// cameraController!!.initializationFuture.addListener({
// isCameraReady = true
// Timber.d("Camera initialized successfully")
// }, ContextCompat.getMainExecutor(this))
// } catch (e: Exception) {
// Timber.d("Camera initialized error = ${e.message}")
// }
// }
}
fun bind() {
cameraController?.bindToLifecycle(activity)
}
fun unbind() {
cameraController?.unbind()
}
}
@@ -0,0 +1,28 @@
package com.shuwei.dish.match.utils
/**
* 防抖工具
*/
class Debouncer(private val delayMillis: Long = 2000) {
private var lastActionTime = 0L
/**
* 执行防抖操作
* @param action 要执行的操作
* @return Boolean 是否执行了操作 (true=已执行, false=被防抖)
*/
fun debounce(action: () -> Unit): Boolean {
val currentTime = System.currentTimeMillis()
if (currentTime - lastActionTime >= delayMillis) {
lastActionTime = currentTime
action()
return true
}
return false
}
// 重置防抖计时
fun reset() {
lastActionTime = 0L
}
}
@@ -0,0 +1,251 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.graphics.Bitmap
import android.widget.ImageView
import androidx.annotation.DrawableRes
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
/**
* Glide图片加载工具类
*/
object GlideUtils {
// 默认配置
private val defaultOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.skipMemoryCache(false)
/**
* 普通加载图片
* @param context 上下文
* @param url 图片地址
* @param imageView 目标ImageView
* @param placeholderResId 占位图资源ID
* @param errorResId 错误图资源ID
*/
fun loadImage(
context: Context,
url: Any?,
imageView: ImageView,
@DrawableRes placeholderResId: Int = 0,
@DrawableRes errorResId: Int = 0
) {
val requestBuilder = Glide.with(context)
.load(url)
.apply(defaultOptions)
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView)
}
/**
* 加载圆形图片
* @param context 上下文
* @param url 图片地址
* @param imageView 目标ImageView
* @param placeholderResId 占位图资源ID
* @param errorResId 错误图资源ID
*/
fun loadCircleImage(
context: Context,
url: Any?,
imageView: ImageView,
@DrawableRes placeholderResId: Int = 0,
@DrawableRes errorResId: Int = 0
) {
val requestBuilder = Glide.with(context)
.load(url)
.apply(defaultOptions)
.transform(CircleCrop())
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
.into(imageView)
}
/**
* 加载圆角图片
* @param context 上下文
* @param url 图片地址
* @param imageView 目标ImageView
* @param radius 圆角半径(px)
* @param placeholderResId 占位图资源ID
* @param errorResId 错误图资源ID
*/
fun loadRoundCornerImage(
context: Context,
url: Any?,
imageView: ImageView,
radius: Int,
@DrawableRes placeholderResId: Int = 0,
@DrawableRes errorResId: Int = 0
) {
val requestBuilder = Glide.with(context)
.load(url)
.apply(defaultOptions)
.transform(RoundedCorners(radius))
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
.into(imageView)
}
fun loadRoundCornerWitBitmap(
context: Context,
url: Bitmap,
imageView: ImageView,
radius: Int,
@DrawableRes placeholderResId: Int = 0,
@DrawableRes errorResId: Int = 0
) {
val requestBuilder = Glide.with(context)
.load(url)
.apply(RequestOptions.bitmapTransform(RoundedCorners(radius)))
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
.into(imageView)
}
/**
* 加载图片并获取Bitmap
* @param context 上下文
* @param url 图片地址
* @param callback 加载回调
*/
fun loadImageAsBitmap(
context: Context,
url: Any?,
callback: (Bitmap?) -> Unit
) {
Glide.with(context)
.asBitmap()
.load(url)
.apply(defaultOptions)
.addListener(object : RequestListener<Bitmap> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap?>,
isFirstResource: Boolean
): Boolean {
callback(null)
return false
}
override fun onResourceReady(
resource: Bitmap,
model: Any,
target: Target<Bitmap?>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
callback(resource)
return false
}
})
.submit()
}
/**
* 清除内存缓存
* @param context 上下文
*/
fun clearMemoryCache(context: Context) {
Glide.get(context).clearMemory()
}
/**
* 清除磁盘缓存
* @param context 上下文
*/
fun clearDiskCache(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
Glide.get(context).clearDiskCache()
}
}
/**
* 获取缓存文件
* @param context 上下文
* @param url 图片地址
* @param callback 回调
*/
fun getImageCacheFile(
context: Context,
url: String,
callback: (File?) -> Unit
) {
Glide.with(context)
.downloadOnly()
.load(url)
.addListener(object : RequestListener<File> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<File?>,
isFirstResource: Boolean
): Boolean {
callback(null)
return false
}
override fun onResourceReady(
resource: File,
model: Any,
target: Target<File?>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
callback(resource)
return false
}
})
.submit()
}
/**
* 暂停请求
* @param context 上下文
*/
fun pauseRequests(context: Context) {
Glide.with(context).pauseRequests()
}
/**
* 恢复请求
* @param context 上下文
*/
fun resumeRequests(context: Context) {
Glide.with(context).resumeRequests()
}
// 私有方法:应用占位图和错误图
private fun <T> applyPlaceholder(
requestBuilder: RequestBuilder<T>,
@DrawableRes placeholderResId: Int,
@DrawableRes errorResId: Int
): RequestBuilder<T> {
return requestBuilder.apply {
if (placeholderResId != 0) {
placeholder(placeholderResId)
}
if (errorResId != 0) {
error(errorResId)
}
}
}
}
@@ -0,0 +1,56 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
object ImageUtil {
fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
return try {
val options = BitmapFactory.Options()
//options.inSampleSize = 2; // 这会将图片的尺寸缩小到原来的1/2
options.inJustDecodeBounds = false
// options.inPreferredConfig = Bitmap.Config.ARGB_8888
options.inPreferredConfig = Bitmap.Config.RGB_565
context.contentResolver.openInputStream(uri)?.use { stream ->
BitmapFactory.decodeStream(stream, null, options)
// BitmapFactory.decodeStream(stream)
}
context.contentResolver.openInputStream(uri)?.use { stream ->
BitmapFactory.decodeStream(stream)
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun uriToMultipart(context: Context, uri: Uri, partName: String): MultipartBody.Part? {
try {
val contentResolver = context.contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
// 临时文件(避免直接访问SAF文件)
val tempFile = File.createTempFile("upload_", ".jpg", context.cacheDir)
val outputStream = FileOutputStream(tempFile)
inputStream.copyTo(outputStream)
inputStream.close()
outputStream.close()
val requestFile = RequestBody.create("image/*".toMediaTypeOrNull(), tempFile)
return MultipartBody.Part.createFormData(partName, tempFile.name, requestFile)
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
}
@@ -0,0 +1,60 @@
package com.shuwei.dish.match.utils
import android.util.Log
import com.shuwei.dish.match.base.BaseApp
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object LogSaveUtil {
private const val TAG = "LogSaveUtil"
private const val CRASH_REPORTS_DIR = "crash_reports"
public fun saveLogFile(msg: String) {
saveLogFile("yyyy-MM-dd", msg)
}
public fun saveLogFile(pattern: String, msg: String) {
try {
saveLog(pattern, msg)
} catch (e: Exception) {
Log.d(TAG, "Error saving crash info to file:${e.message}")
try {
saveLog(pattern, "Error saving crash info to file:${e.message}")
} catch (e: Exception) {
Log.d(TAG, "save exception:${e.message}")
}
}
}
fun saveLog(pattern: String, logInfo: String) {
val time = SimpleDateFormat(pattern, Locale.getDefault()).format(Date())
val fileName = "crash_$time.log"
val crashDir = getCrashDir()
val crashFile = File(crashDir, fileName)
val saveTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
FileOutputStream(crashFile, true).use { it.write("$saveTime||$logInfo\n".toByteArray()) }
Log.d(TAG,"Crash info saved to: ${crashFile.absolutePath}")
}
fun getCrashDir(): File {
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
var crashDir = File(BaseApp.instance!!.filesDir, CRASH_REPORTS_DIR)
if (!crashDir.exists()) {
crashDir.mkdirs()
}
if (!(crashDir.exists())) {
crashDir = File(BaseApp.instance!!.cacheDir, CRASH_REPORTS_DIR)
}
return crashDir
}
}
@@ -0,0 +1,91 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.net.Uri
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.view.CameraController
import androidx.core.content.ContextCompat
import java.io.File
/**
* 拍照工具类
* @param context Context 上下文
* @param cameraController CameraController 相机控制器
* @param onSuccess (Uri) -> Unit 拍照成功回调
* @param onError (String) -> Unit 拍照失败回调
*/
class PhotoCaptureHelper(
private val context: Context,
private val cameraController: CameraController,
private val onSuccess: (Uri) -> Unit = {},
private val onError: (String) -> Unit = {}
) {
private val callbackList: MutableList<(Uri) -> Unit> = mutableListOf()
fun addSuccessCallback(callback:(Uri) -> Unit) {
if (callbackList.contains(callback).not()) {
callbackList.add(callback)
}
}
private var bindCamera:(()->Unit)?=null
fun bindCameraCallback(callback:()->Unit) {
this.bindCamera = callback
}
/**
* 拍照方法
* @param fileNamePrefix 文件名前缀,默认为"IMG_"
* @param fileExtension 文件扩展名,默认为".jpg"
*/
fun takePhoto(
fileNamePrefix: String = "IMG_",
fileExtension: String = ".jpg"
) {
// Timber.d("开始拍照采集")
try {
val executor = ContextCompat.getMainExecutor(context)
val cacheDir = context.cacheDir
val photoFile = File.createTempFile(
"${fileNamePrefix}${System.currentTimeMillis()}",
fileExtension,
cacheDir
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
cameraController.takePicture(
outputOptions,
executor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val photoUri = outputFileResults.savedUri ?: Uri.fromFile(photoFile)
// Timber.d("照片保存成功: $photoUri")
onSuccess(photoUri)
callbackList.forEach {
it(photoUri)
}
}
override fun onError(exception: ImageCaptureException) {
val errorMsg = "拍照失败: ${exception.message}"
if (errorMsg.contains("Not bound to a valid Camera")) {
if (bindCamera != null) {
bindCamera?.invoke()
//takePhoto()
}
}
// Timber.e(exception, errorMsg)
onError(errorMsg)
}
}
)
} catch (e: Exception) {
val errorMsg = "创建临时文件失败: ${e.message}"
// Timber.e(e, errorMsg)
onError(errorMsg)
}
}
}
@@ -1,6 +1,8 @@
package com.shuwei.dish.match.utils
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@@ -11,7 +13,20 @@ import com.shuwei.dish.match.R
object ToastUtil {
fun show(context: Context, message: String, duration:Int = Toast.LENGTH_SHORT) {
private val mainHandler = Handler(Looper.getMainLooper())
fun show(context: Context, message: String, duration: Int = Toast.LENGTH_SHORT) {
// 确保在主线程中执行
if (Thread.currentThread() == Looper.getMainLooper().thread) {
showToast(context, message, duration)
} else {
mainHandler.post {
showToast(context, message, duration)
}
}
}
private fun showToast(context: Context, message: String, duration: Int) {
val inflater = LayoutInflater.from(context)
val layout: View = inflater.inflate(R.layout.custom_toast, null)
val textView = layout.findViewById<TextView>(R.id.tvMessage)
@@ -22,7 +37,6 @@ object ToastUtil {
toast.setGravity(Gravity.CENTER, 0, 0) // 调整位置居中显示
toast.setDuration(duration)
toast.show()
}
}
@@ -24,7 +24,8 @@ public class Weigher2 {
public static final int ERR_004 = 1004;
public static final int ERR_PCB_NOT_SUPPORT = 2000;
private static SensorScale mSensorScale;
private static String mDevicePort = "/dev/ttyS4";
// private static String mDevicePort = "/dev/ttyS4";
private static String mDevicePort = "/dev/ttyS7";
private static boolean mConnect = false;
private static Listener mListener;
@@ -41,7 +42,8 @@ public class Weigher2 {
System.init();
initScale();
if (var0 == "pcb_908") {
mDevicePort = "/dev/ttyS4";
// mDevicePort = "/dev/ttyS4";
mDevicePort = "/dev/ttyS7";
}
mSensorScale.openScale(mDevicePort, 115200, (open) -> {
@@ -69,7 +69,8 @@ object WeightUtil {
fun tareTwo(address: Int) {
try {
Weigher2.tareTwo(address)
//Weigher2.tareTwo(address)
Weigher2.zeroTwo(address)
} catch (e: Exception) {
e.printStackTrace()
}
@@ -81,6 +82,10 @@ object WeightUtil {
}
}
fun removeWeightListener(weightKey: String){
weightFuncMap?.remove(weightKey)
}
fun addWeightListener(
weightKey: String,
getWeight: WeightCallback = { _, _, _ -> }
@@ -11,11 +11,13 @@ import android.util.TypedValue
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat
import androidx.fragment.app.Fragment
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.shuwei.dish.match.utils.GlideUtils
import com.shuwei.dish.match.utils.ToastUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -181,3 +183,6 @@ fun Double.roundedDecimalPlace(num: Int): Double {
fun Double.roundedOneDecimalPlace(): Double {
return this.roundedDecimalPlace(1)
}
fun ImageView.load(url: Any?) {
GlideUtils.loadImage(context, url, this)
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#FF0A1428" android:state_pressed="false"/>
<item android:color="#FFFF3232" android:state_pressed="true"/>
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFF3232"/>
<corners android:radius="12dp"/>
</shape>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 浅红色背景 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#FFE8E8" />
</shape>
</item>
<!-- 底部红色下划线(固定宽度100dp,高度3dp,距离底部5dp -->
<item android:bottom="5dp" android:top="62dp" android:gravity="center">
<shape android:shape="rectangle">
<size android:width="100dp" android:height="3dp" />
<solid android:color="#FF3232" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 底部白色下划线(固定宽度100dp,高度5dp,距离底部5dp,圆角5dp) -->
<item android:bottom="5dp" android:top="60dp" android:gravity="center">
<shape android:shape="rectangle">
<size android:width="100dp" android:height="5dp" />
<solid android:color="#FFFFFF" />
<corners android:radius="5dp" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 白色背景 -->
<item android:drawable="@color/white" />
<!-- 底部浅灰色下划线 -->
<item android:bottom="0dp">
<shape android:shape="rectangle">
<solid android:color="#E8E8E8" />
<size android:height="1dp" />
</shape>
</item>
</layer-list>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFE7EFF8"/>
<corners android:radius="8dp"/>
</shape>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="rectangle">
<corners android:radius="50dp"/>
<solid android:color="@color/white"/>
<stroke android:color="#FFF5F6F8" android:width="2dp"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="50dp"/>
<solid android:color="#FFFFF0F0"/>
<stroke android:color="#FFFF3232" android:width="2dp"/>
</shape>
</item>
</selector>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="false">
<shape android:shape="rectangle">
<corners android:radius="50dp"/>
<solid android:color="@color/white"/>
<!-- <stroke android:color="#FFC4CFDA" android:width="2dp"/>-->
</shape>
</item>
<item android:state_checked="true">
<shape android:shape="rectangle">
<corners android:radius="50dp"/>
<solid android:color="#FFFFF0F0"/>
<stroke android:color="#FFFF3232" android:width="2dp"/>
</shape>
</item>
</selector>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFF3232"/>
<size android:width="10dp" android:height="40dp"/>
<corners android:radius="5dp"/>
</shape>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tvFoodName"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@drawable/bg_text_dialog"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:layout_marginHorizontal="14dp"
android:layout_marginVertical="14dp"
android:paddingHorizontal="15dp"
android:textAlignment="center"
android:textColor="@color/color_text_dialog"
android:textSize="30sp"
android:textStyle="bold"
tools:text="肉沫干拌面" />
@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@android:color/white" />
</item>
</ripple>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFF4FAFF"/>
<stroke android:width="2dp" android:color="@color/white"/>
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white"/>
<stroke android:width="2dp" android:color="#FFC4CFDA"/>
<corners android:radius="45dp"/>
</shape>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#f0aaf0"/>
<size android:width="2dp" android:height="100dp"/>
</shape>
@@ -1,216 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@drawable/bg_other_page">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="106dp"
android:paddingStart="30dp"
android:paddingEnd="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:text="菜品模式设置"
android:textStyle="bold"
android:textColor="@color/black"
android:textSize="28sp" />
<TextView
android:id="@+id/tvSetting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:text="配置终端默认模式"
android:textColor="@color/gray_b4"
android:textSize="28sp" />
</FrameLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/gray_eb" />
<LinearLayout
android:id="@+id/modeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/rbModeCook"
android:layout_width="match_parent"
android:layout_height="190dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/white"
android:button="@null"
android:drawableEnd="@drawable/mode_select"
android:textSize="36sp"
tools:text="制作模式" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/gray_eb" />
<RadioButton
android:id="@+id/rbModeSampling"
android:layout_width="match_parent"
android:layout_height="190dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/white"
android:button="@null"
android:drawableEnd="@drawable/mode_select"
android:textSize="36sp"
tools:text="采样模式" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/gray_eb" />
<RadioButton
android:id="@+id/rbModeQuality"
android:layout_width="match_parent"
android:layout_height="190dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:button="@null"
android:drawableEnd="@drawable/ic_dish_disable"
android:enabled="false"
android:textColor="@color/gray_b4"
android:textSize="36sp"
tools:text="品控模式" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="@string/seasoning_setting"
android:textColor="@color/black666"
android:textSize="28sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="15dp"
android:text="@string/seasoning_setting2"
android:textColor="@color/gray_b4"
android:textSize="28sp" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSeasoning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginStart="23dp"
android:layout_marginEnd="22dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:itemCount="15"
tools:listitem="@layout/list_item_cell" />
<TextView
android:id="@+id/tvRightBottomCell"
android:layout_width="467dp"
android:layout_height="255dp"
android:layout_gravity="bottom|end"
android:layout_marginEnd="30dp"
android:layout_marginBottom="8dp"
android:background="@drawable/selector_text_cell"
android:gravity="center"
android:textColor="@color/black"
android:textSize="28sp"
android:textStyle="bold"
tools:text="食盐" />
</FrameLayout>
</LinearLayout>
<!-- <LinearLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:layout_marginStart="30dp"-->
<!-- android:layout_marginTop="10dp"-->
<!-- android:layout_marginEnd="30dp"-->
<!-- android:layout_marginBottom="30dp"-->
<!-- android:gravity="bottom"-->
<!-- android:orientation="horizontal">-->
<!-- <TextView-->
<!-- android:id="@+id/btnCancel"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="90dp"-->
<!-- android:layout_weight="1"-->
<!-- android:background="@drawable/ripple_effect_light"-->
<!-- android:gravity="center"-->
<!-- android:text="取消"-->
<!-- android:textColor="@color/dish_green"-->
<!-- android:textSize="32sp"-->
<!-- android:textStyle="bold"-->
<!-- tools:ignore="HardcodedText" />-->
<!-- <TextView-->
<!-- android:id="@+id/btnConfirm"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="90dp"-->
<!-- android:layout_marginStart="30dp"-->
<!-- android:layout_weight="1"-->
<!-- android:background="@drawable/ripple_effect_green"-->
<!-- android:gravity="center"-->
<!-- android:text="确定"-->
<!-- android:textColor="@color/white"-->
<!-- android:textSize="32sp"-->
<!-- android:textStyle="bold"-->
<!-- tools:ignore="HardcodedText" />-->
<!-- </LinearLayout>-->
</LinearLayout>
@@ -243,7 +243,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="30dp"
android:layout_weight="1"
android:background="@drawable/shape_white_30_corners"
@@ -262,15 +262,16 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDishPartList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"
android:overScrollMode="never"
tools:itemCount="3"
tools:listitem="@layout/list_item_dish_cook" />
tools:listitem="@layout/list_item_dish_cook"
android:scrollbars="vertical"/>
</LinearLayout>
<TextView
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCook"
android:layout_width="match_parent"
android:layout_height="90dp"
@@ -280,9 +281,10 @@
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold"
android:background="@drawable/ripple_effect_green"
android:background="@drawable/shape_green_bg"
android:clickable="true" />
<!-- android:background="@drawable/ripple_effect_green"-->
<!-- android:background="@drawable/shape_green_bg"-->
<!-- android:background="?attr/selectableItemBackground"-->
<!-- android:clickable="true"-->
@@ -62,7 +62,7 @@
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_marginBottom="40dp"
android:text="@string/dish_composition"
android:text="@string/dish_list"
android:textColor="@color/black666"
android:textSize="28sp" />
+3 -2
View File
@@ -31,7 +31,7 @@
android:textSize="30sp"
android:textStyle="bold"
tools:ignore="HardcodedText"
android:visibility="visible"/>
android:visibility="invisible"/>
<Space
android:layout_width="wrap_content"
@@ -45,6 +45,7 @@
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="50dp"
tools:src="@mipmap/ic_logo1024"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription"
android:visibility="invisible"/>
</LinearLayout>
@@ -204,7 +204,7 @@
android:layout_marginTop="39dp"
android:layout_marginBottom="39dp"
android:text="@string/dish_weight_set_remind"
android:textColor="@color/dish_green"
android:textColor="@color/black999"
android:textSize="26sp" />
</LinearLayout>
@@ -253,35 +253,26 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDishPartList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"
android:overScrollMode="never"
tools:itemCount="3"
tools:listitem="@layout/list_item_dish_cook" />
tools:listitem="@layout/list_item_dish_cook"
android:scrollbars="vertical" />
</LinearLayout>
<!-- <com.google.android.material.button.MaterialButton-->
<!-- android:id="@+id/btnCook"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="90dp"-->
<!-- android:layout_margin="30dp"-->
<!-- android:text="@string/goCooking"-->
<!-- android:textSize="32sp"-->
<!-- android:backgroundTint="@null"-->
<!-- style="@style/CookButtonStyle"-->
<!-- android:textStyle="bold"/>-->
<TextView
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCook"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_margin="30dp"
android:background="@drawable/ripple_effect_green"
android:background="@drawable/shape_green_bg"
android:gravity="center"
android:text="@string/goCooking"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold" />
<!-- android:background="@drawable/ripple_effect_green"-->
</LinearLayout>
@@ -90,16 +90,17 @@
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
</LinearLayout>
<TextView
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnAddSampling"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_margin="30dp"
android:background="@drawable/ripple_effect_green"
android:background="@drawable/shape_green_bg"
android:gravity="center"
android:text="@string/add_sampling"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold" />
<!-- android:background="@drawable/ripple_effect_green"-->
</LinearLayout>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 底部导航菜单 -->
<LinearLayout
android:id="@+id/bottomMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/transparent"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:elevation="8dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnDeviceConfig"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:text="设备配置"
android:textSize="28sp"
android:textStyle="bold"
android:background="@android:color/transparent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnFoodCollect"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_weight="1"
android:text="采集食材"
android:textSize="26sp"
android:background="@android:color/transparent" />
</LinearLayout>
<!-- Fragment 容器 -->
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
@@ -1,173 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:background="@drawable/bg_other_page">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/seasoning"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:textColor="@color/black666"
android:textSize="28sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSeasoning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginStart="23dp"
android:layout_marginEnd="22dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:itemCount="15"
tools:listitem="@layout/list_item_cell" />
<TextView
android:id="@+id/tvRightBottomCell"
android:layout_width="467dp"
android:layout_height="255dp"
android:layout_gravity="bottom|end"
android:layout_marginEnd="30dp"
android:layout_marginBottom="8dp"
android:background="@drawable/shape_white_fb_15_corners2"
android:gravity="center"
android:textColor="@color/black"
android:textSize="28sp"
android:textStyle="bold"
tools:text="食盐" />
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="30dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="30dp"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical"
android:paddingStart="30dp"
android:paddingEnd="30dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="120dp"
android:layout_height="80dp"
android:layout_gravity="start"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:gravity="center"
android:text="熟重"
android:textColor="@color/black666"
android:textSize="28sp" />
<TextView
android:id="@+id/btnWeightClear"
android:layout_width="120dp"
android:layout_height="80dp"
android:layout_gravity="end"
android:layout_marginTop="30dp"
android:layout_marginEnd="30dp"
android:background="@drawable/shape_green_stroke"
android:gravity="center"
android:text="清零"
android:textColor="@color/dish_green"
android:textSize="28sp" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvTotalWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="0.00"
android:textColor="@color/black"
android:textSize="90sp" />
<TextView
android:id="@+id/tvWeightUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:includeFontPadding="false"
android:text="千克"
android:textColor="@color/black999"
android:textSize="32sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="30dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/btnCook"
android:layout_width="0dp"
android:layout_height="90dp"
android:layout_weight="1"
android:gravity="center"
android:text="制作"
android:background="@drawable/ripple_effect_light"
android:textColor="@color/dish_green"
android:textSize="32sp"
android:textStyle="bold" />
<!-- android:background="@drawable/shape_green_stroke"-->
<TextView
android:id="@+id/btnSubmit"
android:layout_width="0dp"
android:layout_height="90dp"
android:layout_marginStart="30dp"
android:layout_weight="1"
android:background="@drawable/ripple_effect_green"
android:gravity="center"
android:text="提交"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
@@ -20,28 +20,28 @@
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/seasoning"
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="30dp"
android:text="@string/seasoning"
android:textColor="@color/black666"
android:textSize="28sp" />
<include
android:id="@+id/include"
layout="@layout/layout_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/layout_grid"/>
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="30dp"
android:layout_weight="1"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical"
android:paddingStart="30dp"
@@ -106,38 +106,41 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="30dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCook"
android:layout_width="0dp"
android:layout_height="90dp"
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_marginBottom="30dp"
android:layout_weight="1"
android:background="@drawable/shape_green_stroke"
android:gravity="center"
android:text="制作"
android:background="@drawable/ripple_effect_light"
android:textColor="@color/dish_green"
android:textSize="32sp"
android:textStyle="bold" />
<!-- android:background="@drawable/shape_green_stroke"-->
<!-- android:background="@drawable/ripple_effect_light"-->
<TextView
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSubmit"
android:layout_width="0dp"
android:layout_height="90dp"
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="30dp"
android:layout_weight="1"
android:background="@drawable/ripple_effect_green"
android:background="@drawable/shape_green_bg"
android:gravity="center"
android:text="提交"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold" />
<!-- android:background="@drawable/ripple_effect_green"-->
</LinearLayout>
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
tools:background="@color/white"
android:orientation="vertical">
<View
@@ -67,6 +68,7 @@
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
app:srlEnableOverScrollDrag="false">
<com.scwang.smart.refresh.header.ClassicsHeader
@@ -76,12 +78,13 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="45dp"
android:layout_marginTop="35dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="45dp"
android:layout_marginBottom="150dp"
android:layout_marginBottom="15dp"
android:nestedScrollingEnabled="true"
android:minHeight="380dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"

Some files were not shown because too many files have changed in this diff Show More