feat: 绑定页UI对齐原型图V3
- 二维码页面:标题移到上方,白色圆角背景框,说明文字居中 - 新增配对中Loading状态(spinner + "正在配对…") - 颜色值对齐原型图(blue=#3B9EFF, green=#4ADE80, orange=#FFB340, red=#FF6B6B) - 新增 bg_qr_frame.xml 白色圆角背景 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.xiaoqu.watch.ui.bind
|
package com.xiaoqu.watch.ui.bind
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -10,7 +11,6 @@ import com.google.gson.Gson
|
|||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
import com.google.zxing.MultiFormatWriter
|
import com.google.zxing.MultiFormatWriter
|
||||||
import com.google.zxing.common.BitMatrix
|
import com.google.zxing.common.BitMatrix
|
||||||
import android.graphics.Bitmap
|
|
||||||
import com.xiaoqu.watch.R
|
import com.xiaoqu.watch.R
|
||||||
import com.xiaoqu.watch.data.device.WatchBindInfo
|
import com.xiaoqu.watch.data.device.WatchBindInfo
|
||||||
import com.xiaoqu.watch.data.prefs.DevicePrefs
|
import com.xiaoqu.watch.data.prefs.DevicePrefs
|
||||||
@@ -28,8 +28,9 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备绑定页面
|
* 设备绑定页面
|
||||||
* 显示二维码供手机 APP 扫码绑定
|
* 两种状态:
|
||||||
* 监听 MQTT messageType=2 绑定成功消息
|
* 1. 二维码展示 — 等待手机 APP 扫码
|
||||||
|
* 2. 配对中 — 收到 MQTT 绑定消息后,显示 Loading,API 确认后跳转首页
|
||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class BindFragment : BaseFragment<FragmentBindBinding>() {
|
class BindFragment : BaseFragment<FragmentBindBinding>() {
|
||||||
@@ -57,10 +58,10 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
/**
|
/**
|
||||||
* 生成二维码
|
* 生成二维码
|
||||||
* 编码设备信息 JSON:{bluetoothName, imei, serial, power}
|
* 编码设备信息 JSON:{bluetoothName, imei, serial, power}
|
||||||
|
* 与旧版 codeScanPair.vue 格式一致
|
||||||
*/
|
*/
|
||||||
private fun generateQrCode() {
|
private fun generateQrCode() {
|
||||||
try {
|
try {
|
||||||
// 构建设备信息 JSON(与旧版 codeScanPair.vue 一致)
|
|
||||||
val qrData = gson.toJson(mapOf(
|
val qrData = gson.toJson(mapOf(
|
||||||
"bluetoothName" to devicePrefs.bluetoothName,
|
"bluetoothName" to devicePrefs.bluetoothName,
|
||||||
"imei" to devicePrefs.imei,
|
"imei" to devicePrefs.imei,
|
||||||
@@ -69,8 +70,8 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
))
|
))
|
||||||
Timber.d("绑定: QR 数据=$qrData")
|
Timber.d("绑定: QR 数据=$qrData")
|
||||||
|
|
||||||
// 使用 ZXing 生成二维码 Bitmap
|
// ZXing 生成二维码 Bitmap
|
||||||
val size = 500 // 生成 500×500 像素,ImageView 会缩放
|
val size = 500
|
||||||
val bitMatrix: BitMatrix = MultiFormatWriter().encode(
|
val bitMatrix: BitMatrix = MultiFormatWriter().encode(
|
||||||
qrData, BarcodeFormat.QR_CODE, size, size
|
qrData, BarcodeFormat.QR_CODE, size, size
|
||||||
)
|
)
|
||||||
@@ -82,10 +83,7 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 将 ZXing BitMatrix 转换为 Android Bitmap */
|
||||||
* 将 ZXing BitMatrix 转换为 Android Bitmap
|
|
||||||
* 黑色像素用黑色,白色像素用白色
|
|
||||||
*/
|
|
||||||
private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap {
|
private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap {
|
||||||
val width = matrix.width
|
val width = matrix.width
|
||||||
val height = matrix.height
|
val height = matrix.height
|
||||||
@@ -93,9 +91,9 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
for (y in 0 until height) {
|
for (y in 0 until height) {
|
||||||
for (x in 0 until width) {
|
for (x in 0 until width) {
|
||||||
pixels[y * width + x] = if (matrix[x, y]) {
|
pixels[y * width + x] = if (matrix[x, y]) {
|
||||||
0xFF000000.toInt() // 黑色
|
0xFF000000.toInt() // 黑色模块
|
||||||
} else {
|
} else {
|
||||||
0xFFFFFFFF.toInt() // 白色
|
0xFFFFFFFF.toInt() // 白色背景
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +102,7 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听 MQTT 绑定成功消息(messageType=2)
|
* 监听 MQTT 绑定成功消息(messageType=2)
|
||||||
* 收到后存储用户信息 → 调用 API 确认 → 导航到首页
|
* 收到后切换到配对中状态 → 存储用户信息 → API 确认 → 导航到首页
|
||||||
*/
|
*/
|
||||||
private fun observeBindEvent() {
|
private fun observeBindEvent() {
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
@@ -118,14 +116,18 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理绑定成功
|
* 处理绑定成功
|
||||||
* 1. 解析用户信息并存入 UserPrefs
|
* 1. 切换到配对中 Loading 状态
|
||||||
* 2. 调用 bindWatchConfirm API 确认
|
* 2. 解析用户信息并存入 UserPrefs
|
||||||
* 3. 导航到首页
|
* 3. 调用 bindWatchConfirm API 确认
|
||||||
|
* 4. 导航到首页
|
||||||
*/
|
*/
|
||||||
private fun handleBindSuccess(rawJson: String) {
|
private fun handleBindSuccess(rawJson: String) {
|
||||||
try {
|
try {
|
||||||
Timber.d("绑定: 收到绑定消息 $rawJson")
|
Timber.d("绑定: 收到绑定消息 $rawJson")
|
||||||
|
|
||||||
|
// 切换到配对中状态
|
||||||
|
showLoading()
|
||||||
|
|
||||||
// 解析用户信息
|
// 解析用户信息
|
||||||
val bindInfo = gson.fromJson(rawJson, WatchBindInfo::class.java)
|
val bindInfo = gson.fromJson(rawJson, WatchBindInfo::class.java)
|
||||||
|
|
||||||
@@ -137,22 +139,35 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
|
|||||||
headUrl = bindInfo.headUrl
|
headUrl = bindInfo.headUrl
|
||||||
)
|
)
|
||||||
|
|
||||||
// 调用 API 确认绑定
|
// 调用 API 确认绑定,然后导航到首页
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
val params = mapOf<String, Any>(
|
val params = mapOf<String, Any>(
|
||||||
"imei" to devicePrefs.imei,
|
"imei" to devicePrefs.imei,
|
||||||
"userId" to bindInfo.userId
|
"userId" to bindInfo.userId
|
||||||
)
|
)
|
||||||
safeApiCall { commonApi.bindWatchConfirm(params) }
|
safeApiCall { commonApi.bindWatchConfirm(params) }
|
||||||
Timber.d("绑定: 确认 API 已调用")
|
Timber.d("绑定: 确认 API 已调用,导航到首页")
|
||||||
}
|
|
||||||
|
|
||||||
// 导航到首页
|
// 导航到首页
|
||||||
Timber.d("绑定: 绑定成功,导航到首页")
|
|
||||||
findNavController().navigate(R.id.action_bind_to_home)
|
findNavController().navigate(R.id.action_bind_to_home)
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "绑定: 处理绑定消息异常")
|
Timber.e(e, "绑定: 处理绑定消息异常")
|
||||||
|
// 异常时恢复二维码显示
|
||||||
|
showQrCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 显示二维码状态 */
|
||||||
|
private fun showQrCode() {
|
||||||
|
binding.qrWrap.visibility = View.VISIBLE
|
||||||
|
binding.loadingWrap.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 显示配对中 Loading 状态 */
|
||||||
|
private fun showLoading() {
|
||||||
|
binding.qrWrap.visibility = View.GONE
|
||||||
|
binding.loadingWrap.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
app/src/main/res/drawable/bg_qr_frame.xml
Normal file
7
app/src/main/res/drawable/bg_qr_frame.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 二维码白色圆角背景框 -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#FFFFFF" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
@@ -1,45 +1,83 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- BindFragment:二维码配对页面(全屏,无 NavBar) -->
|
<!-- BindFragment:二维码配对页面(全屏,无 NavBar)
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
两种状态:qrWrap=二维码展示, loadingWrap=配对中 -->
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background">
|
||||||
|
|
||||||
|
<!-- 状态1:二维码展示(默认显示) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/qrWrap"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/background"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="@dimen/safe_area_left">
|
android:padding="@dimen/safe_area_left">
|
||||||
|
|
||||||
<!-- 二维码图片 -->
|
<!-- 标题(在二维码上方) -->
|
||||||
<ImageView
|
|
||||||
android:id="@+id/ivQrCode"
|
|
||||||
android:layout_width="160dp"
|
|
||||||
android:layout_height="160dp"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_lg"
|
|
||||||
android:contentDescription="配对二维码" />
|
|
||||||
|
|
||||||
<!-- 标题 -->
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="扫码绑定手表"
|
android:text="扫码配对"
|
||||||
android:textColor="@color/text_primary"
|
android:textColor="@color/text_primary"
|
||||||
android:textSize="@dimen/text_title"
|
android:textSize="@dimen/text_title"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:layout_marginBottom="@dimen/spacing_md" />
|
android:layout_marginBottom="@dimen/spacing_lg" />
|
||||||
|
|
||||||
|
<!-- 二维码白色圆角背景框 -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:background="@drawable/bg_qr_frame"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_marginBottom="@dimen/spacing_md">
|
||||||
|
|
||||||
|
<!-- 二维码图片 -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivQrCode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="配对二维码"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- 说明文字 -->
|
<!-- 说明文字 -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="1. 下载小趣智品APP"
|
android:gravity="center"
|
||||||
|
android:lineSpacingMultiplier="1.7"
|
||||||
|
android:text="扫码下载小趣智慧清洁App\n在App中扫码添加手表"
|
||||||
android:textColor="@color/text_secondary"
|
android:textColor="@color/text_secondary"
|
||||||
android:textSize="@dimen/text_caption"
|
android:textSize="@dimen/text_small" />
|
||||||
android:layout_marginBottom="@dimen/spacing_xs" />
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 状态2:配对中(默认隐藏) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loadingWrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<!-- Loading spinner -->
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:indeterminateTint="@color/text_secondary"
|
||||||
|
android:layout_marginBottom="@dimen/spacing_md" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="2. 在APP中扫此码添加手表"
|
android:text="正在配对…"
|
||||||
android:textColor="@color/text_secondary"
|
android:textColor="@color/text_secondary"
|
||||||
android:textSize="@dimen/text_caption" />
|
android:textSize="@dimen/text_caption" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<!-- 全局背景 -->
|
<!-- 全局背景 -->
|
||||||
<color name="background">#FF000000</color>
|
<color name="background">#FF000000</color>
|
||||||
|
|
||||||
<!-- 主题色 -->
|
<!-- 主题色(对齐原型图 V3) -->
|
||||||
<color name="primary">#FF007AFF</color>
|
<color name="primary">#FF3B9EFF</color>
|
||||||
<color name="action_primary">#FF339AFB</color>
|
<color name="action_primary">#FF3B9EFF</color>
|
||||||
|
|
||||||
<!-- 状态色 -->
|
<!-- 状态色(对齐原型图 V3) -->
|
||||||
<color name="success">#FF1CC46B</color>
|
<color name="success">#FF4ADE80</color>
|
||||||
<color name="warning">#FFEB9A26</color>
|
<color name="warning">#FFFFB340</color>
|
||||||
<color name="error">#FFDA5050</color>
|
<color name="error">#FFFF6B6B</color>
|
||||||
|
|
||||||
<!-- 按钮色 -->
|
<!-- 按钮色 -->
|
||||||
<color name="grey_button">#FF666666</color>
|
<color name="grey_button">#FF666666</color>
|
||||||
|
|||||||
Reference in New Issue
Block a user