diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..f14f344
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,68 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.shuwei.intelligent.shelves"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "com.shuwei.intelligent.shelves"
+ minSdk = 25
+ //noinspection ExpiredTargetSdkVersion
+ targetSdk = 25
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ viewBinding = true
+ }
+
+ ndkVersion = "29.0.13846066"
+}
+
+dependencies {
+ //implementation(fileTree("libs") { include("*.jar", "*.aar") })
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ implementation("androidx.activity:activity-ktx:1.8.0")
+ implementation("io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.2")
+
+ implementation("com.squareup.okhttp3:okhttp:4.9.1")
+ implementation("com.google.code.gson:gson:2.10.1")
+
+// implementation("io.github.jeremyliao:live-event-bus-x:1.8.0")
+
+
+// implementation("com.hoho:android-usb-serial:1.3.0")
+
+ implementation("io.github.jeadyx:kmp-serialport:1.0.0")
+
+// implementation("io.github.jeadyx:jserialport:1.5")
+
+ implementation("androidx.work:work-runtime-ktx:2.8.1")
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/shuwei/intelligent/shelves/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/shuwei/intelligent/shelves/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..98526a7
--- /dev/null
+++ b/app/src/androidTest/java/com/shuwei/intelligent/shelves/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.shuwei.intelligent.shelves
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.shuwei.intelligent.shelves", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a88eddf
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/App.kt b/app/src/main/java/com/shuwei/intelligent/shelves/App.kt
new file mode 100644
index 0000000..ac2c9a3
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/App.kt
@@ -0,0 +1,23 @@
+package com.shuwei.intelligent.shelves
+
+import android.app.Application
+
+/**
+ * @author: star
+ * @date: 2022-04-26
+ */
+class App : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ app = this
+ }
+
+ companion object {
+ private lateinit var app: App
+
+ fun get(): App {
+ return app
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/HomeActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/HomeActivity.kt
new file mode 100644
index 0000000..0778d73
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/HomeActivity.kt
@@ -0,0 +1,222 @@
+package com.shuwei.intelligent.shelves
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.util.SparseIntArray
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.GridLayoutManager
+import com.chad.library.adapter4.util.setOnDebouncedItemClick
+import com.shuwei.intelligent.shelves.adapter.ShelfAdapter
+import com.shuwei.intelligent.shelves.base.BaseActivity
+import com.shuwei.intelligent.shelves.databinding.ActivityHomeBinding
+import com.shuwei.intelligent.shelves.model.ShelfModel
+import com.shuwei.intelligent.shelves.serial.SerialPortManager
+import com.shuwei.intelligent.shelves.task.TaskManager
+import com.shuwei.intelligent.shelves.utils.UIUtils
+import com.shuwei.intelligent.shelves.utils.hexToDec
+import kotlinx.coroutines.launch
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class HomeActivity : BaseActivity() {
+
+ companion object {
+ private const val TAG = "HomeActivity"
+
+ //协议头
+ const val HEADER = "D6DADEDB"
+
+ //协议尾
+ const val FOOTER = "FFFF"
+ const val ACTIVE_CMD =
+ "${HEADER}000281C5D70A7D428A4FA67B50F7DACA241938938B4B9CBD73673E9C8E7F2E15062D${FOOTER}"
+
+ const val LEFT_SHELF_OPEN_CMD =
+ "${HEADER}020101000000000000000000000000000000000000${FOOTER}"
+ const val RIGHT_SHELF_OPEN_CMD =
+ "${HEADER}020100010000000000000000000000000000000000${FOOTER}"
+
+ //const val TEMPERATURE_CMD = "${HEADER}1001${FOOTER}"
+
+ const val DEVICE_INFO_CMD = "${HEADER}0301000000000000000000000000${FOOTER}"
+
+ val list: MutableList = mutableListOf()
+ }
+
+ private lateinit var binding: ActivityHomeBinding
+
+ @SuppressLint("NotifyDataSetChanged")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityHomeBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ updateLeftStatus("1-1冷藏柜 -2°C / 87%")
+
+ initList()
+ initRecyclerView()
+
+ lifecycleScope.launch {
+ val openState = SerialPortManager.open()
+ Log.d(TAG, "onCreate: openState=$openState")
+ if (openState) {
+ SerialPortManager.startReceive { data ->
+ runOnUiThread {
+ // 更新UI显示接收数据
+ receiveSerialPortData(data)
+ }
+ }
+ SerialPortManager.send(ACTIVE_CMD)
+ }
+ }
+ TaskManager.startTask()
+ }
+
+ private fun receiveSerialPortData(data: String) {
+ Log.d(TAG, "receiveSerialPortData: $data")
+ if (data.startsWith(HEADER).not()) {
+ return
+ }
+ val cmd = data.substring(HEADER.length, HEADER.length + 2)
+ when (cmd) {
+ "03" -> {
+ //获取设备状态
+ val weightStartIndex = HEADER.length + 4 + 10
+ val temperatureStartIndex = weightStartIndex + 10 * 8
+ val temperatureHex = data.substring(temperatureStartIndex, 2)
+ val temperature = hexToDec(temperatureHex)
+ updateLeftStatus("1-1冷藏柜 $temperature°C / 87%")
+
+ var index = 1
+ var count = weightStartIndex
+ while (count < temperatureStartIndex) {
+ val weightInfo = data.substring(count, count + 8)
+ var weightHex = ""
+ weightInfo.run {
+ weightHex =
+ substring(6, 8) + substring(4, 6) + substring(2, 4) + substring(0, 2)
+ }
+ var realWeight = 0
+ if (weightHex.first() in '8'..'F') {
+ //负数
+ val weightHex2 = "0${weightHex.substring(1)}"
+ realWeight = hexToDec(weightHex2)
+ } else {
+ //整数
+ realWeight = hexToDec(weightHex)
+ }
+ weightArray.put(count, realWeight)
+ count += 8
+ index++
+ }
+
+ val intent = Intent(ShelfActivity.RECEIVER_DEVICE_INFO)
+ intent.putExtra(ShelfActivity.SHELF_WEIGHT, weightArray[clickIndex])
+ sendBroadcast(intent)
+ }
+
+ else -> {}
+ }
+ }
+
+ private val weightArray by lazy { SparseIntArray() }
+ private var clickIndex = 0
+
+ override fun onDestroy() {
+ lifecycleScope.launch {
+ SerialPortManager.close()
+ }
+ TaskManager.cancelTask()
+ super.onDestroy()
+ }
+
+ @Suppress("DEPRECATION")
+ private val shelfAdapter by lazy {
+// val list = viewModel.getCurrentItems()
+ ShelfAdapter(list).apply {
+ setOnDebouncedItemClick { adapter, view, position ->
+ this@HomeActivity.clickIndex = position
+ lifecycleScope.launch {
+ val openCmd = if (list[position].shelfNo in 1..5)
+ LEFT_SHELF_OPEN_CMD
+ else
+ RIGHT_SHELF_OPEN_CMD
+ val openState = SerialPortManager.send(openCmd)
+ if (openState.not()) {
+ UIUtils.toast("${list[position].foodName}柜门开启失败")
+ return@launch
+ }
+ launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
+ it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
+ }) {
+ it?.run {
+ val model =
+ getSerializableExtra(ShelfActivity.SHELF_MODEL) as ShelfModel
+ list[position].apply {
+ foodName = model.foodName
+ foodWeight = model.foodWeight
+ storeDate = model.storeDate
+ }
+ notifyItemChanged(position)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun initRecyclerView() {
+ binding.rvShelf.run {
+ layoutManager =
+ GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false)
+ adapter = shelfAdapter
+ }
+ }
+
+ private fun initList() {
+// val list: MutableList = mutableListOf()
+ list.add(ShelfModel(shelfNo = 1))
+ list.add(ShelfModel(shelfNo = 6))
+ list.add(ShelfModel(shelfNo = 2))
+ list.add(ShelfModel(shelfNo = 7))
+ list.add(ShelfModel(shelfNo = 3))
+ list.add(ShelfModel(shelfNo = 8))
+ list.add(ShelfModel(shelfNo = 4))
+ list.add(ShelfModel(shelfNo = 9))
+ list.add(ShelfModel(shelfNo = 5))
+ list.add(ShelfModel(shelfNo = 10))
+// viewModel.updateItems(list)
+ }
+
+ private val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
+
+ private fun updateDateTime() {
+ val sdf = SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA)
+ val dateTime = sdf.format(Date())
+ updateRightStatus(dateTime)
+ }
+
+ private val handler = Handler(Looper.getMainLooper())
+ private val updateTask = object : Runnable {
+ override fun run() {
+ updateDateTime()
+ handler.postDelayed(this, 1000)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ hideStatusBar()
+ handler.post(updateTask)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ handler.removeCallbacks(updateTask)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/ShelfActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/ShelfActivity.kt
new file mode 100644
index 0000000..1cd6e0b
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/ShelfActivity.kt
@@ -0,0 +1,208 @@
+package com.shuwei.intelligent.shelves
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import androidx.annotation.RequiresApi
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.GridLayoutManager
+import com.chad.library.adapter4.util.setOnDebouncedItemClick
+import com.shuwei.intelligent.shelves.HomeActivity.Companion.ACTIVE_CMD
+import com.shuwei.intelligent.shelves.HomeActivity.Companion.DEVICE_INFO_CMD
+import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
+import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
+import com.shuwei.intelligent.shelves.utils.ext.toast
+import com.shuwei.intelligent.shelves.adapter.SearchAdapter
+import com.shuwei.intelligent.shelves.base.BaseActivity
+import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
+import com.shuwei.intelligent.shelves.model.SearchModel
+import com.shuwei.intelligent.shelves.model.ShelfModel
+import com.shuwei.intelligent.shelves.serial.SerialPortManager
+import com.shuwei.intelligent.shelves.utils.DateTimeUtil
+import com.shuwei.intelligent.shelves.utils.KeyboardUtil
+import com.shuwei.intelligent.shelves.utils.decToHex
+import kotlinx.coroutines.launch
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import kotlin.random.Random
+
+@SuppressLint("NotifyDataSetChanged")
+class ShelfActivity : BaseActivity() {
+
+ companion object {
+ const val SHELF_MODEL = "shelfModel"
+ const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo"
+ const val SHELF_WEIGHT = "shelfWeight"
+
+ }
+
+ private lateinit var binding: ActivityShelfBinding
+
+ private var shelfModel: ShelfModel? = null
+
+ private var clickIndex: Int = -1
+
+ private var realWeight: Int = 0
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ @Suppress("DEPRECATION")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityShelfBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ shelfModel = intent.getSerializableExtra(SHELF_MODEL) as ShelfModel?
+
+ shelfModel?.let {
+ binding.tvShelfName.text = it.shelfName
+ binding.tvFoodName.text = if (it.foodName.isNullOrBlank()) "-" else it.foodName
+ }
+
+ initRecyclerView()
+// initWeight()
+ binding.etInputFood.run {
+ addOnActionSearchListener {
+ if (text.isNullOrBlank()) {
+ toast(hint.toString())
+ return@addOnActionSearchListener
+ }
+ loadSearchData()
+ KeyboardUtil.hideKeyboard(context, this)
+ }
+ }
+
+ binding.btnConfirm.clickWithDebounce {
+ if (clickIndex < 0) {
+ toast("请选择食材")
+ return@clickWithDebounce
+ }
+ val data = ShelfModel(
+ shelfName = shelfModel?.shelfName,
+ foodName = list[clickIndex].foodName,
+ foodWeight = realWeight,
+ storeDate = DateTimeUtil.formatDateTime(dateTime = Date())
+ )
+ val intent = Intent().apply {
+ putExtra(SHELF_MODEL, data)
+ }
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ binding.tvFoodWeight.text = "${realWeight}克"
+ binding.root.setOnClickListener { v ->
+ KeyboardUtil.hideKeyboard(v.context, v)
+ }
+ binding.btnClearZero.setOnClickListener { v ->
+ lifecycleScope.launch {
+ val shelfNo = shelfModel?.shelfNo ?: 0
+ val zeroClearingCmd =
+ "${HomeActivity.HEADER}0401${decToHex(shelfNo)}${HomeActivity.FOOTER}"
+ SerialPortManager.send(zeroClearingCmd)
+ }
+ KeyboardUtil.hideKeyboard(v.context, v)
+ }
+
+ lifecycleScope.launch {
+ SerialPortManager.send(DEVICE_INFO_CMD)
+ }
+
+ registerReceiver(receiver, IntentFilter(RECEIVER_DEVICE_INFO))
+ }
+
+ private val receiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ intent?.let {
+ realWeight = it.getIntExtra(SHELF_WEIGHT, 0)
+ binding.tvFoodWeight.text = "${realWeight}克"
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ unregisterReceiver(receiver)
+ super.onDestroy()
+ }
+
+// private fun initWeight() {
+// realWeight = Random.nextInt(1, 5001)
+// }
+
+ private fun loadSearchData() {
+ list.add(SearchModel(foodId = 1000, foodName = "菲律宾乳猪五花肉"))
+ list.add(SearchModel(foodId = 1001, foodName = "金针菇"))
+ list.add(SearchModel(foodId = 1002, foodName = "大菠菜"))
+ list.add(SearchModel(foodId = 1003, foodName = "清远麻鸡块"))
+ list.add(SearchModel(foodId = 1004, foodName = "普罗旺斯番茄"))
+ list.add(SearchModel(foodId = 1005, foodName = "长条茄子"))
+ list.add(SearchModel(foodId = 1006, foodName = "得莫利鱼"))
+ list.add(SearchModel(foodId = 1007, foodName = "土豆丝"))
+ list.add(SearchModel(foodId = 1008, foodName = "辣椒炒肉"))
+ list.add(SearchModel(foodId = 1009, foodName = "雪菜烧豆腐"))
+ list.add(SearchModel(foodId = 1010, foodName = "冬虫夏草炝拌芥兰苗"))
+ list.add(
+ SearchModel(
+ foodId = 1011,
+ foodName = "蘑菇玉米山药莲藕胡萝卜红枣莲子枸杞清炖排骨汤"
+ )
+ )
+ searchAdapter.notifyDataSetChanged()
+ }
+
+ private val list: MutableList = mutableListOf()
+ private val searchAdapter by lazy {
+ SearchAdapter(list).apply {
+ setOnDebouncedItemClick { adapter, v, position ->
+ this@ShelfActivity.clickIndex = position
+ list.forEach { it.isClicked = false }
+ list[position].isClicked = true
+ notifyDataSetChanged()
+ binding.tvFoodName.text = list[position].foodName
+ KeyboardUtil.hideKeyboard(v.context, v)
+ }
+ }
+ }
+
+ private fun initRecyclerView() {
+ binding.rvSearch.run {
+ layoutManager = GridLayoutManager(this@ShelfActivity, 2)
+ adapter = searchAdapter
+ }
+ }
+
+ private val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
+
+ private fun updateDateTime() {
+ val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
+ val dateTime = sdf.format(Date())
+ val arr = dateTime.split("***")
+ updateLeftStatus(arr[0])
+ updateRightStatus(arr[1])
+ }
+
+ private val handler = Handler(Looper.getMainLooper())
+ private val updateTask = object : Runnable {
+ override fun run() {
+ updateDateTime()
+ handler.postDelayed(this, 1000)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ hideStatusBar()
+ handler.post(updateTask)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ handler.removeCallbacks(updateTask)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/adapter/SearchAdapter.kt b/app/src/main/java/com/shuwei/intelligent/shelves/adapter/SearchAdapter.kt
new file mode 100644
index 0000000..aa5ff6a
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/adapter/SearchAdapter.kt
@@ -0,0 +1,50 @@
+package com.shuwei.intelligent.shelves.adapter
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.toColorInt
+import com.chad.library.adapter4.BaseQuickAdapter
+import com.chad.library.adapter4.viewholder.QuickViewHolder
+import com.shuwei.intelligent.shelves.R
+import com.shuwei.intelligent.shelves.databinding.ListItemSearchBinding
+import com.shuwei.intelligent.shelves.model.SearchModel
+
+class SearchAdapter(list: MutableList) :
+ BaseQuickAdapter(list) {
+
+ inner class VH(var binding: ListItemSearchBinding) : QuickViewHolder(binding.root)
+
+ override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
+ val inflater = LayoutInflater.from(context)
+ val binding = ListItemSearchBinding.inflate(inflater, parent, false)
+ return VH(binding)
+ }
+
+ override fun onBindViewHolder(holder: VH, position: Int, item: SearchModel?) {
+ val binding = holder.binding
+ binding.btnFoodName.run {
+ text = item!!.foodName
+ setTextColor(
+ getColor(if (item.isClicked) R.color.shelf_name_blue else R.color.black999)
+ )
+ val btnStrokeColor = if (item.isClicked) "#4969F5" else "#E6E6E6"
+ strokeColor = ColorStateList.valueOf(btnStrokeColor.toColorInt())
+ }
+// val bgDrawable = createCustomDrawable(
+// solidColor = "#FFFFFF",
+// strokeWidth = 2.dp,
+// strokeColor = if (item!!.isClicked) "#4969F5" else "#E6E6E6",
+// radius = 12.dp
+// )
+// binding.root.background = bgDrawable
+
+ }
+
+ private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/adapter/ShelfAdapter.kt b/app/src/main/java/com/shuwei/intelligent/shelves/adapter/ShelfAdapter.kt
new file mode 100644
index 0000000..3ad9563
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/adapter/ShelfAdapter.kt
@@ -0,0 +1,72 @@
+package com.shuwei.intelligent.shelves.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import com.chad.library.adapter4.BaseQuickAdapter
+import com.chad.library.adapter4.viewholder.QuickViewHolder
+import com.shuwei.intelligent.shelves.R
+import com.shuwei.intelligent.shelves.databinding.ListItemShelfBinding
+import com.shuwei.intelligent.shelves.model.ShelfModel
+
+class ShelfAdapter(list: MutableList) :
+ BaseQuickAdapter(list) {
+
+ inner class VH(var binding: ListItemShelfBinding) : QuickViewHolder(binding.root)
+
+ override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
+ val inflater = LayoutInflater.from(context)
+ val binding = ListItemShelfBinding.inflate(inflater, parent, false)
+ return VH(binding)
+ }
+
+ override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModel?) {
+ val shelfNo = item!!.shelfNo
+ item.shelfName = "货架-${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo" }"
+ val weight = item.foodWeight ?: 0
+ val binding = holder.binding
+ val isBlankShelf = weight <= 0
+ if (isBlankShelf) {
+ binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_blue))
+ binding.tvShelfName.run {
+ setTextColor(getColor(R.color.shelf_name_white))
+ text = item.shelfName
+ }
+ binding.tvFoodName.run {
+ setTextColor(getColor(R.color.white))
+ text = "空"
+ }
+ binding.tvFoodWeight.run {
+ setTextColor(getColor(R.color.food_weight_blue))
+ text = "0千克"
+ }
+ binding.tvStoreDate.run {
+ setTextColor(getColor(R.color.food_weight_blue))
+ text = "-"
+ }
+ return
+ }
+ binding.layoutCard.setCardBackgroundColor(getColor(R.color.bg_card_white))
+ binding.tvShelfName.run {
+ setTextColor(getColor(R.color.shelf_name_blue))
+ text = item.shelfName
+ }
+ binding.tvFoodName.run {
+ setTextColor(getColor(R.color.food_name_black))
+ text = item.foodName
+ }
+ binding.tvFoodWeight.run {
+ setTextColor(getColor(R.color.food_weight_orange))
+ text = "${weight/1000F}千克"
+ }
+ binding.tvStoreDate.run {
+ setTextColor(getColor(R.color.black999))
+ text = item.storeDate
+ }
+ }
+
+ private fun getColor(@ColorRes id: Int) = ContextCompat.getColor(context, id)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt
new file mode 100644
index 0000000..5691937
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt
@@ -0,0 +1,83 @@
+package com.shuwei.intelligent.shelves.base
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import com.shuwei.intelligent.shelves.R
+import com.shuwei.intelligent.shelves.databinding.ActivityBaseBinding
+
+@Suppress("DEPRECATION")
+open class BaseActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityBaseBinding
+ private var launcher: ActivityResultLauncher? = null
+ private var launchCallback: ((Intent?) -> Unit)? = null
+
+ public fun launch(cls: Class<*>, launchCallback: (Intent?) -> Unit) {
+ this.launchCallback = launchCallback
+ launcher?.launch(Intent(this, cls))
+ }
+
+ public fun launch(intent: Intent, launchCallback: (Intent?) -> Unit) {
+ this.launchCallback = launchCallback
+ launcher?.launch(intent)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityBaseBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ statusBarDarkFont(enable = false)
+ launcher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ launchCallback?.invoke(result.data)
+ }
+ }
+
+// window.setDecorFitsSystemWindows(false) // 启用 Edge-to-Edge
+// window.insetsController?.apply {
+// hide(WindowInsets.Type.statusBars()) // 隐藏状态栏
+// systemBarsBehavior =
+// WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 滑动时临时显示
+// }
+ }
+
+ fun hideStatusBar() {
+ enableEdgeToEdge()
+// val uiOptions = (View.SYSTEM_UI_FLAG_FULLSCREEN // 隐藏状态栏
+// or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) // 隐藏导航栏(可选)
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
+ }
+
+ fun statusBarDarkFont(enable: Boolean) {
+ //window.decorView.systemUiVisibility = if (enable) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0
+ val color = ContextCompat.getColor(this, R.color.white)
+ binding.tvLeftStatus.setTextColor(color)
+ binding.tvRightStatus.setTextColor(color)
+ }
+
+ override fun setContentView(view: View?) {
+ if (view == binding.root) {
+ super.setContentView(view)
+ } else {
+ binding.flContainer.addView(view)
+ }
+ }
+
+ fun updateLeftStatus(data: String) {
+ binding.tvLeftStatus.text = data
+ }
+
+ fun updateRightStatus(data: String) {
+ binding.tvRightStatus.text = data
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/model/SearchModel.kt b/app/src/main/java/com/shuwei/intelligent/shelves/model/SearchModel.kt
new file mode 100644
index 0000000..5336314
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/model/SearchModel.kt
@@ -0,0 +1,9 @@
+package com.shuwei.intelligent.shelves.model
+
+import java.io.Serializable
+
+data class SearchModel(
+ var foodId: Int? = null,
+ var foodName: String? = null,
+ var isClicked: Boolean = false
+) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/model/ShelfModel.kt b/app/src/main/java/com/shuwei/intelligent/shelves/model/ShelfModel.kt
new file mode 100644
index 0000000..37bb64c
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/model/ShelfModel.kt
@@ -0,0 +1,11 @@
+package com.shuwei.intelligent.shelves.model
+
+import java.io.Serializable
+
+data class ShelfModel(
+ var shelfNo: Int = 0,
+ var shelfName: String? = null,
+ var foodName: String? = null,
+ var foodWeight: Int? = null,
+ var storeDate: String? = null
+) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt b/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt
new file mode 100644
index 0000000..611885c
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/serial/SerialPortManager.kt
@@ -0,0 +1,62 @@
+package com.shuwei.intelligent.shelves.serial
+
+import io.github.jeadyx.jserialport.AndroidSerialPort
+import io.github.jeadyx.jserialport.SerialPort
+import io.github.jeadyx.jserialport.SerialPortFactory
+import kotlinx.coroutines.*
+
+object SerialPortManager {
+ private const val portName: String = "/dev/ttyS1"
+ private const val baudRate: Int = 115200
+ private var serialPort: AndroidSerialPort? = null
+// private var serialPort: SerialPort? = null
+ private val scope = CoroutineScope(Dispatchers.IO)
+
+ // 打开串口
+ suspend fun open(): Boolean {
+ return try {
+ serialPort = SerialPortFactory.create().apply {
+ open(
+ portName = portName,
+ baudRate = baudRate,
+ dataBits = 8,
+ stopBits = 1,
+ parity = SerialPort.PARITY_NONE
+ )
+ } as AndroidSerialPort?
+ true
+ } catch (e: Exception) {
+ e.printStackTrace()
+ false
+ }
+ }
+
+ // 发送数据
+ suspend fun send(data: String): Boolean {
+ return try {
+ serialPort?.write(data.toByteArray())
+ true
+ } catch (e: Exception) {
+ e.printStackTrace()
+ false
+ }
+ }
+
+ // 启动接收协程
+ fun startReceive(callback: (String) -> Unit) {
+ scope.launch {
+ while (isActive) {
+ serialPort?.read()?.collect { buffer ->
+ callback(String(buffer))
+ }
+ delay(50)
+ }
+ }
+ }
+
+ // 关闭串口
+ suspend fun close() {
+ scope.cancel()
+ serialPort?.close()
+ }
+}
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/task/SyncTask.kt b/app/src/main/java/com/shuwei/intelligent/shelves/task/SyncTask.kt
new file mode 100644
index 0000000..288345e
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/task/SyncTask.kt
@@ -0,0 +1,40 @@
+package com.shuwei.intelligent.shelves.task
+
+import android.content.Context
+import android.util.Log
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.shuwei.intelligent.shelves.HomeActivity
+import com.shuwei.intelligent.shelves.utils.ext.toJsonString
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class SyncTask(appContext: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(appContext, workerParams) {
+
+ companion object {
+ private const val TAG = "SyncTask"
+ }
+
+ override suspend fun doWork(): Result {
+ return try {
+ // 执行后台任务逻辑
+ performSync()
+
+ TaskManager.startTask()
+
+ Result.success()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Result.retry()
+ }
+ }
+
+ private suspend fun performSync() {
+ withContext(Dispatchers.IO) {
+ val list = HomeActivity.list
+ Log.d(TAG, "performSync: list:${list.toJsonString()}")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/task/TaskManager.kt b/app/src/main/java/com/shuwei/intelligent/shelves/task/TaskManager.kt
new file mode 100644
index 0000000..b59ccc1
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/task/TaskManager.kt
@@ -0,0 +1,44 @@
+package com.shuwei.intelligent.shelves.task
+
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.shuwei.intelligent.shelves.App
+import java.util.concurrent.TimeUnit
+
+object TaskManager {
+ const val TASK_NAME = "IntelligentShelvesSyncTask"
+
+ fun startTask() {
+ val nextRequest = OneTimeWorkRequestBuilder()
+ .setInitialDelay(3, TimeUnit.MINUTES)
+// .setInitialDelay(10, TimeUnit.SECONDS)
+// .setInputData(inputData)
+ .build()
+ WorkManager.getInstance(App.get())
+ .enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
+
+// val constraints = Constraints.Builder()
+// .setRequiredNetworkType(NetworkType.CONNECTED)
+// .setRequiresBatteryNotLow(true)
+// .build()
+// val syncRequest = PeriodicWorkRequestBuilder(
+// 3L, TimeUnit.MINUTES, // 最小间隔15分钟
+// 3L, TimeUnit.MINUTES // 弹性时间窗口
+// )
+// .setConstraints(constraints)
+// .build()
+// WorkManager.getInstance(App.get())
+// .enqueueUniquePeriodicWork(
+// TASK_NAME,
+// ExistingPeriodicWorkPolicy.KEEP,
+// syncRequest
+// )
+ }
+
+ fun cancelTask() {
+ WorkManager.getInstance(App.get())
+ .cancelUniqueWork(TASK_NAME)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/Constant.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/Constant.kt
new file mode 100644
index 0000000..bd38c5a
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/Constant.kt
@@ -0,0 +1,8 @@
+package com.shuwei.intelligent.shelves.utils
+
+/**
+ * 命令常亮定义
+ */
+object Constant {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/DateTimeUtil.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/DateTimeUtil.kt
new file mode 100644
index 0000000..20aa2e5
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/DateTimeUtil.kt
@@ -0,0 +1,36 @@
+package com.shuwei.intelligent.shelves.utils
+
+import java.text.SimpleDateFormat
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.util.Date
+import java.util.Locale
+
+
+object DateTimeUtil {
+
+ const val YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"
+
+ fun formatDateTime(dateTime: Date, pattern: String = YYYY_MM_DD_HH_MM_SS): String {
+// val formatter = DateTimeFormatter.ofPattern(pattern)
+// return dateTime.format(formatter)
+ val sdf = SimpleDateFormat(pattern, Locale.CHINA)
+ return sdf.format(dateTime)
+ }
+
+// fun convert(dateStr: String, pattern: String = YYYY_MM_DD_HH_MM_SS): Date {
+// val formatter = DateTimeFormatter.ofPattern(pattern)
+// val ldt = LocalDateTime.parse(dateStr, formatter)
+// val zdt = ldt.atZone(ZoneId.systemDefault())
+// return Date.from(zdt.toInstant())
+// }
+//
+// fun main() {
+// val now = LocalDateTime.now()
+// println("默认格式: ${formatDateTime(now)}")
+// println("自定义格式: ${formatDateTime(now, "yyyy年MM月dd日 HH时mm分ss秒")}")
+// }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/DecToHex.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/DecToHex.kt
new file mode 100644
index 0000000..c0eafa7
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/DecToHex.kt
@@ -0,0 +1,30 @@
+package com.shuwei.intelligent.shelves.utils
+
+fun decToHex(decimal: Int): String {
+ if (decimal == 0) return "0"
+
+ val hexDigits = "0123456789ABCDEF"
+ var num = decimal
+ var result = ""
+
+ while (num > 0) {
+ val remainder = num % 16
+ result = hexDigits[remainder] + result
+ num /= 16
+ }
+
+ return result
+}
+
+fun hexToDec(hex: String): Int {
+ val digits = "0123456789ABCDEF"
+ var result = 0
+
+ hex.uppercase().forEach { char ->
+ val digitValue = digits.indexOf(char)
+ require(digitValue != -1) { "非法十六进制字符: $char" }
+ result = result * 16 + digitValue
+ }
+
+ return result
+}
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/KeyboardUtil.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/KeyboardUtil.kt
new file mode 100644
index 0000000..a6e1ae6
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/KeyboardUtil.kt
@@ -0,0 +1,17 @@
+package com.shuwei.intelligent.shelves.utils
+
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+
+
+object KeyboardUtil {
+
+ fun hideKeyboard(context: Context, view: View) {
+ val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(view.windowToken, 0)
+ view.clearFocus() // 清除焦点避免键盘再次弹出
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/SerialPortHelper.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/SerialPortHelper.kt
new file mode 100644
index 0000000..b071891
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/SerialPortHelper.kt
@@ -0,0 +1,57 @@
+//package com.shuwei.intelligent.shelves.utils
+//
+//
+//import android.serialport.*
+////import io.github.jeadyx.jserialport.SerialPort
+//import java.io.File
+//import java.io.IOException
+//import java.io.InputStream
+//import java.io.OutputStream
+//
+//class SerialPortHelper(
+// private val devicePath: String = "/dev/ttyS1",
+// private val baudRate: Int = 115200
+//) {
+// private var serialPort: SerialPort? = null
+// private var inputStream: InputStream? = null
+// private var outputStream: OutputStream? = null
+//
+// // 打开串口
+// fun open(): Boolean {
+// return try {
+// serialPort = SerialPort(File(devicePath), baudRate, 0)
+// inputStream = serialPort?.inputStream
+// outputStream = serialPort?.outputStream
+// true
+// } catch (e: IOException) {
+// false
+// }
+// }
+//
+// // 发送数据
+// fun send(data: ByteArray): Boolean {
+// return try {
+// outputStream?.write(data)
+// outputStream?.flush()
+// true
+// } catch (e: IOException) {
+// false
+// }
+// }
+//
+// // 接收数据(需在子线程调用)
+// fun receive(buffer: ByteArray): Int {
+// return inputStream?.read(buffer) ?: -1
+// }
+//
+// // 关闭串口
+// fun close() {
+// try {
+// inputStream?.close()
+// outputStream?.close()
+// serialPort?.close()
+// } catch (e: IOException) {
+// e.printStackTrace()
+// }
+// }
+//}
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/ToastUtil.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ToastUtil.kt
new file mode 100644
index 0000000..9e2cb0e
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ToastUtil.kt
@@ -0,0 +1,28 @@
+package com.shuwei.intelligent.shelves.utils
+
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.TextView
+import android.widget.Toast
+import com.shuwei.intelligent.shelves.R
+
+
+object ToastUtil {
+
+ fun show(context: Context, message: String, duration:Int = Toast.LENGTH_SHORT) {
+ val inflater = LayoutInflater.from(context)
+ val layout: View = inflater.inflate(R.layout.custom_toast, null)
+ val textView = layout.findViewById(R.id.tvMessage)
+ textView.text = message
+
+ val toast = Toast(context)
+ toast.view = layout // 设置自定义视图
+ toast.setGravity(Gravity.CENTER, 0, 0) // 调整位置居中显示
+ toast.setDuration(duration)
+ toast.show()
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/UIUtils.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/UIUtils.kt
new file mode 100644
index 0000000..a28d006
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/UIUtils.kt
@@ -0,0 +1,14 @@
+package com.shuwei.intelligent.shelves.utils
+
+import android.widget.Toast
+import com.shuwei.intelligent.shelves.App
+
+object UIUtils {
+ fun toast(strRes: Int) {
+ Toast.makeText(App.get(), strRes, Toast.LENGTH_SHORT).show()
+ }
+
+ fun toast(str: String) {
+ Toast.makeText(App.get(), str, Toast.LENGTH_SHORT).show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/Common.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/Common.kt
new file mode 100644
index 0000000..dc1aa40
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/Common.kt
@@ -0,0 +1,153 @@
+package com.shuwei.intelligent.shelves.utils.ext
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.res.Resources
+import android.os.Bundle
+import android.util.TypedValue
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.widget.EditText
+import android.widget.Toast
+import androidx.core.app.ActivityOptionsCompat
+import androidx.fragment.app.Fragment
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.shuwei.intelligent.shelves.utils.ToastUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+fun Context.toast(
+ message: String?,
+ duration: Int = Toast.LENGTH_SHORT,
+) {
+ if (message.isNullOrBlank()) {
+ return
+ }
+ //Toast.makeText(this, message, duration).show()
+ ToastUtil.show(this, message, duration)
+}
+
+fun Fragment.toast(
+ message: String?,
+ duration: Int = 2000
+) {
+ activity?.toast(message, duration)
+}
+
+fun View.visible() {
+ visibility = View.VISIBLE
+}
+
+fun View.invisible() {
+ visibility = View.INVISIBLE
+}
+
+fun View.gone() {
+ visibility = View.GONE
+}
+
+inline fun Context.startActivity(
+ bundle: Bundle? = null,
+ options: ActivityOptionsCompat? = null
+) {
+ Intent(this, T::class.java).apply {
+ bundle?.let { putExtras(it) }
+ if (options != null && this@startActivity is Activity) {
+ startActivity(this, options.toBundle())
+ } else {
+ startActivity(this)
+ }
+ }
+}
+
+inline fun Context.startActivity(
+ block: Intent.() -> Unit = {}
+) {
+ Intent(this, T::class.java).apply {
+ block()
+ startActivity(this)
+ }
+}
+
+//inline fun Context.startActivity(action:(bundle: Bundle)-> Unit) {
+// Intent(this, T::class.java).apply {
+// action(Bundle())
+// startActivity(this)
+// }
+//}
+
+inline fun String.toType(gson: Gson? = null, typeToken: TypeToken): T {
+ return (gson ?: Gson()).fromJson(this, typeToken.type)
+}
+
+inline fun String.toObject(gson: Gson? = null): T {
+ return (gson ?: Gson()).fromJson(this, T::class.java)
+}
+
+fun Any?.toJsonString(gson: Gson? = null): String {
+ return (gson ?: Gson()).toJson(this) ?: ""
+}
+
+@SuppressLint("ApplySharedPref")
+inline fun SharedPreferences.edit(
+ commit: Boolean = false,
+ action: SharedPreferences.Editor.() -> Unit
+) {
+ val editor = edit()
+ action(editor)
+ if (commit) editor.commit() else editor.apply()
+}
+
+fun SharedPreferences.put(vararg pairs: Pair) {
+ edit {
+ pairs.forEach { (key, value) ->
+ when (value) {
+ is Int -> putInt(key, value)
+ is String -> putString(key, value)
+ is Boolean -> putBoolean(key, value)
+ is Float -> putFloat(key, value)
+ is Long -> putLong(key, value)
+ }
+ }
+ }
+}
+
+val Float.dp: Float
+ get() = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this,
+ Resources.getSystem().displayMetrics
+ )
+
+val Int.dp: Int
+ get() = this.toFloat().dp.toInt()
+
+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()
+ }
+ }
+}
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/DrawableExt.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/DrawableExt.kt
new file mode 100644
index 0000000..e65cd55
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/DrawableExt.kt
@@ -0,0 +1,70 @@
+package com.shuwei.intelligent.shelves.utils.ext
+
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import androidx.core.graphics.toColorInt
+
+interface DrawableDsl {
+ fun shape(init: ShapeDsl.() -> Unit)
+}
+
+interface ShapeDsl {
+ fun solidColor(color: Int)
+ fun stroke(width: Int, color: Int)
+ fun cornerRadius(radius: Float)
+}
+
+class DrawableBuilder : DrawableDsl {
+ val drawables = mutableListOf()
+
+ override fun shape(init: ShapeDsl.() -> Unit) {
+ val shape = ShapeDrawable()
+ init(shape)
+ drawables.add(shape)
+ }
+}
+
+class ShapeDrawable : ShapeDsl {
+ var color: Int? = null
+ var strokeWidth: Int? = null
+ var strokeColor: Int? = null
+ var cornerRadius: Float? = null
+
+ override fun solidColor(color: Int) {
+ this.color = color
+ }
+
+ override fun stroke(width: Int, color: Int) {
+ this.strokeWidth = width
+ this.strokeColor = color
+ }
+
+ override fun cornerRadius(radius: Float) {
+ this.cornerRadius = radius
+ }
+}
+
+fun createCustomDrawable(
+ solidColor: String,
+ strokeWidth: Int,
+ strokeColor: String,
+ radius: Int
+): Drawable {
+ val builder = DrawableBuilder()
+ builder.shape {
+ solidColor(solidColor.toColorInt())
+ stroke(strokeWidth, strokeColor.toColorInt())
+ cornerRadius(radius.toFloat())
+ }
+
+ return convertToDrawable(builder.drawables[0])
+}
+
+fun convertToDrawable(shapeDrawable: ShapeDrawable): Drawable {
+ val drawable = GradientDrawable()
+ shapeDrawable.color?.let { drawable.setColor(it) }
+ shapeDrawable.cornerRadius?.let { drawable.cornerRadius = it }
+ shapeDrawable.strokeWidth?.let { drawable.setStroke(it, shapeDrawable.strokeColor ?: Color.BLACK) }
+ return drawable
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/SpannableExt.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/SpannableExt.kt
new file mode 100644
index 0000000..bce4b9f
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/ext/SpannableExt.kt
@@ -0,0 +1,24 @@
+package com.shuwei.intelligent.shelves.utils.ext
+
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+
+
+inline fun buildSpannableString(builderAction: SpannableStringBuilder.() -> Unit): SpannableStringBuilder {
+ return SpannableStringBuilder().apply(builderAction)
+}
+
+fun SpannableStringBuilder.appendText(text: String, vararg spans: Any): SpannableStringBuilder {
+ val start = length
+ append(text)
+ spans.forEach { span ->
+ setSpan(span, start, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+ }
+ return this
+}
+
+fun SpannableStringBuilder.withSpan(span: Any, block: SpannableStringBuilder.() -> Unit) {
+ val start = length
+ block()
+ setSpan(span, start, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+}
diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/viewmodel/ShelfDataViewModel.kt b/app/src/main/java/com/shuwei/intelligent/shelves/viewmodel/ShelfDataViewModel.kt
new file mode 100644
index 0000000..344d22a
--- /dev/null
+++ b/app/src/main/java/com/shuwei/intelligent/shelves/viewmodel/ShelfDataViewModel.kt
@@ -0,0 +1,19 @@
+//package com.shuwei.intelligent.shelves.viewmodel
+//
+//import androidx.lifecycle.LiveData
+//import androidx.lifecycle.MutableLiveData
+//import androidx.lifecycle.ViewModel
+//import com.shuwei.intelligent.shelves.model.ShelfModel
+//
+//class ShelfDataViewModel : ViewModel() {
+//
+// private val _shelfItems = MutableLiveData>()
+// val shelfItems: LiveData> = _shelfItems
+//
+// fun updateItems(newItems: MutableList) {
+// _shelfItems.value = newItems
+// }
+//
+// fun getCurrentItems(): MutableList = _shelfItems.value ?: mutableListOf()
+//
+//}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search_food.png b/app/src/main/res/drawable-xxxhdpi/ic_search_food.png
new file mode 100644
index 0000000..782ae67
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_search_food.png differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_dialog.xml b/app/src/main/res/drawable/shape_dialog.xml
new file mode 100644
index 0000000..c7e4546
--- /dev/null
+++ b/app/src/main/res/drawable/shape_dialog.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_search.xml b/app/src/main/res/drawable/shape_search.xml
new file mode 100644
index 0000000..a7616f9
--- /dev/null
+++ b/app/src/main/res/drawable/shape_search.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_base.xml b/app/src/main/res/layout/activity_base.xml
new file mode 100644
index 0000000..82afe0a
--- /dev/null
+++ b/app/src/main/res/layout/activity_base.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..aadd7c6
--- /dev/null
+++ b/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_shelf.xml b/app/src/main/res/layout/activity_shelf.xml
new file mode 100644
index 0000000..a5b97f3
--- /dev/null
+++ b/app/src/main/res/layout/activity_shelf.xml
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_toast.xml b/app/src/main/res/layout/custom_toast.xml
new file mode 100644
index 0000000..2deaaa4
--- /dev/null
+++ b/app/src/main/res/layout/custom_toast.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_search.xml b/app/src/main/res/layout/list_item_search.xml
new file mode 100644
index 0000000..343c584
--- /dev/null
+++ b/app/src/main/res/layout/list_item_search.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_item_shelf.xml b/app/src/main/res/layout/list_item_shelf.xml
new file mode 100644
index 0000000..1ff3323
--- /dev/null
+++ b/app/src/main/res/layout/list_item_shelf.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..2515de4
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,17 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..0314839
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
+ #4666E8
+
+ #5C77F7
+ #EFF1FE
+ #EFF1FE
+ #4969F5
+ #0A143C
+ #C8C8FF
+ #FF6400
+ #999999
+ #F6F6F6
+ #E6EBF0
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b83cca8
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+ 智能货架
+
+ Send Failed
+ present connected Usb dev:
+ Connect success
+ Connect failed
+ connection has disconnected
+ USB DETACHED
+ USB ATTACHED
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..b9b3b57
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/shuwei/intelligent/shelves/ExampleUnitTest.kt b/app/src/test/java/com/shuwei/intelligent/shelves/ExampleUnitTest.kt
new file mode 100644
index 0000000..9dd3cad
--- /dev/null
+++ b/app/src/test/java/com/shuwei/intelligent/shelves/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.shuwei.intelligent.shelves
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..922f551
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..20e2a01
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..f13b46a
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,22 @@
+[versions]
+agp = "8.11.1"
+kotlin = "2.0.21"
+coreKtx = "1.10.1"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+appcompat = "1.6.1"
+material = "1.10.0"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..379cb0f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jul 17 15:32:57 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/local.properties b/local.properties
new file mode 100644
index 0000000..62c4a0c
--- /dev/null
+++ b/local.properties
@@ -0,0 +1,10 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file should *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=D\:\\Android\\sdk
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..020438d
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,29 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ 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") }
+ mavenCentral()
+ google()
+ }
+}
+
+rootProject.name = "IntelligentShelves"
+include(":app")
+
\ No newline at end of file