Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9818dfc438 | ||
|
|
cb9d68d2e3 | ||
|
|
b4087e435e | ||
|
|
4abc855b36 | ||
|
|
c898cbb3e2 | ||
|
|
0a09814a61 | ||
|
|
cc2fd2d743 | ||
|
|
cd883af92f | ||
|
|
9c60049843 | ||
|
|
e47b362883 | ||
|
|
ed6c5e83be | ||
|
|
a27cc5f7ee | ||
|
|
dd51448aea | ||
|
|
b62738aa15 | ||
|
|
7a60f4b34d | ||
|
|
c23aedf464 | ||
|
|
d9b725cb63 | ||
|
|
caefbd0944 |
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="17" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-08-05T03:50:06.075639Z">
|
<DropdownSelection timestamp="2025-11-07T10:55:54.677927600Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=2FD2511004054749" />
|
<DeviceId pluginId="Default" identifier="serial=192.168.1.56:5555;connection=2687c52a" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
<option name="gradleJvm" value="azul-17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
// alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
// alias(libs.plugins.kotlin.android)
|
||||||
id("com.google.devtools.ksp")
|
id("com.android.application")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
id("com.google.dagger.hilt.android")
|
id("com.google.dagger.hilt.android")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
|
kotlin("kapt")
|
||||||
id("com.github.ben-manes.versions") version "0.52.0"
|
|
||||||
id("io.objectbox") // ✅ 使用插件 id(不用带版本号)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -58,14 +57,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// implementation(
|
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
|
||||||
// fileTree(
|
|
||||||
// mapOf(
|
|
||||||
// "dir" to "libs",
|
|
||||||
// "include" to listOf("*.aar", "*.jar")
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
@@ -113,9 +105,15 @@ dependencies {
|
|||||||
implementation(libs.core)
|
implementation(libs.core)
|
||||||
implementation(libs.android.core)
|
implementation(libs.android.core)
|
||||||
|
|
||||||
implementation (libs.pytorch.android)
|
implementation(libs.pytorch.android)
|
||||||
implementation (libs.pytorch.android.torchvision)
|
implementation(libs.pytorch.android.torchvision)
|
||||||
|
|
||||||
val objectboxVersion = "5.0.1"
|
val objectboxVersion = "5.0.1"
|
||||||
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
||||||
|
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||||
|
|
||||||
|
implementation(libs.androidx.recyclerview)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(plugin = "io.objectbox")
|
||||||
@@ -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:594331511073099531",
|
||||||
|
"lastPropertyId": "4:1528837175750321569",
|
||||||
|
"name": "Food",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "1:2317727855243736226",
|
||||||
|
"name": "id",
|
||||||
|
"type": 6,
|
||||||
|
"flags": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2:340951913928211738",
|
||||||
|
"name": "name",
|
||||||
|
"type": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3:5484846939472684412",
|
||||||
|
"name": "foodIdx",
|
||||||
|
"type": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4:1528837175750321569",
|
||||||
|
"name": "foodVector",
|
||||||
|
"indexId": "1:6010838397086628487",
|
||||||
|
"type": 28,
|
||||||
|
"flags": 8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"relations": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastEntityId": "1:594331511073099531",
|
||||||
|
"lastIndexId": "1:6010838397086628487",
|
||||||
|
"lastRelationId": "0:0",
|
||||||
|
"lastSequenceId": "0:0",
|
||||||
|
"modelVersion": 5,
|
||||||
|
"modelVersionParserMinimum": 5,
|
||||||
|
"retiredEntityUids": [],
|
||||||
|
"retiredIndexUids": [],
|
||||||
|
"retiredPropertyUids": [],
|
||||||
|
"retiredRelationUids": [],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.DualScreen"
|
android:theme="@style/Theme.DualScreen"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<!-- 注册BootReceiver,监听开机完成广播 -->
|
<!-- 注册BootReceiver,监听开机完成广播 -->
|
||||||
@@ -50,6 +51,22 @@
|
|||||||
android:name=".activity.MainActivity"
|
android:name=".activity.MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.FoodCollectionActivity"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
<activity android:name="com.sw.dualscreen.activity.CollectedDataActivity" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
After Width: | Height: | Size: 515 KiB |
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"class_names": [
|
||||||
|
"回锅肉",
|
||||||
|
"手撕包菜",
|
||||||
|
"炒细面",
|
||||||
|
"红烧肉",
|
||||||
|
"肉沫酸豆角",
|
||||||
|
"蒜苔炒肉",
|
||||||
|
"西红柿鸡蛋",
|
||||||
|
"豆角茄子",
|
||||||
|
"麻婆豆腐",
|
||||||
|
"麻辣小面"
|
||||||
|
],
|
||||||
|
"class_to_idx": {
|
||||||
|
"回锅肉": 0,
|
||||||
|
"手撕包菜": 1,
|
||||||
|
"炒细面": 2,
|
||||||
|
"红烧肉": 3,
|
||||||
|
"肉沫酸豆角": 4,
|
||||||
|
"蒜苔炒肉": 5,
|
||||||
|
"西红柿鸡蛋": 6,
|
||||||
|
"豆角茄子": 7,
|
||||||
|
"麻婆豆腐": 8,
|
||||||
|
"麻辣小面": 9
|
||||||
|
},
|
||||||
|
"idx_to_class": {
|
||||||
|
"0": "回锅肉",
|
||||||
|
"1": "手撕包菜",
|
||||||
|
"2": "炒细面",
|
||||||
|
"3": "红烧肉",
|
||||||
|
"4": "肉沫酸豆角",
|
||||||
|
"5": "蒜苔炒肉",
|
||||||
|
"6": "西红柿鸡蛋",
|
||||||
|
"7": "豆角茄子",
|
||||||
|
"8": "麻婆豆腐",
|
||||||
|
"9": "麻辣小面"
|
||||||
|
},
|
||||||
|
"embedding_dim": 512,
|
||||||
|
"total_images": 500,
|
||||||
|
"build_time": "2025-10-16T17:51:06.285784"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
|
||||||
@@ -11,6 +11,7 @@ object GlobalData {
|
|||||||
* 设备id
|
* 设备id
|
||||||
*/
|
*/
|
||||||
var deviceId: String = ""
|
var deviceId: String = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* app版本号
|
* app版本号
|
||||||
*/
|
*/
|
||||||
@@ -38,7 +39,7 @@ object GlobalData {
|
|||||||
|
|
||||||
var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
|
var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
|
||||||
var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
|
var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
|
||||||
var activeKey = "085F-118G-Q3AB-1WVJ"
|
var activeKey = "085F-118G-Q3LH-HPGZ"//2号楼"085F-118G-Q3AB-1WVJ" 5号楼"085F-118G-Q3LH-HPGZ"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,4 +60,5 @@ object GlobalKey {
|
|||||||
const val KEY_TOKEN = "tokenKey"
|
const val KEY_TOKEN = "tokenKey"
|
||||||
const val KEY_FIRST_RUN = "firstRun"
|
const val KEY_FIRST_RUN = "firstRun"
|
||||||
const val KEY_USER_INFO = "userInfoKey"
|
const val KEY_USER_INFO = "userInfoKey"
|
||||||
|
const val KEY_PICKUP_MODE = "pickupMode"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sw.dualscreen
|
package com.sw.dualscreen
|
||||||
|
|
||||||
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
import com.sw.dualscreen.utils.CrashHandler
|
import com.sw.dualscreen.utils.CrashHandler
|
||||||
import com.sw.plate.App
|
import com.sw.plate.App
|
||||||
import com.sw.plate.utils.AppUtil
|
import com.sw.plate.utils.AppUtil
|
||||||
@@ -8,16 +9,18 @@ import timber.log.Timber
|
|||||||
class MyApp : App() {
|
class MyApp : App() {
|
||||||
companion object {
|
companion object {
|
||||||
const val DEBUG: Boolean = true
|
const val DEBUG: Boolean = true
|
||||||
|
var instance: MyApp?=null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
instance = this
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
var deviceId = AppUtil.getUDID(this)
|
var deviceId = AppUtil.getUDID(this)
|
||||||
Timber.d("UDID = ${AppUtil.getUDID(this)}")
|
Timber.d("UDID = ${AppUtil.getUDID(this)}")
|
||||||
deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
|
||||||
GlobalData.deviceId = deviceId
|
GlobalData.deviceId = deviceId
|
||||||
|
ObjectBox.init(this)
|
||||||
// 初始化崩溃处理器
|
// 初始化崩溃处理器
|
||||||
CrashHandler.init(this)
|
CrashHandler.init(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import android.view.View
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.model.IActivityResult
|
||||||
import com.sw.dualscreen.view.CustomDialog
|
import com.sw.dualscreen.view.CustomDialog
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -81,4 +81,20 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
mDialogWaiting?.dismiss()
|
mDialogWaiting?.dismiss()
|
||||||
mDialogWaiting = null
|
mDialogWaiting = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var launchPermissionCallback: IActivityResult.RequestPermissionCallback? = null
|
||||||
|
|
||||||
|
fun requestSinglePermissionResult(
|
||||||
|
permission: String,
|
||||||
|
permissionCallback: IActivityResult.RequestPermissionCallback?
|
||||||
|
) {
|
||||||
|
this.launchPermissionCallback = permissionCallback
|
||||||
|
permissionLauncher.launch(permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val permissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { result: Boolean ->
|
||||||
|
launchPermissionCallback?.callback(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.adapter.CollectedFoodAdapter
|
||||||
|
import com.sw.dualscreen.databinding.ActivityCollectedDataBinding
|
||||||
|
import com.sw.dualscreen.databinding.LayoutEmptySearchBinding
|
||||||
|
import com.sw.dualscreen.dialog.WarnDialog
|
||||||
|
import com.sw.dualscreen.ext.addOnActionSearchListener
|
||||||
|
import com.sw.dualscreen.ext.hideKeyboard
|
||||||
|
import com.sw.dualscreen.objbox.CollectedFoodBean
|
||||||
|
import com.sw.dualscreen.objbox.Food
|
||||||
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
|
import com.sw.plate.utils.ToastUtils
|
||||||
|
import io.objectbox.Box
|
||||||
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
|
class CollectedDataActivity : BaseActivity<ActivityCollectedDataBinding>() {
|
||||||
|
private val viewModel by viewModels<UserViewModel>()
|
||||||
|
override fun getViewModel(): BaseViewModel {
|
||||||
|
return viewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inflateViewBinding(): ActivityCollectedDataBinding {
|
||||||
|
return ActivityCollectedDataBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val list: MutableList<CollectedFoodBean> = mutableListOf()
|
||||||
|
private val adapter by lazy {
|
||||||
|
CollectedFoodAdapter(list).apply {
|
||||||
|
isStateViewEnable = true
|
||||||
|
addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position ->
|
||||||
|
deleteGoods(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initialize() {
|
||||||
|
super.initialize()
|
||||||
|
binding.rvFoodList.let {
|
||||||
|
it.layoutManager = LinearLayoutManager(this)
|
||||||
|
it.adapter = adapter
|
||||||
|
}
|
||||||
|
binding.ivBack.setOnClickListener { finish() }
|
||||||
|
binding.ivGoodsSearch.setOnClickListener {
|
||||||
|
val searchName = binding.etInputGoods.text.toString().trim()
|
||||||
|
if (searchName.isBlank()) {
|
||||||
|
ToastUtils.showToast("请输入物品名称")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
getCollectGoods(searchName)
|
||||||
|
}
|
||||||
|
binding.etInputGoods.let { v ->
|
||||||
|
v.addOnActionSearchListener {
|
||||||
|
val searchName = binding.etInputGoods.text.toString().trim()
|
||||||
|
if (searchName.isBlank()) {
|
||||||
|
ToastUtils.showToast("请输入物品名称")
|
||||||
|
return@addOnActionSearchListener
|
||||||
|
}
|
||||||
|
getCollectGoods(searchName)
|
||||||
|
}
|
||||||
|
v.addTextChangedListener {
|
||||||
|
if (it.isNullOrBlank()) {
|
||||||
|
getCollectGoods()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getCollectGoods()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var box: Box<Food>? = null
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun getCollectGoods(searchName:String?=null) {
|
||||||
|
if (box == null) {
|
||||||
|
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||||
|
}
|
||||||
|
list.clear()
|
||||||
|
var queryList = box?.all?.distinctBy { it.name }
|
||||||
|
if (searchName.isNullOrBlank().not()) {
|
||||||
|
queryList = queryList?.filter { it.name?.contains(searchName) == true }
|
||||||
|
}
|
||||||
|
queryList?.forEach {
|
||||||
|
list.add(CollectedFoodBean(foodName = it.name))
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
loadEmptyView()
|
||||||
|
}
|
||||||
|
binding.root.hideKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var emptyBinding: LayoutEmptySearchBinding? = null
|
||||||
|
private fun loadEmptyView() {
|
||||||
|
if (emptyBinding == null) {
|
||||||
|
emptyBinding = LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false)
|
||||||
|
}
|
||||||
|
emptyBinding?.root?.let { layout ->
|
||||||
|
layout.setOnClickListener { layout.hideKeyboard() }
|
||||||
|
adapter.stateView = layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteGoods(position: Int) {
|
||||||
|
WarnDialog(
|
||||||
|
context = context,
|
||||||
|
content = "请确认是否删除菜品:${list[position].foodName}?",
|
||||||
|
confirmBlock = {
|
||||||
|
Thread {
|
||||||
|
runOnUiThread {
|
||||||
|
showWaitingDialog("加载中……")
|
||||||
|
}
|
||||||
|
val name = list[position].foodName
|
||||||
|
val filterIdList = box?.all?.filter { it.name == name }?.map { it.id }
|
||||||
|
box?.removeByIds(filterIdList)
|
||||||
|
runOnUiThread {
|
||||||
|
hideWaitingDialog()
|
||||||
|
adapter.removeAt(position)
|
||||||
|
ToastUtils.showToast("删除成功")
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
loadEmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.example.utils.FloatBase64Utils
|
||||||
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.adapter.FoodCollectionAdapter
|
||||||
|
import com.sw.dualscreen.adapter.GenericItemAdapter
|
||||||
|
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
|
||||||
|
import com.sw.dualscreen.adapter.dpToPx
|
||||||
|
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
|
||||||
|
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
|
||||||
|
import com.sw.dualscreen.databinding.LayoutCameraPreviewBinding
|
||||||
|
import com.sw.dualscreen.ext.clickWithDebounce
|
||||||
|
import com.sw.dualscreen.ext.dp
|
||||||
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
import com.sw.dualscreen.objbox.Food
|
||||||
|
import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||||
|
import com.sw.dualscreen.objbox.FoodModule
|
||||||
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
|
import com.sw.dualscreen.utils.BitmapCropper
|
||||||
|
import com.sw.dualscreen.utils.BitmapSaver
|
||||||
|
import com.sw.dualscreen.utils.CameraHelper
|
||||||
|
import com.sw.dualscreen.utils.CameraUtils
|
||||||
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
|
import com.sw.plate.utils.ToastUtils
|
||||||
|
import io.objectbox.Box
|
||||||
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.pytorch.IValue
|
||||||
|
import org.pytorch.Module
|
||||||
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
||||||
|
companion object {
|
||||||
|
val MAX_COUNT = 100
|
||||||
|
}
|
||||||
|
private val viewModel by viewModels<UserViewModel>()
|
||||||
|
private var selectedFoodId: String? = ""
|
||||||
|
private var selectedFoodName: String? = ""
|
||||||
|
|
||||||
|
private var box: Box<Food>? = null
|
||||||
|
private val foodCollectionList: MutableList<FoodCollectionBean> = mutableListOf()
|
||||||
|
|
||||||
|
// private lateinit var cameraHelper: CameraHelper
|
||||||
|
|
||||||
|
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
|
||||||
|
private val foodList = mutableListOf<FoodInfo>() // 适配器内部维护的数据列表
|
||||||
|
private val debouncer = Debouncer(2000)
|
||||||
|
private lateinit var previewView: PreviewView
|
||||||
|
|
||||||
|
private val cameraUtils: CameraUtils by lazy {
|
||||||
|
CameraUtils(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val collectionAdapter: FoodCollectionAdapter by lazy {
|
||||||
|
FoodCollectionAdapter(foodCollectionList).apply {
|
||||||
|
// setOnItemClickListener { _, _, position ->
|
||||||
|
// if (list[position].isShowCamera) {
|
||||||
|
//// takePhoto()
|
||||||
|
// cameraHelper.openCamera()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
|
||||||
|
// list.removeAt(position)
|
||||||
|
// collectionAdapter.notifyItemRemoved(position)
|
||||||
|
// collectionAdapter.notifyItemRangeChanged(position, list.size)
|
||||||
|
foodCollectionList[position].let {
|
||||||
|
it.bitmap = null
|
||||||
|
it.isShowCamera = true
|
||||||
|
it.isFinish = false
|
||||||
|
}
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewModel(): BaseViewModel {
|
||||||
|
return viewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inflateViewBinding(): ActivityFoodCollectionBinding {
|
||||||
|
return ActivityFoodCollectionBinding.inflate(layoutInflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cameraCallback: (Uri) -> Unit = { uri ->
|
||||||
|
val index = foodCollectionList.indexOfFirst { it.bitmap == null }
|
||||||
|
if (index == -1) {
|
||||||
|
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||||
|
rerurn@ cameraCallback
|
||||||
|
}
|
||||||
|
ImageUtil.uriToBitmap(this, uri)?.let { bitmap ->
|
||||||
|
// val cropBitmap = BitmapCropper.cropCenter(
|
||||||
|
// original = bitmap,
|
||||||
|
// targetWidth = 1300, targetHeight = 900,
|
||||||
|
// //offsetX = 30, offsetY = 100
|
||||||
|
// )
|
||||||
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
|
bitmap, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
|
)
|
||||||
|
Timber.d("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||||
|
|
||||||
|
foodCollectionList[index].let {
|
||||||
|
it.bitmap = bitmap
|
||||||
|
it.isShowCamera = false
|
||||||
|
it.imageUri = file?.toUri()
|
||||||
|
}
|
||||||
|
collectionAdapter.notifyItemChanged(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun takePhoto() {
|
||||||
|
val count = foodCollectionList.count { it.bitmap != null }
|
||||||
|
if (count >= MAX_COUNT) {
|
||||||
|
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cameraUtils.takePhoto(cameraCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initialize() {
|
||||||
|
cameraUtils.initCamera()
|
||||||
|
val previewBinding =
|
||||||
|
LayoutCameraPreviewBinding.inflate(layoutInflater, binding.flCameraPreview)
|
||||||
|
previewView = previewBinding.previewView.also {
|
||||||
|
it.updateLayoutParams {
|
||||||
|
width = 180.dp
|
||||||
|
height = 180.dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cameraUtils.setPreviewController(previewView)
|
||||||
|
// 初始化 CameraHelper
|
||||||
|
// cameraHelper = CameraHelper(
|
||||||
|
// context = this,
|
||||||
|
// caller = this,
|
||||||
|
// authority = "${packageName}.fileprovider"
|
||||||
|
// ) { uri, path ->
|
||||||
|
// //if (foodCollectionList.size < 6) {
|
||||||
|
// // val insertIndex = if (foodCollectionList.isEmpty()) 0 else foodCollectionList.size - 1
|
||||||
|
// // foodCollectionList.add(insertIndex, FoodCollectionBean(imageUri = uri))
|
||||||
|
// //} else {
|
||||||
|
// // foodCollectionList[foodCollectionList.size - 1] = FoodCollectionBean(imageUri = uri)
|
||||||
|
// //}
|
||||||
|
// //collectionAdapter.notifyDataSetChanged()
|
||||||
|
// cameraCallback(uri)
|
||||||
|
// }
|
||||||
|
|
||||||
|
binding.ivBack.setOnClickListener {
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
repeat(MAX_COUNT) {
|
||||||
|
foodCollectionList.add(FoodCollectionBean(isShowCamera = true))
|
||||||
|
}
|
||||||
|
binding.rvFoodCollection.let {
|
||||||
|
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
|
||||||
|
it.adapter = collectionAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnFoodSearch.setOnClickListener {
|
||||||
|
searchInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnSave.setOnClickListener {
|
||||||
|
if (selectedFoodName.isNullOrBlank()) {
|
||||||
|
Toast.makeText(this, "请选择菜品名称", Toast.LENGTH_SHORT).show()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
if (foodCollectionList.size <= 1) {
|
||||||
|
Toast.makeText(this, "请拍摄菜品照片", Toast.LENGTH_SHORT).show()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
vectorThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||||
|
searchInfo()
|
||||||
|
val imm =
|
||||||
|
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnCollectedGoods.setOnClickListener {
|
||||||
|
startActivity(Intent(this, CollectedDataActivity::class.java))
|
||||||
|
}
|
||||||
|
binding.btnTakePhoto.clickWithDebounce {
|
||||||
|
binding.btnTakePhoto.text = "拍照"
|
||||||
|
val count = foodCollectionList.count { it.bitmap != null }
|
||||||
|
if (count >= MAX_COUNT) {
|
||||||
|
ToastUtils.showToast("每次只允许保存${MAX_COUNT}条数据")
|
||||||
|
return@clickWithDebounce
|
||||||
|
}
|
||||||
|
// cameraHelper.openCamera()
|
||||||
|
takePhoto()
|
||||||
|
}
|
||||||
|
binding.btnClearData.setOnClickListener { clearData() }
|
||||||
|
foodAdapter = createAdapter()
|
||||||
|
binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
|
||||||
|
// 添加间距装饰(12dp)
|
||||||
|
binding.recyclerview.addItemDecoration(
|
||||||
|
GridSpacingItemDecoration(
|
||||||
|
spanCount = 2,
|
||||||
|
spacing = dpToPx(30),
|
||||||
|
includeEdge = false // 包含边缘间距
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.recyclerview.adapter = foodAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun vectorThread() {
|
||||||
|
//Loading.show(this)
|
||||||
|
showWaitingDialog("加载中……")
|
||||||
|
Thread {
|
||||||
|
foodCollectionList
|
||||||
|
.filter { it.bitmap != null }
|
||||||
|
.forEachIndexed { index, it ->
|
||||||
|
image2VectorTask(it, index)
|
||||||
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
window.decorView.postDelayed({
|
||||||
|
//Loading.dismiss()
|
||||||
|
hideWaitingDialog()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initBox() {
|
||||||
|
if (box == null) {
|
||||||
|
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun image2VectorTask(item: FoodCollectionBean, position: Int) {
|
||||||
|
initBox()
|
||||||
|
val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
|
||||||
|
|
||||||
|
val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
|
||||||
|
Timber.tag("mzf1").e(base64Str)
|
||||||
|
// viewModel.postImageData(
|
||||||
|
// context,
|
||||||
|
// foodId = selectedFoodId.toString(),
|
||||||
|
// foodName = selectedFoodName.toString(),
|
||||||
|
// foodVector = base64Str,
|
||||||
|
// uri = item.imageUri!!
|
||||||
|
// )
|
||||||
|
box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector))
|
||||||
|
foodCollectionList[position].let {
|
||||||
|
it.imageVector = imageVector
|
||||||
|
it.isFinish = true
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
collectionAdapter.notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun searchInfo() {
|
||||||
|
debouncer.debounce {
|
||||||
|
viewModel.searchByFoodName(binding.editFoodName.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerDataChange() {
|
||||||
|
super.registerDataChange()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.searchFoodInfoList.collect {
|
||||||
|
// foodList.clear()
|
||||||
|
// foodList.addAll(it)
|
||||||
|
foodAdapter.updateData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var checkedItem: FoodInfo? = null
|
||||||
|
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
|
||||||
|
return GenericItemAdapter(
|
||||||
|
items = emptyList(),
|
||||||
|
bindingInflater = ItemSearchFoodInfoBinding::inflate,
|
||||||
|
bindCallback = { item, position ->
|
||||||
|
this.tvName.text = item.foodName
|
||||||
|
if (item.id != checkedItem?.id) {
|
||||||
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
|
||||||
|
this.tvName.setTextColor(resources.getColor(R.color.search_normal))
|
||||||
|
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
|
||||||
|
} else {
|
||||||
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
|
||||||
|
this.tvName.setTextColor(resources.getColor(R.color.search_checked))
|
||||||
|
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.llRoot.setOnClickListener {
|
||||||
|
Timber.d("itemClick ${item.foodName}, position = $position")
|
||||||
|
checkedItem = item
|
||||||
|
// viewModel.updateCurrentItem(item)
|
||||||
|
// itemClickCallback(item)
|
||||||
|
selectedFoodName = item.foodName
|
||||||
|
selectedFoodId = item.id
|
||||||
|
|
||||||
|
foodAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var clickIndex = -1
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun clearData() {
|
||||||
|
foodCollectionList.forEach {
|
||||||
|
it.bitmap = null
|
||||||
|
it.isShowCamera = true
|
||||||
|
it.isFinish = false
|
||||||
|
}
|
||||||
|
collectionAdapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
clickIndex = -1
|
||||||
|
binding.editFoodName.setText("")
|
||||||
|
// foodList.clear()
|
||||||
|
// foodAdapter.updateData(mutableListOf())
|
||||||
|
// foodAdapter.notifyDataSetChanged()
|
||||||
|
//loadEmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
cameraUtils.bind()
|
||||||
|
binding.llCameraFlag.run {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
postDelayed({
|
||||||
|
visibility = View.GONE
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
cameraUtils.unbind()
|
||||||
|
binding.llCameraFlag.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sw.dualscreen.activity
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
@@ -10,6 +11,7 @@ import android.view.View
|
|||||||
import android.view.ViewOutlineProvider
|
import android.view.ViewOutlineProvider
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.camera.core.ImageAnalysis
|
||||||
@@ -21,6 +23,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.sw.dualscreen.GlobalData
|
||||||
|
import com.sw.dualscreen.GlobalKey
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.adapter.GenericItemAdapter
|
import com.sw.dualscreen.adapter.GenericItemAdapter
|
||||||
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
|
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
|
||||||
@@ -28,20 +32,31 @@ import com.sw.dualscreen.adapter.dpToPx
|
|||||||
import com.sw.dualscreen.databinding.ActivityMainBinding
|
import com.sw.dualscreen.databinding.ActivityMainBinding
|
||||||
import com.sw.dualscreen.databinding.ItemFoodInfoBinding
|
import com.sw.dualscreen.databinding.ItemFoodInfoBinding
|
||||||
import com.sw.dualscreen.ext.dp
|
import com.sw.dualscreen.ext.dp
|
||||||
|
import com.sw.dualscreen.ext.format2String
|
||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
import com.sw.dualscreen.objbox.FoodModule
|
||||||
|
import com.sw.dualscreen.objbox.FoodModule.IdNameScore
|
||||||
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
||||||
import com.sw.dualscreen.sdk.SensorScaleUtils
|
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||||
|
import com.sw.dualscreen.utils.AssetsTool
|
||||||
|
import com.sw.dualscreen.utils.BitmapCropper
|
||||||
|
import com.sw.dualscreen.utils.BitmapSaver
|
||||||
import com.sw.dualscreen.utils.Debouncer
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
import com.sw.dualscreen.utils.GlideUtils
|
import com.sw.dualscreen.utils.GlideUtils
|
||||||
|
import com.sw.dualscreen.utils.GsonUtils
|
||||||
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
|
import com.sw.dualscreen.utils.SPUtil
|
||||||
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
|
import com.sw.plate.utils.LightManager
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务员显示界面
|
* 服务员显示界面
|
||||||
@@ -61,6 +76,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
private var lastWeight: Double = 0.0
|
private var lastWeight: Double = 0.0
|
||||||
private var lastPhotoUri: Uri? = null // 最后拍照的图片
|
private var lastPhotoUri: Uri? = null // 最后拍照的图片
|
||||||
private var debouncer = Debouncer(2000)
|
private var debouncer = Debouncer(2000)
|
||||||
|
private var isRecognitionFood = true
|
||||||
|
|
||||||
override fun getViewModel(): BaseViewModel {
|
override fun getViewModel(): BaseViewModel {
|
||||||
return viewModel
|
return viewModel
|
||||||
@@ -76,6 +92,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
setupSecondaryDisplay()
|
setupSecondaryDisplay()
|
||||||
setupCamera()
|
setupCamera()
|
||||||
initData()
|
initData()
|
||||||
|
FoodModule.init(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initData() {
|
private fun initData() {
|
||||||
@@ -85,6 +102,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
viewModel.activeEngine()
|
viewModel.activeEngine()
|
||||||
|
binding.foodDisplayLayout.setOnClickListener {
|
||||||
|
val intent = Intent(this, FoodCollectionActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
|
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
|
||||||
override fun getOutline(view: View, outline: Outline) {
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
|
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
|
||||||
@@ -105,7 +127,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
arrayOf("即放即取", "余量取餐")
|
arrayOf("即放即取", "余量取餐")
|
||||||
)
|
)
|
||||||
binding.spinner.adapter = spinnerAdapter
|
binding.spinner.adapter = spinnerAdapter
|
||||||
binding.spinner.setSelection(0)
|
val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||||
|
binding.spinner.setSelection(mode ?: 0)
|
||||||
|
presentation?.updateMealPickupMode(mode ?: 0)
|
||||||
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(
|
override fun onItemSelected(
|
||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
@@ -113,7 +137,12 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
position: Int,
|
position: Int,
|
||||||
id: Long
|
id: Long
|
||||||
) {
|
) {
|
||||||
|
SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, position)
|
||||||
|
updateCurrentFood(null)
|
||||||
|
isRecognitionFood = true
|
||||||
presentation?.updateMealPickupMode(position)
|
presentation?.updateMealPickupMode(position)
|
||||||
|
|
||||||
|
SensorScaleUtils.zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
@@ -132,6 +161,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
binding.tvFoodName.setOnClickListener {
|
binding.tvFoodName.setOnClickListener {
|
||||||
SensorScaleUtils.zero()
|
SensorScaleUtils.zero()
|
||||||
}
|
}
|
||||||
|
binding.tvRescan.setOnClickListener {
|
||||||
|
debouncer.debounce { recognizeFood() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCurrentFood(foodInfo: FoodInfo?) {
|
fun updateCurrentFood(foodInfo: FoodInfo?) {
|
||||||
@@ -165,6 +197,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
override fun registerDataChange() {
|
override fun registerDataChange() {
|
||||||
super.registerDataChange()
|
super.registerDataChange()
|
||||||
|
// lifecycleScope.launch {
|
||||||
|
// viewModel.identifiedFoodInfoList2.collect { list ->
|
||||||
|
// updateFoodInfo(list)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.identifiedFoodInfoList.collect { list ->
|
viewModel.identifiedFoodInfoList.collect { list ->
|
||||||
adapter.updateData(list)
|
adapter.updateData(list)
|
||||||
@@ -180,24 +217,94 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
Timber.d("registerDataChange weight = $weight")
|
Timber.d("registerDataChange weight = $weight")
|
||||||
presentation?.updateWeight(weight)
|
presentation?.updateWeight(weight)
|
||||||
|
|
||||||
if (weight - lastWeight > 0.1 && presentation?.mealPickupMode == 0) { // 大于100g
|
if (weight <= 0.005) {//余量取餐,检测到秤上没有东西,重新启动识别菜品 && presentation?.mealPickupMode == 1
|
||||||
debouncer.debounce {
|
isRecognitionFood = true
|
||||||
takePhoto { photoUri ->
|
|
||||||
lastPhotoUri = photoUri
|
|
||||||
viewModel.getIdentifiedFoodList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (weight - lastWeight > 0.05 && isRecognitionFood) { // 大于50g
|
||||||
|
if (presentation?.mealPickupMode == 1) {
|
||||||
|
isRecognitionFood = false
|
||||||
|
}
|
||||||
|
debouncer.debounce {
|
||||||
|
recognizeFood()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastWeight = weight
|
lastWeight = weight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recognizeFood() {
|
||||||
|
LightManager.openRedLight()
|
||||||
|
LightManager.openGreenLight()
|
||||||
|
takePhoto { photoUri ->
|
||||||
|
lastPhotoUri = photoUri
|
||||||
|
Timber.d("registerDataChange photoUri = ${photoUri.path}")
|
||||||
|
// viewModel.getIdentifiedFoodList()
|
||||||
|
|
||||||
|
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap ->
|
||||||
|
Timber.d("registerDataChange photoUri 拿到bitmap")
|
||||||
|
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
||||||
|
Timber.d("registerDataChange photoUri bitmap裁剪完成")
|
||||||
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
|
bitmap,
|
||||||
|
this,
|
||||||
|
"IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
|
)
|
||||||
|
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
||||||
|
//val nameList = FoodModule.queryFood(bitmap)
|
||||||
|
val scoreList = FoodModule.getFoodScoreList(bitmap)
|
||||||
|
Timber.d(
|
||||||
|
"registerDataChange photoUri 拿到识别数据:${
|
||||||
|
GsonUtils.toJson(
|
||||||
|
scoreList
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
val nameList = scoreList.map { it.name }
|
||||||
|
val foodName = nameList.joinToString(separator = ",")
|
||||||
|
|
||||||
|
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
|
||||||
|
if (TextUtils.isEmpty(foodName)) {
|
||||||
|
LightManager.closeGreenLight()
|
||||||
|
LightManager.closeRedLight()
|
||||||
|
binding.layoutRescan.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.layoutRescan.visibility = View.GONE
|
||||||
|
viewModel.getFoodInfo(foodName) { list ->
|
||||||
|
runOnUiThread {
|
||||||
|
updateFoodInfo(list, scoreList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFoodInfo(list: List<FoodInfo>, scoreList: List<IdNameScore>) {
|
||||||
|
Timber.d("registerDataChange识别后查询接口数据:${GsonUtils.toJson(list)}")
|
||||||
|
list.forEach { foodInfo ->
|
||||||
|
val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName }
|
||||||
|
val score = scoreItem?.score ?: 0.0
|
||||||
|
foodInfo.score = ((1 - score) * 10000).roundToInt()
|
||||||
|
}
|
||||||
|
adapter.updateData(list)
|
||||||
|
if (list.isNotEmpty()) {
|
||||||
|
checkedItem = list[0]
|
||||||
|
checkedItem!!.photoUri = lastPhotoUri
|
||||||
|
updateCurrentFood(checkedItem)
|
||||||
|
lastPhotoUri = null
|
||||||
|
LightManager.closeRedLight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
|
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
|
||||||
return GenericItemAdapter(
|
return GenericItemAdapter(
|
||||||
items = emptyList(),
|
items = emptyList(),
|
||||||
bindingInflater = ItemFoodInfoBinding::inflate,
|
bindingInflater = ItemFoodInfoBinding::inflate,
|
||||||
bindCallback = { item, position ->
|
bindCallback = { item, position ->
|
||||||
this.tvName.text = item.foodName
|
val score = (item.score / 100.0).format2String(2)
|
||||||
|
this.tvName.text = "${item.foodName}\n${score}%"
|
||||||
if (item.id != checkedItem?.id) {
|
if (item.id != checkedItem?.id) {
|
||||||
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
|
||||||
this.tvName.setTextColor(resources.getColor(R.color.identify_normal))
|
this.tvName.setTextColor(resources.getColor(R.color.identify_normal))
|
||||||
@@ -378,6 +485,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
)
|
)
|
||||||
presentation!!.setStepChangeCallback { step ->
|
presentation!!.setStepChangeCallback { step ->
|
||||||
if (step == 1) {
|
if (step == 1) {
|
||||||
|
Timber.d("setStepChangeCallback${step}")
|
||||||
updateCurrentFood(null)
|
updateCurrentFood(null)
|
||||||
bottomSheetDialog?.dismiss()
|
bottomSheetDialog?.dismiss()
|
||||||
}
|
}
|
||||||
@@ -388,6 +496,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearFoodList() {
|
||||||
|
adapter.updateData(listOf())
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
SensorScaleUtils.closeScale()
|
SensorScaleUtils.closeScale()
|
||||||
presentation?.dismiss()
|
presentation?.dismiss()
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.sw.dualscreen.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.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.databinding.ListItemCollectedDataBinding
|
||||||
|
import com.sw.dualscreen.databinding.ListItemFoodCollectionBinding
|
||||||
|
import com.sw.dualscreen.objbox.CollectedFoodBean
|
||||||
|
import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||||
|
|
||||||
|
class CollectedFoodAdapter (var list: MutableList<CollectedFoodBean>) :
|
||||||
|
BaseQuickAdapter<CollectedFoodBean, CollectedFoodAdapter.VH>(list) {
|
||||||
|
|
||||||
|
inner class VH(var binding: ListItemCollectedDataBinding) : QuickViewHolder(binding.root)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val binding = ListItemCollectedDataBinding.inflate(inflater, parent, false)
|
||||||
|
return VH(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodBean?) {
|
||||||
|
holder.binding.tvFoodName.text = item?.foodName
|
||||||
|
holder.binding.divider.run {
|
||||||
|
visibility = if (position == list.size - 1) View.GONE else View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.sw.dualscreen.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.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.databinding.ListItemFoodCollectionBinding
|
||||||
|
import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||||
|
|
||||||
|
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_camera256)
|
||||||
|
} else {
|
||||||
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
|
//setImageURI(it.imageUri)
|
||||||
|
setImageBitmap(it.bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.sw.dualscreen.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.View
|
||||||
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.ext.dp
|
||||||
|
import com.sw.dualscreen.ext.hideKeyboard
|
||||||
|
|
||||||
|
abstract class BaseDialog(
|
||||||
|
context: Context,
|
||||||
|
var defWidth: Int = 400.dp,
|
||||||
|
var defHeight: Int = 260.dp
|
||||||
|
) : Dialog(context, R.style.DialogTheme) {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val rootView = getRootView()
|
||||||
|
setContentView(rootView)
|
||||||
|
setCancelable(false)
|
||||||
|
setCanceledOnTouchOutside(false)
|
||||||
|
window?.apply {
|
||||||
|
setLayout(defWidth, defHeight)
|
||||||
|
setBackgroundDrawable(ColorDrawable())
|
||||||
|
setGravity(Gravity.CENTER)
|
||||||
|
}
|
||||||
|
rootView.setOnClickListener { it.hideKeyboard() }
|
||||||
|
initView()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun getRootView(): View
|
||||||
|
|
||||||
|
abstract fun initView()
|
||||||
|
|
||||||
|
// fun getReceiptActivity(): GoodsListActivity? {
|
||||||
|
// var ctx = context
|
||||||
|
// while (ctx is ContextWrapper) {
|
||||||
|
// ctx = ctx.baseContext
|
||||||
|
// if (ctx is GoodsListActivity) {
|
||||||
|
// return ctx
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.sw.dualscreen.dialog
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import com.sw.dualscreen.databinding.DialogWarnBinding
|
||||||
|
|
||||||
|
class WarnDialog(
|
||||||
|
context: Context,
|
||||||
|
var content:String,
|
||||||
|
var cancelBlock: () -> Unit = {},
|
||||||
|
var confirmBlock: () -> Unit = {},
|
||||||
|
) : BaseDialog(context = context) {
|
||||||
|
|
||||||
|
private lateinit var binding: DialogWarnBinding
|
||||||
|
override fun getRootView(): View {
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
binding = DialogWarnBinding.inflate(inflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initView() {
|
||||||
|
binding.tvWarnContent.text = content
|
||||||
|
binding.btnCancel.setOnClickListener {
|
||||||
|
cancelBlock()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
binding.btnConfirm.setOnClickListener {
|
||||||
|
confirmBlock()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sw.dualscreen.ext
|
package com.sw.dualscreen.ext
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
@@ -7,8 +8,17 @@ import android.graphics.ImageFormat
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.YuvImage
|
import android.graphics.YuvImage
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.EditText
|
||||||
import androidx.annotation.Dimension
|
import androidx.annotation.Dimension
|
||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +66,7 @@ val Float.sp: Float
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun Double.format2String(): String = "%.1f".format(this)
|
fun Double.format2String(): String = "%.1f".format(this)
|
||||||
|
fun Double.format2String(num:Int): String = "%.${num}f".format(this)
|
||||||
|
|
||||||
// 添加扩展函数
|
// 添加扩展函数
|
||||||
fun ImageProxy.toSafeBitmap(): Bitmap {
|
fun ImageProxy.toSafeBitmap(): Bitmap {
|
||||||
@@ -82,3 +93,30 @@ fun ImageProxy.toSafeBitmap(): Bitmap {
|
|||||||
val imageBytes = out.toByteArray()
|
val imageBytes = out.toByteArray()
|
||||||
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.hideKeyboard() {
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(this.windowToken, 0)
|
||||||
|
this.clearFocus() // 清除焦点避免键盘再次弹出
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EditText.addOnActionSearchListener(searchCallback: () -> Unit) {
|
||||||
|
setOnEditorActionListener { v, actionId, event ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||||
|
// 处理搜索逻辑
|
||||||
|
searchCallback()
|
||||||
|
return@setOnEditorActionListener true // 阻止事件继续传递
|
||||||
|
}
|
||||||
|
return@setOnEditorActionListener false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
|
||||||
|
var job: Job? = null
|
||||||
|
setOnClickListener {
|
||||||
|
job?.cancel()
|
||||||
|
job = CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
delay(delay)
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.sw.dualscreen.model;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IActivityResult {
|
||||||
|
|
||||||
|
interface LaunchActivityCallback {
|
||||||
|
void callback(ActivityResult result);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LaunchTackPictureCallback {
|
||||||
|
void callback(boolean result);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestPermissionCallback {
|
||||||
|
void callback(boolean result);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestMultiplePermissionsCallback {
|
||||||
|
void callback(Map<String,Boolean> result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,8 @@ data class FoodInfo(
|
|||||||
@SerializedName("stFoodInfoSetting")
|
@SerializedName("stFoodInfoSetting")
|
||||||
val stFoodInfoSetting: StFoodInfoSetting? = StFoodInfoSetting(),
|
val stFoodInfoSetting: StFoodInfoSetting? = StFoodInfoSetting(),
|
||||||
@SerializedName("stFoodInfoSpecificationList")
|
@SerializedName("stFoodInfoSpecificationList")
|
||||||
val stFoodInfoSpecificationList: List<StFoodInfoSpecification?>? = listOf()
|
val stFoodInfoSpecificationList: List<StFoodInfoSpecification?>? = listOf(),
|
||||||
|
var score: Int = 0
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class FoodTypeAndRealIntakeVo(
|
data class FoodTypeAndRealIntakeVo(
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ import com.sw.dualscreen.model.response.EquipmentInfo
|
|||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.model.response.UserFaceInfo
|
import com.sw.dualscreen.model.response.UserFaceInfo
|
||||||
import com.sw.dualscreen.model.response.UserNutritionData
|
import com.sw.dualscreen.model.response.UserNutritionData
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
import retrofit2.http.Multipart
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Part
|
||||||
|
import retrofit2.http.PartMap
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
|
||||||
@@ -99,4 +104,27 @@ interface ApiService {
|
|||||||
@Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/userEatFood/doubleBalance/face/stall",
|
@Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/userEatFood/doubleBalance/face/stall",
|
||||||
@Body param: List<UserNutritionParam>
|
@Body param: List<UserNutritionParam>
|
||||||
): ApiResponse<String>
|
): ApiResponse<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜品信息
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
suspend fun getFoodInfo(
|
||||||
|
@Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/getRestInfoFoodsByType/stall/v2",
|
||||||
|
@Query("restId") restId: String,
|
||||||
|
@Query("foodNames") foodName: String,
|
||||||
|
): ApiResponse<List<FoodInfo>>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交采集图片数据
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST
|
||||||
|
suspend fun postImageData(
|
||||||
|
@Url url: String = "http://192.168.1.201:14801/terminal/neglect/dishCollectionVectorData/add",
|
||||||
|
@PartMap params: Map<String, @JvmSuppressWildcards RequestBody>,
|
||||||
|
@Part image: MultipartBody.Part?
|
||||||
|
): ApiResponse<String>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.sw.dualscreen.objbox
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
data class FoodClassInfo(
|
||||||
|
var class_names: List<String>,
|
||||||
|
var class_to_idx: Map<String, Int>,
|
||||||
|
var idx_to_class: Map<String, String>
|
||||||
|
)
|
||||||
|
|
||||||
|
//data class FoodQueryResult(
|
||||||
|
// var id: Long,
|
||||||
|
// var name: String?,
|
||||||
|
// var foodIdx: Int,
|
||||||
|
// var score: Double
|
||||||
|
//)
|
||||||
|
|
||||||
|
data class FoodCollectionBean(
|
||||||
|
var imageUri: Uri? = null,
|
||||||
|
var bitmap: Bitmap? = null,
|
||||||
|
var imageVector: FloatArray? = null,
|
||||||
|
var isShowCamera: Boolean = false,
|
||||||
|
var isFinish:Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CollectedFoodBean(
|
||||||
|
var foodName: String?,
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.sw.dualscreen.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,241 @@
|
|||||||
|
package com.sw.dualscreen.objbox
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.SparseLongArray
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.sw.dualscreen.MyApp
|
||||||
|
import com.sw.dualscreen.utils.AssetsTool
|
||||||
|
import com.sw.dualscreen.utils.GsonUtils
|
||||||
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
|
import io.objectbox.Box
|
||||||
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import io.objectbox.query.Query
|
||||||
|
import org.pytorch.IValue
|
||||||
|
import org.pytorch.Module
|
||||||
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
object FoodModule {
|
||||||
|
|
||||||
|
private const val THRESHOLD = 0.8
|
||||||
|
private lateinit var module_mobile: Module
|
||||||
|
private lateinit var box: Box<Food>
|
||||||
|
private lateinit var embeddingsList: List<List<Float>>
|
||||||
|
private lateinit var labelsList: IntArray
|
||||||
|
private lateinit var classInfo: FoodClassInfo
|
||||||
|
val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
|
||||||
|
val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
|
||||||
|
val DEFAULT_FOOD_INDEX = -1
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
Thread {
|
||||||
|
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
|
||||||
|
box = ObjectBox.boxStore.boxFor(Food::class)
|
||||||
|
//if (box.all.isNotEmpty()) {
|
||||||
|
// box.removeAll()
|
||||||
|
//}
|
||||||
|
//if (box.all.isEmpty()) {
|
||||||
|
// initDefFoodData(context)
|
||||||
|
//}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uri2FloatArray(uri: Uri): FloatArray? {
|
||||||
|
return MyApp.instance?.let { context ->
|
||||||
|
ImageUtil.uriToBitmap(context, uri)?.let {
|
||||||
|
bitmap2FloatArray(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bitmap2FloatArray(bitmap: Bitmap): FloatArray {
|
||||||
|
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
|
||||||
|
bitmap,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryFood(uri: Uri, queryCount: Int = 15): List<String>? {
|
||||||
|
return uri2FloatArray(uri)?.let {
|
||||||
|
queryFood(it, queryCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回识别物品名称列表
|
||||||
|
*/
|
||||||
|
fun queryFood(bitmap: Bitmap, queryCount: Int = 15): List<String> {
|
||||||
|
val floatArray = bitmap2FloatArray(bitmap)
|
||||||
|
return queryFood(floatArray, queryCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回识别物品IdNameScore对象列表
|
||||||
|
*/
|
||||||
|
fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
||||||
|
val floatArray = bitmap2FloatArray(bitmap)
|
||||||
|
return queryFoodNameScore(floatArray, queryCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryFoodNameScore(floatArray: FloatArray, queryCount: Int = 15): List<IdNameScore> {
|
||||||
|
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}" }
|
||||||
|
val idScoreList = query.findIdsWithScores()
|
||||||
|
val nameScoreList = mutableListOf<IdNameScore>()
|
||||||
|
idScoreList.forEach {
|
||||||
|
nameScoreList.add(IdNameScore(id = it.id, name = box.get(it.id).name?:"", score = it.score))
|
||||||
|
}
|
||||||
|
Timber.tag("FoodModule").d("registerDataChange,queryFood数据:${GsonUtils.toJson(nameScoreList)}")
|
||||||
|
return nameScoreList
|
||||||
|
}
|
||||||
|
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
|
||||||
|
val floatArray = bitmap2FloatArray(bitmap)
|
||||||
|
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||||
|
val maxScoreList = nameScoreList
|
||||||
|
.filter { it.score < 1 - THRESHOLD }
|
||||||
|
.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)
|
||||||
|
})
|
||||||
|
//Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
||||||
|
return sortedScoreList
|
||||||
|
}
|
||||||
|
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
|
||||||
|
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}" }
|
||||||
|
val map = mutableMapOf<String, Int>()
|
||||||
|
query.findIdsWithScores().forEach {
|
||||||
|
val food = box.get(it.id)
|
||||||
|
Timber.d("${food.name}|${food.foodIdx}|${it.score}")
|
||||||
|
if (1 - it.score >= THRESHOLD) {
|
||||||
|
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
|
||||||
|
food.name?.let { key ->
|
||||||
|
val count = map[key] ?: 0
|
||||||
|
map.put(key, count + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val list = map.entries.sortedByDescending { it.value }.map { it.key }
|
||||||
|
return list
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
fun initDefFoodData(context: Context, action:()-> Unit={}) {
|
||||||
|
val count = box.all.count { it.foodIdx == 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")
|
||||||
|
|
||||||
|
embeddingsList =
|
||||||
|
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
|
||||||
|
labelsList = Gson().fromJson(labelsJson, IntArray::class.java)
|
||||||
|
classInfo =
|
||||||
|
Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
|
||||||
|
|
||||||
|
val foodMap = classInfo.idx_to_class
|
||||||
|
embeddingsList.forEachIndexed { index, floatList ->
|
||||||
|
val classIdx = labelsList[index]
|
||||||
|
val foodName = foodMap["$classIdx"]
|
||||||
|
val array = floatList.toFloatArray()
|
||||||
|
box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
|
||||||
|
}
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ,此方法的主要目的是:从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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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.sw.dualscreen.objbox
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import io.objectbox.BoxStore
|
||||||
|
import io.objectbox.BoxStoreBuilder
|
||||||
|
import io.objectbox.android.Admin
|
||||||
|
import io.objectbox.android.ObjectBoxLiveData
|
||||||
|
import io.objectbox.exception.DbException
|
||||||
|
import io.objectbox.exception.FileCorruptException
|
||||||
|
import io.objectbox.sync.Sync
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
import kotlin.also
|
||||||
|
import kotlin.io.copyTo
|
||||||
|
import kotlin.io.inputStream
|
||||||
|
import kotlin.io.outputStream
|
||||||
|
import kotlin.io.use
|
||||||
|
import kotlin.jvm.java
|
||||||
|
import kotlin.jvm.javaClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
lateinit var boxStore: BoxStore
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If building the [boxStore] failed, contains the thrown error message.
|
||||||
|
*/
|
||||||
|
var dbExceptionMessage: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
// On Android make sure to pass a Context when building the Store.
|
||||||
|
boxStore = try {
|
||||||
|
MyObjectBox.builder()
|
||||||
|
.androidContext(context.applicationContext)
|
||||||
|
.build()
|
||||||
|
} catch (e: DbException) {
|
||||||
|
if (e.javaClass.equals(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()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// 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
|
||||||
|
Admin(boxStore).start(context.applicationContext)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the database file is not in use, compresses (GZIP) and copies it to the given [target].
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
File(context.filesDir, "objectbox/$dbName/data.mdb").inputStream().use { input ->
|
||||||
|
target.parentFile?.mkdirs()
|
||||||
|
GZIPOutputStream(target.outputStream()).use { output ->
|
||||||
|
input.copyTo(output, DEFAULT_BUFFER_SIZE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -74,8 +74,9 @@ class SecondaryScreenPresentation(
|
|||||||
private lateinit var itemUserNutritionBinding: ItemUserNutritionBinding
|
private lateinit var itemUserNutritionBinding: ItemUserNutritionBinding
|
||||||
private lateinit var stepChangeCallback: (Int) -> Unit
|
private lateinit var stepChangeCallback: (Int) -> Unit
|
||||||
|
|
||||||
// 上一次的计算热量结果
|
|
||||||
private var lastWeight = 0.0
|
private var detectWeight = 0.0 //识别菜品时的重量
|
||||||
|
private var lastWeight = 0.0 // 上一次的计算热量结果
|
||||||
private val debouncer = Debouncer(500)
|
private val debouncer = Debouncer(500)
|
||||||
private var recognitionTime: Long = 0L // 人脸识别时的时间
|
private var recognitionTime: Long = 0L // 人脸识别时的时间
|
||||||
private var recognitionWeight: Double = 0.0 // 人脸识别时的重量
|
private var recognitionWeight: Double = 0.0 // 人脸识别时的重量
|
||||||
@@ -85,6 +86,8 @@ class SecondaryScreenPresentation(
|
|||||||
// 当前步骤
|
// 当前步骤
|
||||||
var currentStep: Int = 0
|
var currentStep: Int = 0
|
||||||
|
|
||||||
|
private var isGoStep1 = false;//防止持续调用
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -162,7 +165,7 @@ class SecondaryScreenPresentation(
|
|||||||
Timber.d("step1")
|
Timber.d("step1")
|
||||||
currentStep = 1
|
currentStep = 1
|
||||||
LightManager.closeGreenLight()
|
LightManager.closeGreenLight()
|
||||||
LightManager.openRedLight()
|
LightManager.closeRedLight()
|
||||||
//回复上一次的设置
|
//回复上一次的设置
|
||||||
// lastWeight = 0.0
|
// lastWeight = 0.0
|
||||||
dinnerTypeInfo = null
|
dinnerTypeInfo = null
|
||||||
@@ -180,16 +183,19 @@ class SecondaryScreenPresentation(
|
|||||||
pauseCamera()
|
pauseCamera()
|
||||||
binding.tvBottomTip.visibility = View.VISIBLE
|
binding.tvBottomTip.visibility = View.VISIBLE
|
||||||
itemUserNutritionBinding.clNutritionData.visibility = View.GONE
|
itemUserNutritionBinding.clNutritionData.visibility = View.GONE
|
||||||
|
|
||||||
|
activity.clearFoodList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示人脸识别
|
* 显示人脸识别
|
||||||
*/
|
*/
|
||||||
fun step2(foodInfo: FoodInfo) {
|
fun step2(foodInfo: FoodInfo) {
|
||||||
|
isGoStep1 = true
|
||||||
Timber.d("step2")
|
Timber.d("step2")
|
||||||
currentStep = 2
|
currentStep = 2
|
||||||
LightManager.openRedLight()
|
LightManager.closeRedLight()
|
||||||
LightManager.openGreenLight()
|
// LightManager.openGreenLight()
|
||||||
dinnerTypeInfo = null
|
dinnerTypeInfo = null
|
||||||
userNutritionData = null
|
userNutritionData = null
|
||||||
recognitionWeight = 0.0
|
recognitionWeight = 0.0
|
||||||
@@ -231,6 +237,7 @@ class SecondaryScreenPresentation(
|
|||||||
fun step3() {
|
fun step3() {
|
||||||
Timber.d("step3")
|
Timber.d("step3")
|
||||||
LightManager.closeRedLight()
|
LightManager.closeRedLight()
|
||||||
|
LightManager.openGreenLight()
|
||||||
currentStep = 3
|
currentStep = 3
|
||||||
stepChangeCallback(currentStep)
|
stepChangeCallback(currentStep)
|
||||||
if (currentFood == null) {
|
if (currentFood == null) {
|
||||||
@@ -270,10 +277,24 @@ class SecondaryScreenPresentation(
|
|||||||
fun updateWeight(weight: Double) {
|
fun updateWeight(weight: Double) {
|
||||||
lastWeight = weight * 1000 // 将千克转成克
|
lastWeight = weight * 1000 // 将千克转成克
|
||||||
Timber.d("updateWeight lastWeight = $weight")
|
Timber.d("updateWeight lastWeight = $weight")
|
||||||
|
if (currentStep == 2 &&
|
||||||
|
lastWeight <= 5 &&
|
||||||
|
isGoStep1
|
||||||
|
) {//人脸识别过程中,秤上物品拿走
|
||||||
|
isGoStep1 = false
|
||||||
|
activity.runOnUiThread {
|
||||||
|
Timber.e("切回step1")
|
||||||
|
step1()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (currentFood == null) {
|
if (currentFood == null) {
|
||||||
Timber.e("updateWeight currentFood is null")
|
Timber.e("updateWeight currentFood is null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (userNutritionData == null) {
|
if (userNutritionData == null) {
|
||||||
Timber.e("updateWeight userNutritionData is null")
|
Timber.e("updateWeight userNutritionData is null")
|
||||||
return
|
return
|
||||||
@@ -285,12 +306,17 @@ class SecondaryScreenPresentation(
|
|||||||
calculateNutrition(recognitionWeight - lastWeight)
|
calculateNutrition(recognitionWeight - lastWeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateDetectWeight(weight: Double) {
|
||||||
|
detectWeight = weight * 1000 // 将千克转成克
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 余量取餐
|
* 余量取餐
|
||||||
*/
|
*/
|
||||||
fun updateMealPickupMode(mode: Int) {
|
fun updateMealPickupMode(mode: Int) {
|
||||||
Timber.d("updateMealPickupMode mode = $mode")
|
Timber.d("updateMealPickupMode mode = $mode")
|
||||||
mealPickupMode = mode
|
mealPickupMode = mode
|
||||||
|
step1()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateNutrition(weight1: Double) {
|
private fun calculateNutrition(weight1: Double) {
|
||||||
@@ -758,7 +784,7 @@ class SecondaryScreenPresentation(
|
|||||||
* @param facePreviewInfoList RGB画面的实时人脸信息
|
* @param facePreviewInfoList RGB画面的实时人脸信息
|
||||||
*/
|
*/
|
||||||
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) {
|
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) {
|
||||||
Timber.d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
|
// Timber.d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
|
||||||
if (rgbFaceRectTransformer != null) {
|
if (rgbFaceRectTransformer != null) {
|
||||||
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo(
|
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo(
|
||||||
facePreviewInfoList,
|
facePreviewInfoList,
|
||||||
@@ -789,12 +815,18 @@ class SecondaryScreenPresentation(
|
|||||||
Timber.d("postUserData userNutritionData = ${userNutritionData == null}, currentFood = ${currentFood == null}")
|
Timber.d("postUserData userNutritionData = ${userNutritionData == null}, currentFood = ${currentFood == null}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var eatWeight = 0.0
|
||||||
|
if (mealPickupMode == 0) {//即放即取
|
||||||
|
eatWeight = recognitionWeight - 0
|
||||||
|
} else {
|
||||||
|
eatWeight = recognitionWeight - lastWeight
|
||||||
|
}
|
||||||
val userNutritionParam = UserNutritionParam(
|
val userNutritionParam = UserNutritionParam(
|
||||||
userId = userNutritionData?.userId!!,
|
userId = userNutritionData?.userId!!,
|
||||||
foodId = currentFood?.id!!,
|
foodId = currentFood?.id!!,
|
||||||
faceTime = recognitionTime,
|
faceTime = recognitionTime,
|
||||||
faceEndTime = System.currentTimeMillis(),
|
faceEndTime = System.currentTimeMillis(),
|
||||||
eatWeight = recognitionWeight - lastWeight,
|
eatWeight = eatWeight,//lastWeight
|
||||||
foodWeight = lastWeight
|
foodWeight = lastWeight
|
||||||
)
|
)
|
||||||
viewModel.postUserNutritionData(listOf<UserNutritionParam>(userNutritionParam))
|
viewModel.postUserNutritionData(listOf<UserNutritionParam>(userNutritionParam))
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.sw.dualscreen.repository
|
package com.sw.dualscreen.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.model.request.UserNutritionParam
|
import com.sw.dualscreen.model.request.UserNutritionParam
|
||||||
import com.sw.dualscreen.model.response.ApiResponse
|
import com.sw.dualscreen.model.response.ApiResponse
|
||||||
@@ -9,6 +11,9 @@ import com.sw.dualscreen.model.response.FoodInfo
|
|||||||
import com.sw.dualscreen.model.response.UserFaceInfo
|
import com.sw.dualscreen.model.response.UserFaceInfo
|
||||||
import com.sw.dualscreen.model.response.UserNutritionData
|
import com.sw.dualscreen.model.response.UserNutritionData
|
||||||
import com.sw.dualscreen.network.api.ApiService
|
import com.sw.dualscreen.network.api.ApiService
|
||||||
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 远程数据处理
|
* 远程数据处理
|
||||||
@@ -103,4 +108,39 @@ class RemoteRepository constructor(
|
|||||||
): ApiResponse<String> {
|
): ApiResponse<String> {
|
||||||
return safeApiCall { apiService.postUserNutritionData(param = param) }
|
return safeApiCall { apiService.postUserNutritionData(param = param) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜品信息
|
||||||
|
* @param restId 从device服务获取的canteenId字段
|
||||||
|
* @param foodName 菜品名称,多个使用逗号拼接
|
||||||
|
*/
|
||||||
|
suspend fun getFoodInfo(
|
||||||
|
restId: String = GlobalData.restId,
|
||||||
|
foodName: String,
|
||||||
|
): ApiResponse<List<FoodInfo>> {
|
||||||
|
return safeApiCall { apiService.getFoodInfo(restId = restId, foodName = foodName) }
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取菜品信息
|
||||||
|
* @param restId 从device服务获取的canteenId字段
|
||||||
|
* @param foodName 菜品名称,多个使用逗号拼接
|
||||||
|
*/
|
||||||
|
suspend fun postImageData(
|
||||||
|
context: Context,
|
||||||
|
restId: String = GlobalData.restId,
|
||||||
|
foodId: String,
|
||||||
|
foodName: String,
|
||||||
|
foodVector: String,
|
||||||
|
uri: Uri,
|
||||||
|
): ApiResponse<String> {
|
||||||
|
|
||||||
|
val params = HashMap<String, RequestBody>()
|
||||||
|
params["placeId"] = restId.toRequestBody()
|
||||||
|
params["foodId"] = foodId.toRequestBody()
|
||||||
|
params["foodName"] = foodName.toRequestBody()
|
||||||
|
params["foodVector"] = foodVector.toRequestBody()
|
||||||
|
val imagePart = ImageUtil.uriToMultipart(context, uri, "foodPic")
|
||||||
|
|
||||||
|
return safeApiCall { apiService.postImageData(params = params, image = imagePart) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ object SensorScaleUtils {
|
|||||||
private var mSensorScale: SensorScale? = null
|
private var mSensorScale: SensorScale? = null
|
||||||
private var isOpened: Boolean = false
|
private var isOpened: Boolean = false
|
||||||
private var isRead: Boolean = false
|
private var isRead: Boolean = false
|
||||||
private var isZero = false
|
var isZero = false
|
||||||
private var callback: Callback? = {}
|
private var callback: Callback? = {}
|
||||||
private var lastWeight: Double? = null
|
private var lastWeight: Double? = null
|
||||||
|
|
||||||
@@ -118,8 +118,8 @@ object SensorScaleUtils {
|
|||||||
fun zero() {
|
fun zero() {
|
||||||
Timber.d("zero isOpened = $isOpened, mSensorScale = $mSensorScale")
|
Timber.d("zero isOpened = $isOpened, mSensorScale = $mSensorScale")
|
||||||
if (!isOpened) return
|
if (!isOpened) return
|
||||||
// isZero = true
|
|
||||||
mSensorScale?.zero {
|
mSensorScale?.zero {
|
||||||
|
isZero = true
|
||||||
Timber.d("zero 零位标定操作成功")
|
Timber.d("zero 零位标定操作成功")
|
||||||
ToastUtils.showToast("零位标定操作成功")
|
ToastUtils.showToast("零位标定操作成功")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.sw.dualscreen.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,38 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
|
||||||
|
object BitmapCropper {
|
||||||
|
/**
|
||||||
|
* 裁剪Bitmap中心区域为指定尺寸
|
||||||
|
* @param original 原始Bitmap
|
||||||
|
* @param targetWidth 目标宽度
|
||||||
|
* @param targetHeight 目标高度
|
||||||
|
* @return 裁剪后的Bitmap
|
||||||
|
*/
|
||||||
|
fun cropCenter(original: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
|
||||||
|
val originalWidth = original.width
|
||||||
|
val originalHeight = original.height
|
||||||
|
|
||||||
|
// 计算中心点坐标
|
||||||
|
var startX = (originalWidth - targetWidth) / 2
|
||||||
|
var startY = (originalHeight - targetHeight) / 2
|
||||||
|
|
||||||
|
// 边界检查
|
||||||
|
startX = startX.coerceAtLeast(0)
|
||||||
|
startY = startY.coerceAtLeast(0)
|
||||||
|
val actualWidth = minOf(targetWidth, originalWidth - startX)
|
||||||
|
val actualHeight = minOf(targetHeight, originalHeight - startY)
|
||||||
|
|
||||||
|
return Bitmap.createBitmap(original, startX, startY, actualWidth, actualHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
fun main() {
|
||||||
|
// 假设这是从资源加载的Bitmap
|
||||||
|
val originalBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888)
|
||||||
|
|
||||||
|
// 裁剪中心500x500区域
|
||||||
|
val croppedBitmap = BitmapCropper.cropCenter(originalBitmap, 500, 500)
|
||||||
|
println("裁剪后尺寸:${croppedBitmap.width}x${croppedBitmap.height}")
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.sw.dualscreen.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,68 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.ActivityResultCaller
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简洁相机工具类
|
||||||
|
* 调用系统相机拍照并返回照片路径
|
||||||
|
*/
|
||||||
|
class CameraHelper(
|
||||||
|
private val context: Context,
|
||||||
|
caller: ActivityResultCaller,
|
||||||
|
private val authority: String,
|
||||||
|
private val onResult: (uri: Uri, filePath: String?) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
private lateinit var imageUri: Uri
|
||||||
|
private val takePictureLauncher: ActivityResultLauncher<Uri>
|
||||||
|
|
||||||
|
init {
|
||||||
|
takePictureLauncher =
|
||||||
|
caller.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
||||||
|
if (success) {
|
||||||
|
val path = getRealPathFromUri(context, imageUri)
|
||||||
|
onResult(imageUri, path)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "拍照取消", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开系统相机 */
|
||||||
|
fun openCamera() {
|
||||||
|
// 1. 创建存储图片的文件
|
||||||
|
val photoFile = File(context.externalCacheDir, "IMG_${System.currentTimeMillis()}.jpg")
|
||||||
|
photoFile.parentFile?.mkdirs() // 确保目录存在
|
||||||
|
|
||||||
|
// 2. 生成 content:// Uri
|
||||||
|
imageUri = FileProvider.getUriForFile(context, authority, photoFile)
|
||||||
|
|
||||||
|
// 3. 启动相机
|
||||||
|
takePictureLauncher.launch(imageUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 可选:将 content:// 转为实际路径 */
|
||||||
|
private fun getRealPathFromUri(context: Context, uri: Uri): String? {
|
||||||
|
var filePath: String? = null
|
||||||
|
context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
|
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
cursor.moveToFirst()
|
||||||
|
val fileName = cursor.getString(nameIndex)
|
||||||
|
val file = File(context.cacheDir, fileName)
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
|
FileOutputStream(file).use { output -> input.copyTo(output) }
|
||||||
|
}
|
||||||
|
filePath = file.absolutePath
|
||||||
|
}
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.sw.dualscreen.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 isCameraReady = false
|
||||||
|
fun takePhoto(callback: (Uri) -> Unit) {
|
||||||
|
cameraController?.let {
|
||||||
|
if (photoCaptureHelper == null) {
|
||||||
|
initCaptureHelper()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
photoCaptureHelper?.let {
|
||||||
|
it.addSuccessCallback(callback)
|
||||||
|
it.bindCameraCallback {
|
||||||
|
bind()
|
||||||
|
}
|
||||||
|
it.takePhoto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initCaptureHelper() {
|
||||||
|
photoCaptureHelper = PhotoCaptureHelper(
|
||||||
|
context = activity,
|
||||||
|
cameraController = cameraController!!,
|
||||||
|
onSuccess = {},
|
||||||
|
onError = { msg ->
|
||||||
|
//toast(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,47 @@
|
|||||||
|
package com.example.utils
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具类:float[] 与 Base64 字符串互转
|
||||||
|
* 无需外部依赖,适用于 Android 原生项目。
|
||||||
|
*/
|
||||||
|
object FloatBase64Utils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* float数组 → Base64字符串
|
||||||
|
* @param values FloatArray
|
||||||
|
* @return Base64字符串(无换行)
|
||||||
|
*/
|
||||||
|
fun floatArrayToBase64(values: FloatArray): String {
|
||||||
|
if (values.isEmpty()) return ""
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(values.size * 4)
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN) // 默认大端序,可改 LITTLE_ENDIAN
|
||||||
|
values.forEach { buffer.putFloat(it) }
|
||||||
|
|
||||||
|
val bytes = buffer.array()
|
||||||
|
return Base64.encodeToString(bytes, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64字符串 → float数组
|
||||||
|
* @param base64 Base64字符串
|
||||||
|
* @return FloatArray
|
||||||
|
*/
|
||||||
|
fun base64ToFloatArray(base64: String?): FloatArray {
|
||||||
|
if (base64.isNullOrEmpty()) return FloatArray(0)
|
||||||
|
|
||||||
|
val bytes = Base64.decode(base64, Base64.NO_WRAP)
|
||||||
|
val buffer = ByteBuffer.wrap(bytes)
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN)
|
||||||
|
|
||||||
|
val result = FloatArray(bytes.size / 4)
|
||||||
|
for (i in result.indices) {
|
||||||
|
result[i] = buffer.getFloat()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.sw.dualscreen.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 {
|
||||||
|
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,92 @@
|
|||||||
|
package com.sw.dualscreen.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 timber.log.Timber
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import com.sw.dualscreen.activity.BaseActivity
|
||||||
|
|
||||||
|
object StorageUtils {
|
||||||
|
|
||||||
|
fun requestPermission(activity: BaseActivity<*>, callback: () -> Unit) {
|
||||||
|
activity.requestSinglePermissionResult(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
// activity.requestSinglePermissionResult(Manifest.permission.CAMERA) {
|
||||||
|
//// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
//// if (Environment.isExternalStorageManager()) {
|
||||||
|
//// callback()
|
||||||
|
//// } else {
|
||||||
|
//// val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
////// activity.startActivityForResult(intent, MANAGE_REQUEST_CODE)
|
||||||
|
//// activity.launchActivityResult(intent) {
|
||||||
|
//// callback()
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//// } else {
|
||||||
|
//
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestCameraPermission(activity: BaseActivity<*>, callback: () -> Unit) {
|
||||||
|
activity.requestSinglePermissionResult(Manifest.permission.CAMERA) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,7 +52,10 @@ abstract class BaseViewModel() : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
|
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
|
||||||
if (response.isSuccess()) {
|
if (response.isSuccess()
|
||||||
|
|| response.code == 200
|
||||||
|
|| response.code == 0
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
Timber.d("msg = ${response.message}, code = ${response.code}")
|
Timber.d("msg = ${response.message}, code = ${response.code}")
|
||||||
@@ -62,6 +65,7 @@ abstract class BaseViewModel() : ViewModel() {
|
|||||||
|
|
||||||
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
|
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
|
||||||
GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
|
GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
|
||||||
|
GlobalData.appBaseUrl="http://192.168.1.210/gateway/local"
|
||||||
GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
|
GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
|
||||||
GlobalData.appId = equipmentInfo.arcsoftAppId!!
|
GlobalData.appId = equipmentInfo.arcsoftAppId!!
|
||||||
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
|
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.sw.dualscreen.viewmodel
|
package com.sw.dualscreen.viewmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.arcsoft.face.ErrorInfo
|
import com.arcsoft.face.ErrorInfo
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
@@ -21,6 +23,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import kotlin.math.atan
|
||||||
|
|
||||||
class UserViewModel : BaseViewModel() {
|
class UserViewModel : BaseViewModel() {
|
||||||
|
|
||||||
@@ -48,7 +51,9 @@ class UserViewModel : BaseViewModel() {
|
|||||||
* 人脸加载完成
|
* 人脸加载完成
|
||||||
*/
|
*/
|
||||||
private val _loadFaceResult = MutableStateFlow<Boolean>(false)
|
private val _loadFaceResult = MutableStateFlow<Boolean>(false)
|
||||||
val loadFaceResult : StateFlow<Boolean> = _loadFaceResult
|
val loadFaceResult: StateFlow<Boolean> = _loadFaceResult
|
||||||
|
private val _identifiedFoodInfoList2 = MutableStateFlow<List<FoodInfo>>(emptyList())
|
||||||
|
val identifiedFoodInfoList2: StateFlow<List<FoodInfo>> = _identifiedFoodInfoList2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取token
|
* 获取token
|
||||||
@@ -89,7 +94,7 @@ class UserViewModel : BaseViewModel() {
|
|||||||
val nextPageIndex = response.result?.nextPageIndex ?: -1
|
val nextPageIndex = response.result?.nextPageIndex ?: -1
|
||||||
if (nextPageIndex > 0) {
|
if (nextPageIndex > 0) {
|
||||||
getUserFaceCache(nextPageIndex)
|
getUserFaceCache(nextPageIndex)
|
||||||
} else{
|
} else {
|
||||||
_loadFaceResult.value = true
|
_loadFaceResult.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,4 +210,35 @@ class UserViewModel : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFoodInfo(foodName: String, action:(List<FoodInfo>)-> Unit = {}) {
|
||||||
|
Timber.d("getFoodInfo")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.getFoodInfo(foodName = foodName)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
val list = response.result ?: emptyList()
|
||||||
|
_identifiedFoodInfoList2.value = list
|
||||||
|
action(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postImageData(
|
||||||
|
context: Context,
|
||||||
|
foodId: String,
|
||||||
|
foodName: String,
|
||||||
|
foodVector: String,
|
||||||
|
uri: Uri
|
||||||
|
) {
|
||||||
|
Timber.d("postImageData")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.postImageData(
|
||||||
|
context, foodId = foodId, foodName = foodName, foodVector = foodVector,
|
||||||
|
uri = uri
|
||||||
|
)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
Timber.tag("mzf").d("code=${response.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="#FF3232" android:state_checked="true"/>
|
||||||
|
<item android:color="#5E7585" android:state_checked="false"/>
|
||||||
|
</selector>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="#30000000">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#FF0032C8" />
|
||||||
|
<corners android:radius="10dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -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="#ffffff"/>
|
||||||
|
<stroke android:color="#cdcdcd" android:width="1dp"/>
|
||||||
|
<corners android:radius="5dp"/>
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?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="8dp"/>
|
||||||
|
<solid android:color="#F4FAFF"/>
|
||||||
|
<stroke android:color="@color/white" android:width="2dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<layer-list>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<solid android:color="#FFF0F0"/>
|
||||||
|
<stroke android:color="#FF3232" android:width="2dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:width="32dp" android:height="32dp" android:gravity="bottom|end">
|
||||||
|
<bitmap android:src="@drawable/check_red"/>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="12dp"
|
||||||
|
android:bottomRightRadius="12dp"
|
||||||
|
android:topLeftRadius="12dp"
|
||||||
|
android:topRightRadius="12dp" />
|
||||||
|
<stroke
|
||||||
|
android:color="#ffff9900"
|
||||||
|
android:width="2dp" />
|
||||||
|
</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="@color/white"/>
|
||||||
|
<corners android:radius="12dp"/>
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="#30000000">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white"/>
|
||||||
|
<stroke android:color="#FF0032C8" android:width="1dp"/>
|
||||||
|
<corners android:radius="10dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -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" >
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_cash_normal" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_cash_checked" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</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" >
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_face_normal" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_face_checked" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</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" >
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_number_normal" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_number_checked" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</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" >
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_qrcode_normal" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<layer-list>
|
||||||
|
<item android:width="60dp" android:height="60dp">
|
||||||
|
<bitmap android:src="@drawable/pay_qrcode_checked" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?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:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="88dp"
|
||||||
|
android:layout_marginTop="24dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivBack"
|
||||||
|
android:layout_width="68dp"
|
||||||
|
android:layout_height="68dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:src="@drawable/ic_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="已采集菜品"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flSearchBlock"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:background="@drawable/bg_gray">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etInputGoods"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#00000000"
|
||||||
|
android:gravity="center"
|
||||||
|
android:hint="输入菜品名称"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="80dp"
|
||||||
|
android:textColor="#FF666666"
|
||||||
|
android:textColorHint="#FFC7C8DC"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:inputType="text"
|
||||||
|
android:imeOptions="actionSearch" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivGoodsSearch"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_gravity="end|center_vertical"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:padding="14dp"
|
||||||
|
android:src="@drawable/ic_goods_search" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- <com.scwang.smart.refresh.layout.SmartRefreshLayout-->
|
||||||
|
<!-- android:id="@+id/refreshLayout"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="0dp"-->
|
||||||
|
<!-- android:layout_marginHorizontal="130dp"-->
|
||||||
|
<!-- android:layout_marginTop="40dp"-->
|
||||||
|
<!-- android:layout_marginBottom="58dp"-->
|
||||||
|
<!-- app:layout_constraintBottom_toTopOf="@id/dividerLine"-->
|
||||||
|
<!-- app:layout_constraintTop_toBottomOf="@id/flSearchBlock">-->
|
||||||
|
|
||||||
|
<!-- <com.scwang.smart.refresh.header.ClassicsHeader-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content" />-->
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvFoodList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:itemCount="8"
|
||||||
|
tools:listitem="@layout/list_item_collected_data" />
|
||||||
|
|
||||||
|
<!-- <com.scwang.smart.refresh.footer.ClassicsFooter-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content" />-->
|
||||||
|
<!-- </com.scwang.smart.refresh.layout.SmartRefreshLayout>-->
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="88dp"
|
||||||
|
android:layout_marginTop="24dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivBack"
|
||||||
|
android:layout_width="68dp"
|
||||||
|
android:layout_height="68dp"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:src="@drawable/ic_back" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="菜品采集"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="24dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flPreview"
|
||||||
|
android:layout_width="180dp"
|
||||||
|
android:layout_height="180dp"
|
||||||
|
android:layout_marginStart="24dp">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/flCameraPreview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:cardElevation="0dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llCameraFlag"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_camera256"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="图片采集"
|
||||||
|
android:textColor="#ffb4b4c8"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnTakePhoto"
|
||||||
|
android:layout_width="140dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="开始拍照"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnClearData"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="清除"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnCollectedGoods"
|
||||||
|
android:layout_width="220dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="已采集菜品"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvFoodCollection"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="3dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="3"
|
||||||
|
tools:itemCount="3"
|
||||||
|
tools:listitem="@layout/list_item_food_collection" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="66dp"
|
||||||
|
android:layout_marginLeft="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/editFoodName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_gray"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="输入菜品名称"
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
|
android:paddingStart="15dp"
|
||||||
|
android:paddingEnd="15dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnFoodSearch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:text="菜品检索"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSave"
|
||||||
|
android:layout_width="260dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginVertical="24dp"
|
||||||
|
android:text="保存"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -85,6 +85,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_alignParentBottom="true" />
|
android:layout_alignParentBottom="true"
|
||||||
|
tools:src="@mipmap/ic_launcher"/>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinner"
|
android:id="@+id/spinner"
|
||||||
|
style="@style/customSpinnerStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/customSpinnerStyle"
|
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
android:popupBackground="@drawable/shape_for_custom_spinner" />
|
android:popupBackground="@drawable/shape_for_custom_spinner" />
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
android:text="羊肉臊子荞面饸饹" />
|
android:text="羊肉臊子荞面饸饹" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/foodDisplayLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="531dp"
|
android:layout_height="531dp"
|
||||||
android:layout_marginHorizontal="60dp"
|
android:layout_marginHorizontal="60dp"
|
||||||
@@ -77,6 +78,35 @@
|
|||||||
android:layout_marginHorizontal="60dp"
|
android:layout_marginHorizontal="60dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_rescan"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="140dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未识别出菜品,请重试或手动搜索"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_rescan"
|
||||||
|
android:layout_width="180dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:background="@drawable/bg_rescan"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="重试"
|
||||||
|
android:textColor="#ffff9900"
|
||||||
|
android:textSize="32sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_to_search"
|
android:id="@+id/tv_to_search"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<?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:background="#E7EFF8"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="167dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:layout_marginBottom="64dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||||
|
android:id="@+id/btnPayQrCode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_pay_icon"
|
||||||
|
android:checked="true"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:text="在线支付"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/color_pay"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:drawableTopCompat="@drawable/pay_qrcode" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||||
|
android:id="@+id/btnPayCash"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_pay_icon"
|
||||||
|
android:checked="false"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:text="现金"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/color_pay"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:drawableTopCompat="@drawable/pay_cash" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||||
|
android:id="@+id/btnPayNumber"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_pay_icon"
|
||||||
|
android:checked="false"
|
||||||
|
android:drawablePadding="11dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:text="消费码"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/color_pay"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:drawableTopCompat="@drawable/pay_number" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||||
|
android:id="@+id/btnPayFace"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_pay_icon"
|
||||||
|
android:checked="false"
|
||||||
|
android:drawablePadding="11dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:paddingTop="30dp"
|
||||||
|
android:text="刷脸"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/color_pay"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:drawableTopCompat="@drawable/pay_face" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="400dp"
|
||||||
|
android:layout_height="260dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/bg_white_radius12"
|
||||||
|
tools:ignore="HardcodedText">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:src="@drawable/ic_tip_warn"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvWarnContent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="30dp"
|
||||||
|
android:layout_marginVertical="30dp"
|
||||||
|
android:gravity="center"
|
||||||
|
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?"
|
||||||
|
android:textColor="#ff141428"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnCancel"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:background="@drawable/bg_white_stroke_blue_ripple"
|
||||||
|
android:text="取消"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FF0033CC"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnConfirm"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:background="@drawable/bg_blue_ripple"
|
||||||
|
android:text="确认"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -12,9 +12,10 @@
|
|||||||
android:id="@+id/tv_name"
|
android:id="@+id/tv_name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="肉沫干拌面"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="肉沫干拌面"
|
||||||
android:textColor="#ff031127"
|
android:textColor="#ff031127"
|
||||||
android:textSize="36sp" />
|
android:textSize="36sp" />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<androidx.camera.view.PreviewView
|
||||||
|
android:id="@+id/previewView"
|
||||||
|
android:layout_width="180dp"
|
||||||
|
android:layout_height="180dp"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
</merge>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:ignore="HardcodedText">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:src="@drawable/ic_empty_view"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
android:layout_marginBottom="39dp"
|
||||||
|
android:text="暂无该物品"
|
||||||
|
android:textColor="#ff000000"
|
||||||
|
android:textSize="30sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvContent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="请检查物品名称,或稍后重新搜索"
|
||||||
|
android:textColor="#ff999999"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFoodName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/ivDeleteFood"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="#ff666666"
|
||||||
|
android:textSize="26sp"
|
||||||
|
tools:text="高杆白菜" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivDeleteFood"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:paddingHorizontal="25dp"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginHorizontal="5dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvFoodName"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/tvFoodName"
|
||||||
|
android:src="@drawable/ic_close" />
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginHorizontal="30dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvFoodName"
|
||||||
|
app:dividerColor="#FFE6E6E6"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:layout_margin="3dp"
|
||||||
|
android:background="@drawable/bg_gray">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="center"
|
||||||
|
tools:src="@drawable/ic_camera256" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivDelete"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:src="@drawable/ic_delete_red" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivFinish"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:src="@drawable/ic_finish" />
|
||||||
|
</FrameLayout>
|
||||||
@@ -130,4 +130,23 @@
|
|||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="DialogTheme" parent="@android:style/Theme.Dialog">
|
||||||
|
<!-- 边框 -->
|
||||||
|
<item name="android:windowFrame">@null</item>
|
||||||
|
<!-- 是否浮现在activity之上 -->
|
||||||
|
<item name="android:windowIsFloating">true</item>
|
||||||
|
<!-- 半透明 -->
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<!-- 无标题 -->
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:background">@android:color/transparent</item>
|
||||||
|
<!-- 背景透明 -->
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<!-- 模糊 -->
|
||||||
|
<item name="android:backgroundDimEnabled">true</item>
|
||||||
|
<!-- 遮罩层 -->
|
||||||
|
<item name="android:backgroundDimAmount">0.5</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-cache-path
|
||||||
|
name="images"
|
||||||
|
path="pictures/" />
|
||||||
|
<external-cache-path
|
||||||
|
name="cache"
|
||||||
|
path="." />
|
||||||
|
|
||||||
|
</paths>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<!-- <domain-config cleartextTrafficPermitted="true">-->
|
||||||
|
<!-- <domain includeSubdomains="true">vip.shuziweidao.com</domain>-->
|
||||||
|
<!-- <domain includeSubdomains="true">192.168.1.207</domain>-->
|
||||||
|
<!-- <domain includeSubdomains="true">device.shuziweidao.com</domain>-->
|
||||||
|
<!-- </domain-config>-->
|
||||||
|
<base-config cleartextTrafficPermitted="true" />
|
||||||
|
</network-security-config>
|
||||||
@@ -1,20 +1,52 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
//plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
// alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
// alias(libs.plugins.kotlin.android) apply false
|
||||||
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
|
// id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
|
||||||
id("com.google.dagger.hilt.android") version "2.56.2" apply false
|
// id("com.google.dagger.hilt.android") version "2.56.2" apply false
|
||||||
alias(libs.plugins.android.library) apply false
|
// alias(libs.plugins.android.library) apply false
|
||||||
}
|
//}
|
||||||
|
//plugins {
|
||||||
|
// // Plugin to help us find updated dependencies, not required to use ObjectBox
|
||||||
|
// // https://github.com/ben-manes/gradle-versions-plugin/releases
|
||||||
|
// id("com.github.ben-manes.versions") version "0.52.0"
|
||||||
|
//}
|
||||||
|
// 根目录build.gradle.kts
|
||||||
buildscript {
|
buildscript {
|
||||||
|
val objectboxVersion by extra("5.0.1")
|
||||||
|
dependencies {
|
||||||
|
// classpath("com.android.tools.build:gradle:8.1.0")
|
||||||
|
classpath("com.android.tools.build:gradle:8.10.1")
|
||||||
|
// classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
|
||||||
|
classpath("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.0.21-1.0.27")
|
||||||
|
classpath("com.google.dagger:hilt-android-gradle-plugin:2.56.2")
|
||||||
|
classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion")
|
||||||
|
}
|
||||||
repositories {
|
repositories {
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/public/") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/central") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/google") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/jcenter") }
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://objectbox.net/beta-repo/") // ✅ 仍需声明
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath("com.android.tools.build:gradle:8.10.1")
|
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
|
|
||||||
classpath("io.objectbox:objectbox-gradle-plugin:5.0.1") // ✅ ObjectBox 插件
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/public/") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/central") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/google") }
|
||||||
|
maven { url = uri("https://maven.aliyun.com/repository/jcenter") }
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.wrapper {
|
||||||
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ fragmentKtx = "1.5.6"
|
|||||||
lifecycleViewmodelKtx = "2.8.3"
|
lifecycleViewmodelKtx = "2.8.3"
|
||||||
lifecycleRuntimeKtx = "2.8.3"
|
lifecycleRuntimeKtx = "2.8.3"
|
||||||
|
|
||||||
pytorch="1.13.0"
|
pytorch = "1.13.0"
|
||||||
|
|
||||||
|
recyclerview = "1.3.2"
|
||||||
|
|
||||||
|
#baseQuickAdapter = "4.1.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
|
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
|
||||||
@@ -64,8 +68,12 @@ androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref
|
|||||||
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
||||||
|
|
||||||
pytorch-android={ group = "org.pytorch", name = "pytorch_android", version.ref = "pytorch" }
|
pytorch-android = { group = "org.pytorch", name = "pytorch_android", version.ref = "pytorch" }
|
||||||
pytorch-android-torchvision={ group = "org.pytorch", name = "pytorch_android_torchvision", version.ref = "pytorch" }
|
pytorch-android-torchvision = { group = "org.pytorch", name = "pytorch_android_torchvision", version.ref = "pytorch" }
|
||||||
|
|
||||||
|
#base-quick-adapter-ktx = { module = "io.github.cymchad:BaseRecyclerViewAdapterHelper4", version.ref = "baseQuickAdapter" }
|
||||||
|
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||||
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
|
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
|
||||||
|
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.library)
|
// alias(libs.plugins.android.library)
|
||||||
|
id("com.android.library")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -64,4 +65,8 @@ dependencies {
|
|||||||
val glideVersion = "4.12.0"
|
val glideVersion = "4.12.0"
|
||||||
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
||||||
annotationProcessor("com.github.bumptech.glide:compiler:$glideVersion")
|
annotationProcessor("com.github.bumptech.glide:compiler:$glideVersion")
|
||||||
|
|
||||||
|
val objectboxVersion = "5.0.1"
|
||||||
|
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
||||||
|
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ public class FaceRectTransformer {
|
|||||||
rect.bottom *= verticalRatio;
|
rect.bottom *= verticalRatio;
|
||||||
|
|
||||||
Rect newRect = new Rect();
|
Rect newRect = new Rect();
|
||||||
L.e("cameraDisplayOrientation " + cameraDisplayOrientation + " === " + cameraId);
|
// L.e("cameraDisplayOrientation " + cameraDisplayOrientation + " === " + cameraId);
|
||||||
switch (cameraDisplayOrientation) {
|
switch (cameraDisplayOrientation) {
|
||||||
case 0:
|
case 0:
|
||||||
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
pluginManagement {
|
//pluginManagement {
|
||||||
repositories {
|
// repositories {
|
||||||
maven {
|
// maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/public/")
|
// url = uri("https://maven.aliyun.com/repository/public/")
|
||||||
}
|
// }
|
||||||
google {
|
// google {
|
||||||
content {
|
// content {
|
||||||
includeGroupByRegex("com\\.android.*")
|
// includeGroupByRegex("com\\.android.*")
|
||||||
includeGroupByRegex("com\\.google.*")
|
// includeGroupByRegex("com\\.google.*")
|
||||||
includeGroupByRegex("androidx.*")
|
// includeGroupByRegex("androidx.*")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
mavenCentral()
|
// mavenCentral()
|
||||||
gradlePluginPortal()
|
// gradlePluginPortal()
|
||||||
|
//
|
||||||
maven("https://objectbox.net/beta-repo/") // ✅ 必加
|
// maven("https://objectbox.net/beta-repo/") // ✅ 必加
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
dependencyResolutionManagement {
|
//dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
// repositories {
|
||||||
maven {
|
// maven {
|
||||||
url = uri("https://maven.aliyun.com/repository/public/")
|
// url = uri("https://maven.aliyun.com/repository/public/")
|
||||||
}
|
// }
|
||||||
google()
|
// google()
|
||||||
mavenCentral()
|
// mavenCentral()
|
||||||
maven("https://objectbox.net/beta-repo/") // ✅ 必加
|
// maven("https://objectbox.net/beta-repo/") // ✅ 必加
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
rootProject.name = "DualScreen"
|
rootProject.name = "DualScreen"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||