添加注释

This commit is contained in:
zxj
2025-08-22 16:36:17 +08:00
parent 18fbdc9875
commit d48f41e917
16 changed files with 28 additions and 440 deletions
@@ -43,6 +43,9 @@ import timber.log.Timber
import java.io.File
import java.util.concurrent.Executors
/**
* 服务员显示界面
*/
class MainActivity : BaseActivity<ActivityMainBinding>() {
private val viewModel by viewModels<UserViewModel>()
private var imageCapture: ImageCapture? = null
@@ -2,7 +2,7 @@ package com.sw.dualscreen.network.interceptor
import android.text.TextUtils
import com.sw.dualscreen.GlobalKey
import com.sw.inbound.utils.SPUtil
import com.sw.dualscreen.utils.SPUtil
import com.sw.plate.App
import okhttp3.Interceptor
import okhttp3.Request
@@ -7,8 +7,10 @@ import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.UserNutritionData
import timber.log.Timber
import kotlin.math.max
import kotlin.math.min
/**
* 用户营养信息计算工具
*/
object UserNutritionUtils {
/**
@@ -1,5 +1,8 @@
package com.sw.dualscreen.utils
/**
* 防抖工具
*/
class Debouncer(private val delayMillis: Long = 2000) {
private var lastActionTime = 0L
@@ -1,156 +0,0 @@
package com.sw.dualscreen.utils
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import timber.log.Timber
import java.io.File
object FileUtils {
/**
* 从Uri获取File
* example: file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg
*/
private fun getFileFromUri(context: Context, uri: Uri): File? {
Timber.d("getFileFromUri uri = ${uri.scheme}")
return when (uri.scheme) {
"file" -> File(uri.path ?: return null)
"content" -> {
try {
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
val cacheDir = context.cacheDir
val file = File.createTempFile(
"upload_${System.currentTimeMillis()}",
".jpg",
cacheDir
)
file.outputStream().use { output ->
inputStream.copyTo(output)
}
file
} catch (e: Exception) {
Timber.e(e)
null
}
}
else -> null
}
}
/**
* 通过uri生成http请求体
*/
fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? {
Timber.d("genRequestPart imageUri = $imageUri")
// 1. 从Uri获取文件
val file = getFileFromUri(context, imageUri)
if (file == null) {
Timber.e("getFileFromUri file is null")
return null
}
// 2. 创建请求体
val requestFile = file
.asRequestBody("application/octet-stream".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData(
"file",
file.name,
requestFile
)
return imagePart
}
/**
* 通过Uri删除文件
* @param context 上下文
* @param uri 文件Uri
* @return Boolean 是否删除成功
*/
fun deleteFileWithUri(context: Context, uri: Uri): Boolean {
Timber.d("deleteFileWithUri uri = ${uri.scheme}")
return when {
// 1. 处理 content:// 类型的Uri (MediaStore)
uri.scheme.equals("content", ignoreCase = true) -> {
deleteContentUriFile(context, uri)
}
// 2. 处理 file:// 类型的Uri
uri.scheme.equals("file", ignoreCase = true) -> {
deleteFileUriFile(uri)
}
// 3. 其他情况尝试直接解析路径
else -> {
deleteFileFromPath(uri.path ?: return false)
}
}
}
// 删除Content Uri文件
private fun deleteContentUriFile(context: Context, uri: Uri): Boolean {
Timber.d("deleteContentUriFile uri = ${uri.scheme}")
return try {
context.contentResolver.delete(uri, null, null) > 0
} catch (e: SecurityException) {
// Android 10+需要特殊处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
deleteMediaStoreFile(context, uri)
} else {
false
}
} catch (e: Exception) {
Timber.e(e)
false
}
}
// Android 10+删除MediaStore文件
@RequiresApi(Build.VERSION_CODES.Q)
private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean {
Timber.d("deleteMediaStoreFile uri = ${uri.scheme}")
val contentResolver = context.contentResolver
val projection = arrayOf(MediaStore.MediaColumns._ID)
return try {
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id =
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
val contentUri = ContentUris.withAppendedId(uri, id)
contentResolver.delete(contentUri, null, null) > 0
} else {
false
}
} ?: false
} catch (e: Exception) {
Timber.e(e)
false
}
}
// 删除File Uri文件
private fun deleteFileUriFile(uri: Uri): Boolean {
Timber.d("deleteFileUriFile uri = $uri")
return try {
File(uri.path ?: return false).delete()
} catch (e: Exception) {
Timber.e(e)
false
}
}
// 直接通过路径删除文件
private fun deleteFileFromPath(path: String): Boolean {
Timber.d("deleteFileFromPath path = $path")
return try {
File(path).delete()
} catch (e: Exception) {
Timber.e(e)
false
}
}
}
@@ -1,4 +1,4 @@
package com.sw.inbound.utils
package com.sw.dualscreen.utils
import android.content.Context
import androidx.core.content.edit
@@ -9,6 +9,9 @@ import kotlinx.coroutines.flow.map
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* 持久化工具
*/
class SPUtil private constructor(context: Context, private val spName: String) {
companion object {
@@ -1,47 +0,0 @@
package com.sw.dualscreen.utils
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
import timber.log.Timber
import kotlin.math.sqrt
object ScreenUtils {
fun printScreenInfo(context: Context, tag: String) {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics()
// 方法 1:获取应用可用区域(排除系统装饰,如状态栏)
windowManager.defaultDisplay.getMetrics(displayMetrics)
// 方法 2:获取整个屏幕的物理尺寸(包括状态栏和导航栏)
// windowManager.defaultDisplay.getRealMetrics(displayMetrics)
val screenWidthPx = displayMetrics.widthPixels // 屏幕宽度(像素)
val screenHeightPx = displayMetrics.heightPixels // 屏幕高度(像素)
val density = displayMetrics.density // 逻辑密度(1.0 = mdpi, 2.0 = xhdpi
val densityDpi = displayMetrics.densityDpi // 屏幕 DPI 分类(160 = mdpi, 320 = xhdpi
val xdpi = displayMetrics.xdpi // 物理水平 DPI
val ydpi = displayMetrics.ydpi // 物理垂直 DPI
// 打印信息
Timber.d("$tag 屏幕信息")
Timber.d("屏幕分辨率(像素): ${screenWidthPx}x${screenHeightPx}")
Timber.d("逻辑密度: $density")
Timber.d("DPI 分类: $densityDpi")
Timber.d("物理 DPI (x/y): $xdpi / $ydpi")
// 计算屏幕物理尺寸(英寸)
val screenWidthInches = screenWidthPx / xdpi
val screenHeightInches = screenHeightPx / ydpi
val diagonalInches = sqrt(
(screenWidthInches * screenWidthInches + screenHeightInches * screenHeightInches).toDouble()
)
Timber.d(
"屏幕物理尺寸(英寸): %.1f x %.1f (对角线: %.1f\")".format(
screenWidthInches, screenHeightInches, diagonalInches
)
)
}
}
@@ -8,7 +8,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@@ -25,6 +24,9 @@ import com.sw.dualscreen.viewmodel.UserViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
/**
* 底部搜索结果弹窗
*/
class CustomBottomSheetDialog(
val viewModel: UserViewModel,
val itemClickCallback: (FoodInfo) -> Unit
@@ -10,6 +10,9 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
/**
* 加载进度条弹窗
*/
public class CustomDialog extends Dialog {
@@ -18,6 +18,9 @@ import com.sw.plate.utils.AppUtil;
import timber.log.Timber;
/**
* 自定义柱状图
*/
public class CustomImageView extends androidx.appcompat.widget.AppCompatImageView {
private AlphaAnimation alphaAnimation;
private String textToDraw;
@@ -15,6 +15,9 @@ import androidx.appcompat.widget.AppCompatSpinner
import androidx.core.content.ContextCompat
import com.sw.dualscreen.R
/**
* spinner控件
*/
class CustomSpinner @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -1,56 +0,0 @@
package com.sw.dualscreen.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
class CylinderDrawView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 圆柱属性(默认红色)
var cylinderColor: Int = Color.RED
set(value) {
field = value
invalidate()
}
var cylinderWidth: Int = 120 // 默认宽度(px
set(value) {
field = value
requestLayout()
}
var cylinderHeight: Int = 360 // 默认高度(px
set(value) {
field = value
requestLayout()
}
private val paint = Paint().apply {
isAntiAlias = true // 抗锯齿
style = Paint.Style.FILL // 填充模式
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val measuredWidth = resolveSize(cylinderWidth, widthMeasureSpec)
val measuredHeight = resolveSize(cylinderHeight, heightMeasureSpec)
setMeasuredDimension(measuredWidth, measuredHeight)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = cylinderColor
// 绘制圆角矩形(半径=宽度的一半,形成圆柱效果)
val radius = cylinderWidth / 2f
canvas.drawRoundRect(
0f, 0f,
cylinderWidth.toFloat(), cylinderHeight.toFloat(),
radius, radius,
paint
)
}
}
@@ -1,104 +0,0 @@
package com.sw.dualscreen.view
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import com.sw.dualscreen.R
class CylinderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val valueText: TextView
private val unitText: TextView
private val cylinderDrawView: CylinderDrawView
private val topImage: ImageView
private val bottomImage: ImageView
private val bottomText: TextView
init {
// 加载内部布局
LayoutInflater.from(context).inflate(R.layout.view_cylinder, this, true)
// 绑定控件
valueText = findViewById(R.id.valueText)
unitText = findViewById(R.id.unitText)
cylinderDrawView = findViewById(R.id.cylinderDrawView)
topImage = findViewById(R.id.topImage)
bottomImage = findViewById(R.id.bottomImage)
bottomText = findViewById(R.id.bottomText)
// 解析自定义属性
context.obtainStyledAttributes(attrs, R.styleable.CylinderView).apply {
// 顶部文字:数值+单位合并
val value = getString(R.styleable.CylinderView_valueText) ?: ""
val unit = getString(R.styleable.CylinderView_unitText) ?: ""
valueText.text = value
valueText.textSize = getDimension(R.styleable.CylinderView_valueTextSize, 20f)
valueText.setTextColor(
getColor(
R.styleable.CylinderView_valueTextColor,
0xFFFFFFFF.toInt()
)
)
unitText.text = unit
unitText.textSize = getDimension(R.styleable.CylinderView_valueTextSize, 10f)
unitText.setTextColor(
getColor(
R.styleable.CylinderView_valueTextColor,
0xFFFFFFFF.toInt()
)
)
// 圆柱属性
cylinderDrawView.cylinderColor =
getColor(R.styleable.CylinderView_cylinderColor, Color.RED)
cylinderDrawView.cylinderWidth =
getDimensionPixelSize(R.styleable.CylinderView_cylinderWidth, 60)
cylinderDrawView.cylinderHeight =
getDimensionPixelSize(R.styleable.CylinderView_cylinderHeight, 180)
getResourceId(R.styleable.CylinderView_topImageSrc, -1).takeIf { it != -1 }?.let {
topImage.setImageResource(it)
// val layoutParams = cylinderDrawView.layoutParams
// layoutParams.height = (layoutParams.width / 2.45).toInt()
// topImage.layoutParams = layoutParams
}
// 底部图片
getResourceId(R.styleable.CylinderView_bottomImageSrc, -1).takeIf { it != -1 }?.let {
bottomImage.setImageResource(it)
// val layoutParams = cylinderDrawView.layoutParams
// layoutParams.height = 44
// bottomImage.layoutParams = layoutParams
}
// 底部文字
bottomText.text = getString(R.styleable.CylinderView_bottomText) ?: "热量"
bottomText.textSize = getDimension(R.styleable.CylinderView_bottomTextSize, 16f)
bottomText.setTextColor(
getColor(
R.styleable.CylinderView_bottomTextColor,
0xFFFFFFFF.toInt()
)
)
recycle() // 回收属性
}
}
fun setToImageSize() {
// topImage.layoutParams = LayoutParams.
}
fun setBottomSize() {
}
}
@@ -4,7 +4,7 @@ import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.model.response.EquipmentInfo
import com.sw.dualscreen.utils.GsonUtils
import com.sw.inbound.utils.SPUtil
import com.sw.dualscreen.utils.SPUtil
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber
@@ -9,7 +9,7 @@ import com.sw.dualscreen.model.response.DinnerTypeInfo
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.UserFaceModel
import com.sw.dualscreen.model.response.UserNutritionData
import com.sw.inbound.utils.SPUtil
import com.sw.dualscreen.utils.SPUtil
import com.sw.plate.App
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ToastUtils
-71
View File
@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:gravity="center"
android:orientation="vertical"
android:padding="8dp"> <!-- 深蓝色背景 -->
<!-- 顶部文字:数值+单位 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/valueText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textColor="@android:color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/unitText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textColor="@android:color/white"
android:textSize="10sp" />
</LinearLayout>
<!-- 圆柱容器 -->
<FrameLayout
android:id="@+id/cylinderContainer"
android:layout_width="87dp"
android:layout_height="wrap_content">
<!-- 圆柱绘制View -->
<com.sw.dualscreen.view.CylinderDrawView
android:id="@+id/cylinderDrawView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal" />
<ImageView
android:id="@+id/topImage"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="top|center_horizontal"
android:background="@drawable/img_red_column" />
<!-- 底部火焰图片(覆盖圆柱底部) -->
<ImageView
android:id="@+id/bottomImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:src="@drawable/ic_heat" /> <!-- 替换为你的火焰图片资源 -->
</FrameLayout>
<!-- 底部文字:热量 -->
<TextView
android:id="@+id/bottomText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="热量"
android:textColor="@android:color/white"
android:textSize="16sp" />
</LinearLayout>