添加注释
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user