feat: 首页与应用壳模块
新增: - StatusBarView 自定义状态栏(圆点+信号条+电池壳,按原型图V3) - ViewPager2 左右滑动(设置页/主页,默认主页) - 主页:时钟+日期+快捷区3卡片(对接statisticsNew API) - 设置页:圆形头像+用户信息+设备信息+调试模式 - TaskApi 接口(统计+考勤) - HomePagerAdapter(View方式,避免Fragment嵌套) - 页面指示器+快捷区卡片背景drawable 修改: - HomeFragment 重写为ViewPager2容器 - NetworkModule 添加TaskApi提供者 - styles.xml 添加ConfigRow/Label/Value样式 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
package com.xiaoqu.watch.data.task
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考勤状态信息
|
||||||
|
* 对应 watchTask/myCurrentAttendance API 返回
|
||||||
|
*/
|
||||||
|
data class AttendanceInfo(
|
||||||
|
/** 工作状态 (0=工作中) */
|
||||||
|
@SerializedName("workStatus") val workStatus: Int = 0,
|
||||||
|
/** 考勤状态 (3=未上班) */
|
||||||
|
@SerializedName("workAtStatus") val workAtStatus: Int = 0
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.xiaoqu.watch.data.task
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页任务统计数据
|
||||||
|
* 对应 watchTask/statisticsNew API 返回
|
||||||
|
*/
|
||||||
|
data class TaskStatistics(
|
||||||
|
/** 接单池(待抢单) */
|
||||||
|
@SerializedName("waitForTask") val waitForTask: Int = 0,
|
||||||
|
/** 待打卡 */
|
||||||
|
@SerializedName("treatTask") val treatTask: Int = 0,
|
||||||
|
/** 待完成 */
|
||||||
|
@SerializedName("incompleteTask") val incompleteTask: Int = 0
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import com.xiaoqu.watch.network.EnvConfig
|
|||||||
import com.xiaoqu.watch.network.SignatureInterceptor
|
import com.xiaoqu.watch.network.SignatureInterceptor
|
||||||
import com.xiaoqu.watch.network.UnbindInterceptor
|
import com.xiaoqu.watch.network.UnbindInterceptor
|
||||||
import com.xiaoqu.watch.network.api.CommonApi
|
import com.xiaoqu.watch.network.api.CommonApi
|
||||||
|
import com.xiaoqu.watch.network.api.TaskApi
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -62,4 +63,10 @@ object NetworkModule {
|
|||||||
fun provideCommonApi(retrofit: Retrofit): CommonApi {
|
fun provideCommonApi(retrofit: Retrofit): CommonApi {
|
||||||
return retrofit.create(CommonApi::class.java)
|
return retrofit.create(CommonApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideTaskApi(retrofit: Retrofit): TaskApi {
|
||||||
|
return retrofit.create(TaskApi::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
app/src/main/java/com/xiaoqu/watch/network/api/TaskApi.kt
Normal file
20
app/src/main/java/com/xiaoqu/watch/network/api/TaskApi.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.xiaoqu.watch.network.api
|
||||||
|
|
||||||
|
import com.xiaoqu.watch.data.task.AttendanceInfo
|
||||||
|
import com.xiaoqu.watch.data.task.TaskStatistics
|
||||||
|
import com.xiaoqu.watch.network.ApiResponse
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务相关 API 接口
|
||||||
|
*/
|
||||||
|
interface TaskApi {
|
||||||
|
|
||||||
|
/** 首页任务统计(接单池/待打卡/待完成) */
|
||||||
|
@GET("watchTask/statisticsNew")
|
||||||
|
suspend fun getStatistics(): ApiResponse<TaskStatistics>
|
||||||
|
|
||||||
|
/** 查询当前考勤状态 */
|
||||||
|
@GET("watchTask/myCurrentAttendance")
|
||||||
|
suspend fun getAttendance(): ApiResponse<AttendanceInfo>
|
||||||
|
}
|
||||||
@@ -4,53 +4,58 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.TextView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.xiaoqu.watch.BuildConfig
|
||||||
import com.xiaoqu.watch.R
|
import com.xiaoqu.watch.R
|
||||||
import com.xiaoqu.watch.data.prefs.DevicePrefs
|
import com.xiaoqu.watch.data.prefs.DevicePrefs
|
||||||
import com.xiaoqu.watch.data.prefs.UserPrefs
|
import com.xiaoqu.watch.data.prefs.UserPrefs
|
||||||
import com.xiaoqu.watch.databinding.FragmentHomeBinding
|
import com.xiaoqu.watch.databinding.FragmentHomeBinding
|
||||||
import com.xiaoqu.watch.device.nfc.NfcController
|
|
||||||
import com.xiaoqu.watch.device.screen.ScreenController
|
|
||||||
import com.xiaoqu.watch.device.sensor.VibrationController
|
|
||||||
import com.xiaoqu.watch.device.sensor.VibrationDefaults
|
|
||||||
import com.xiaoqu.watch.event.AppEvent
|
import com.xiaoqu.watch.event.AppEvent
|
||||||
import com.xiaoqu.watch.event.EventBus
|
import com.xiaoqu.watch.event.EventBus
|
||||||
|
import com.xiaoqu.watch.network.ApiResult
|
||||||
|
import com.xiaoqu.watch.network.api.TaskApi
|
||||||
|
import com.xiaoqu.watch.network.safeApiCall
|
||||||
import com.xiaoqu.watch.ui.common.BaseFragment
|
import com.xiaoqu.watch.ui.common.BaseFragment
|
||||||
import com.xiaoqu.watch.ui.widget.NavBarHelper
|
import com.xiaoqu.watch.ui.widget.StatusBarView
|
||||||
import com.xiaoqu.watch.ui.widget.QuTipDialog
|
|
||||||
import com.xiaoqu.watch.util.DateUtil
|
import com.xiaoqu.watch.util.DateUtil
|
||||||
import com.xiaoqu.watch.util.NetworkUtil
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import timber.log.Timber
|
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 首页 Fragment
|
* 首页 Fragment(ViewPager2 容器)
|
||||||
* 当前为硬件验证 demo 页面,展示系统状态 + 硬件控制测试按钮
|
* Page 0 = 设置页,Page 1 = 主页(默认显示)
|
||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
|
|
||||||
@Inject lateinit var devicePrefs: DevicePrefs
|
@Inject lateinit var devicePrefs: DevicePrefs
|
||||||
@Inject lateinit var userPrefs: UserPrefs
|
@Inject lateinit var userPrefs: UserPrefs
|
||||||
@Inject lateinit var screenController: ScreenController
|
|
||||||
@Inject lateinit var nfcController: NfcController
|
|
||||||
@Inject lateinit var vibrationController: VibrationController
|
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
|
@Inject lateinit var taskApi: TaskApi
|
||||||
|
|
||||||
/** 提示弹窗 */
|
// ===== 主页 View 引用 =====
|
||||||
private lateinit var tipDialog: QuTipDialog
|
private lateinit var mainStatusBar: StatusBarView
|
||||||
|
private lateinit var tvClock: TextView
|
||||||
|
private lateinit var tvDate: TextView
|
||||||
|
private lateinit var tvPoolNum: TextView
|
||||||
|
private lateinit var tvPunchNum: TextView
|
||||||
|
private lateinit var tvCompleteNum: TextView
|
||||||
|
|
||||||
/** NFC 是否正在扫描 */
|
// ===== 设置页 View 引用 =====
|
||||||
private var nfcScanning = false
|
private lateinit var configStatusBar: StatusBarView
|
||||||
|
private lateinit var tvAvatarLetter: TextView
|
||||||
|
private lateinit var tvUserName: TextView
|
||||||
|
private lateinit var tvUserPhone: TextView
|
||||||
|
|
||||||
/** 当前电量和充电状态 */
|
// ===== 调试模式 =====
|
||||||
private var batteryLevel = -1
|
private var debugTapCount = 0
|
||||||
private var batteryCharging = false
|
private var lastTapTime = 0L
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
|
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
|
||||||
return FragmentHomeBinding.inflate(inflater, container, false)
|
return FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
@@ -59,158 +64,206 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
// 设置 NavBar 为首页模式(initDevicePrefs 和 MQTT 已在 SplashFragment 完成)
|
// 创建两个页面 View
|
||||||
NavBarHelper.setupHomePage(binding.root)
|
val inflater = LayoutInflater.from(requireContext())
|
||||||
|
val configPage = inflater.inflate(R.layout.page_config, null)
|
||||||
|
val mainPage = inflater.inflate(R.layout.page_main, null)
|
||||||
|
|
||||||
// 初始化弹窗
|
// 绑定 View 引用
|
||||||
val dialogContainer = requireActivity().findViewById<FrameLayout>(R.id.dialog_container)
|
bindMainViews(mainPage)
|
||||||
tipDialog = QuTipDialog(dialogContainer)
|
bindConfigViews(configPage)
|
||||||
|
|
||||||
// 显示状态信息
|
// 设置 ViewPager2
|
||||||
updateStatus()
|
val adapter = HomePagerAdapter(listOf(configPage, mainPage))
|
||||||
|
binding.viewPager.adapter = adapter
|
||||||
|
binding.viewPager.setCurrentItem(1, false) // 默认显示主页
|
||||||
|
|
||||||
// 绑定测试按钮
|
// 初始化页面数据
|
||||||
setupButtons()
|
initMainPage()
|
||||||
|
initConfigPage()
|
||||||
|
|
||||||
// 启动时间更新定时器(每秒刷新 NavBar 时间)
|
// 启动时钟定时器
|
||||||
startTimeUpdater()
|
startClockUpdater()
|
||||||
|
|
||||||
// 监听系统状态事件
|
// 加载任务统计数据
|
||||||
observeSystemEvents()
|
fetchStatistics()
|
||||||
|
|
||||||
|
// 监听 MQTT 事件
|
||||||
|
observeEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
// ===== 主页 =====
|
||||||
super.onDestroyView()
|
|
||||||
// 停止 NFC 扫描
|
/** 绑定主页 View 引用 */
|
||||||
if (nfcScanning) {
|
private fun bindMainViews(page: View) {
|
||||||
nfcController.stopScan()
|
mainStatusBar = page.findViewById(R.id.statusBar)
|
||||||
nfcScanning = false
|
tvClock = page.findViewById(R.id.tvClock)
|
||||||
|
tvDate = page.findViewById(R.id.tvDate)
|
||||||
|
tvPoolNum = page.findViewById(R.id.tvPoolNum)
|
||||||
|
tvPunchNum = page.findViewById(R.id.tvPunchNum)
|
||||||
|
tvCompleteNum = page.findViewById(R.id.tvCompleteNum)
|
||||||
|
|
||||||
|
// 快捷区卡片点击 → 跳转任务列表(后续实现)
|
||||||
|
page.findViewById<View>(R.id.cardPool)?.setOnClickListener {
|
||||||
|
Timber.d("点击接单池")
|
||||||
|
// TODO: navigate to TaskListFragment with tableStatus=2
|
||||||
|
}
|
||||||
|
page.findViewById<View>(R.id.cardPunch)?.setOnClickListener {
|
||||||
|
Timber.d("点击待打卡")
|
||||||
|
// TODO: navigate to TaskListFragment with tableStatus=3
|
||||||
|
}
|
||||||
|
page.findViewById<View>(R.id.cardComplete)?.setOnClickListener {
|
||||||
|
Timber.d("点击待完成")
|
||||||
|
// TODO: navigate to TaskListFragment with tableStatus=4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新状态信息显示(try-catch 保护,防止系统关机时 DeadSystemException) */
|
/** 初始化主页数据 */
|
||||||
private fun updateStatus() {
|
private fun initMainPage() {
|
||||||
try {
|
updateClock()
|
||||||
val dateInfo = DateUtil.getDateInfo()
|
|
||||||
val sb = StringBuilder()
|
|
||||||
sb.appendLine("${dateInfo.date} ${dateInfo.week} ${dateInfo.time}")
|
|
||||||
sb.appendLine("设备: ${devicePrefs.brand} ${devicePrefs.model}")
|
|
||||||
sb.appendLine("网络: ${NetworkUtil.getNetworkTypeName(requireContext())}")
|
|
||||||
sb.appendLine("绑定: ${if (userPrefs.isBound) "是" else "否"}")
|
|
||||||
// 电量信息(等收到广播后更新)
|
|
||||||
if (batteryLevel >= 0) {
|
|
||||||
sb.appendLine("电量: ${batteryLevel}% ${if (batteryCharging) "(充电中)" else ""}")
|
|
||||||
}
|
|
||||||
sb.appendLine("屏幕: ${if (screenController.isScreenOn()) "亮" else "灭"}")
|
|
||||||
sb.appendLine("NFC: ${if (nfcController.isOpen()) "开" else "关"}")
|
|
||||||
sb.appendLine("MQTT: 已启动")
|
|
||||||
binding.tvStatus.text = sb.toString()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.w(e, "更新状态信息异常")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绑定测试按钮 */
|
/** 更新时钟和日期 */
|
||||||
private fun setupButtons() {
|
private fun updateClock() {
|
||||||
// 熄屏测试:熄屏 3 秒后自动亮屏
|
val info = DateUtil.getDateInfo()
|
||||||
binding.btnScreenOff.setOnClickListener {
|
tvClock.text = DateUtil.formatTimeShort()
|
||||||
screenController.turnOff()
|
tvDate.text = "${info.month}月${info.day}日 ${info.week}"
|
||||||
binding.root.postDelayed({
|
|
||||||
screenController.turnOn()
|
|
||||||
updateStatus()
|
|
||||||
}, 3000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 振动测试:执行 planId=4(打卡成功)方案
|
/** 每秒更新时钟 */
|
||||||
binding.btnVibrate.setOnClickListener {
|
private fun startClockUpdater() {
|
||||||
val pattern = VibrationDefaults.getPattern(4)
|
|
||||||
if (pattern != null) {
|
|
||||||
vibrationController.executePattern(pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NFC 读卡测试:开启/关闭切换
|
|
||||||
binding.btnNfcScan.setOnClickListener {
|
|
||||||
if (nfcScanning) {
|
|
||||||
// 停止扫描
|
|
||||||
nfcController.stopScan()
|
|
||||||
nfcController.close()
|
|
||||||
nfcScanning = false
|
|
||||||
binding.btnNfcScan.text = "NFC 读卡测试"
|
|
||||||
updateStatus()
|
|
||||||
} else {
|
|
||||||
// 开始扫描
|
|
||||||
nfcController.open()
|
|
||||||
nfcController.startScan { nfcId ->
|
|
||||||
// 读到卡号,显示提示
|
|
||||||
tipDialog.show(
|
|
||||||
status = QuTipDialog.Status.SUCCESS,
|
|
||||||
title = "读到NFC卡",
|
|
||||||
desc = "卡号: $nfcId",
|
|
||||||
back = true, step = 0, countdown = 3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
nfcScanning = true
|
|
||||||
binding.btnNfcScan.text = "停止 NFC 扫描"
|
|
||||||
updateStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提示弹窗测试
|
|
||||||
binding.btnShowTip.setOnClickListener {
|
|
||||||
tipDialog.show(
|
|
||||||
status = QuTipDialog.Status.SUCCESS,
|
|
||||||
title = "操作成功",
|
|
||||||
desc = "这是一个提示弹窗 demo",
|
|
||||||
back = true, step = 0, countdown = 3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 每秒更新 NavBar 时间显示,跟随 Fragment 生命周期自动取消 */
|
|
||||||
private fun startTimeUpdater() {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
NavBarHelper.updateTime(binding.root)
|
updateClock()
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听系统状态事件(电量、蓝牙)<EFBFBD><EFBFBD><EFBFBD>更新 NavBar 和状态显示 */
|
/** 从 API 获取任务统计数据 */
|
||||||
private fun observeSystemEvents() {
|
private fun fetchStatistics() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
val result = safeApiCall { taskApi.getStatistics() }
|
||||||
|
if (result is ApiResult.Success && result.data != null) {
|
||||||
|
val data = result.data
|
||||||
|
tvPoolNum.text = data.waitForTask.toString()
|
||||||
|
tvPunchNum.text = data.treatTask.toString()
|
||||||
|
tvCompleteNum.text = data.incompleteTask.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 设置页 =====
|
||||||
|
|
||||||
|
/** 绑定设置页 View 引用 */
|
||||||
|
private fun bindConfigViews(page: View) {
|
||||||
|
configStatusBar = page.findViewById(R.id.statusBar)
|
||||||
|
tvAvatarLetter = page.findViewById(R.id.tvAvatarLetter)
|
||||||
|
tvUserName = page.findViewById(R.id.tvUserName)
|
||||||
|
tvUserPhone = page.findViewById(R.id.tvUserPhone)
|
||||||
|
|
||||||
|
// 头像点击 → 调试模式检测
|
||||||
|
page.findViewById<View>(R.id.userBlock)?.setOnClickListener {
|
||||||
|
checkDebugMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化设置页数据 */
|
||||||
|
private fun initConfigPage() {
|
||||||
|
// 用户信息
|
||||||
|
val userName = userPrefs.userName
|
||||||
|
tvAvatarLetter.text = if (userName.isNotEmpty()) userName.first().toString() else "?"
|
||||||
|
tvUserName.text = userName
|
||||||
|
// 手机号脱敏:138****8000
|
||||||
|
val mobile = userPrefs.mobile
|
||||||
|
tvUserPhone.text = if (mobile.length >= 7) {
|
||||||
|
"${mobile.substring(0, 3)}****${mobile.substring(mobile.length - 4)}"
|
||||||
|
} else {
|
||||||
|
mobile
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备信息
|
||||||
|
val configPage = (binding.viewPager.adapter as? HomePagerAdapter)?.let { return@let null }
|
||||||
|
// 通过 View 直接查找(configPage 是 ViewPager2 的第一个子 View)
|
||||||
|
binding.viewPager.post {
|
||||||
|
val recyclerView = binding.viewPager.getChildAt(0) as? androidx.recyclerview.widget.RecyclerView
|
||||||
|
val configView = recyclerView?.findViewHolderForAdapterPosition(0)?.itemView
|
||||||
|
configView?.let { fillDeviceInfo(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 填充设备信息 */
|
||||||
|
private fun fillDeviceInfo(configView: View) {
|
||||||
|
configView.findViewById<TextView>(R.id.tvModel)?.text =
|
||||||
|
"${devicePrefs.brand} ${devicePrefs.model}"
|
||||||
|
configView.findViewById<TextView>(R.id.tvOsVersion)?.text =
|
||||||
|
"Android ${devicePrefs.osVersion}"
|
||||||
|
configView.findViewById<TextView>(R.id.tvImei)?.text = run {
|
||||||
|
val imei = devicePrefs.imei
|
||||||
|
if (imei.length > 6) "${imei.substring(0, 3)}***${imei.substring(imei.length - 3)}"
|
||||||
|
else imei
|
||||||
|
}
|
||||||
|
configView.findViewById<TextView>(R.id.tvAppVersion)?.text =
|
||||||
|
"v${BuildConfig.VERSION_NAME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 调试模式:500ms 内连续点击 6 次触发 */
|
||||||
|
private fun checkDebugMode() {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastTapTime > 500) {
|
||||||
|
debugTapCount = 1
|
||||||
|
} else {
|
||||||
|
debugTapCount++
|
||||||
|
}
|
||||||
|
lastTapTime = now
|
||||||
|
|
||||||
|
if (debugTapCount >= 6) {
|
||||||
|
debugTapCount = 0
|
||||||
|
Timber.d("调试模式已开启")
|
||||||
|
android.widget.Toast.makeText(requireContext(), "调试模式已开启", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
// TODO: 打开调试页面
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 事件监听 =====
|
||||||
|
|
||||||
|
/** 监听 MQTT 和系统状态事件 */
|
||||||
|
private fun observeEvents() {
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
eventBus.events.collect { event ->
|
eventBus.events.collect { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
// 电量变化:更新 NavBar 电量图标 + 状态文字
|
// 电量变化:更新两个状态栏
|
||||||
is AppEvent.BatteryChanged -> {
|
is AppEvent.BatteryChanged -> {
|
||||||
batteryLevel = event.level
|
mainStatusBar.updateBattery(event.level, event.isCharging)
|
||||||
batteryCharging = event.isCharging
|
configStatusBar.updateBattery(event.level, event.isCharging)
|
||||||
NavBarHelper.updateBattery(binding.root, event.level, event.isCharging)
|
|
||||||
updateStatus()
|
|
||||||
}
|
}
|
||||||
// 蓝牙开关:更新 NavBar 蓝牙图标
|
// 蓝牙状态变化
|
||||||
is AppEvent.BluetoothStateChanged -> {
|
is AppEvent.BluetoothStateChanged -> {
|
||||||
NavBarHelper.updateBluetooth(binding.root, event.isOn)
|
mainStatusBar.updateBluetooth(event.isOn)
|
||||||
updateStatus()
|
configStatusBar.updateBluetooth(event.isOn)
|
||||||
}
|
}
|
||||||
// 蓝牙连接/断开
|
// MQTT 消息
|
||||||
is AppEvent.BluetoothDeviceConnected -> updateStatus()
|
|
||||||
is AppEvent.BluetoothDeviceDisconnected -> updateStatus()
|
|
||||||
// MQTT 连接/断连/消息
|
|
||||||
is AppEvent.MqttConnected -> updateStatus()
|
|
||||||
is AppEvent.MqttDisconnected -> updateStatus()
|
|
||||||
is AppEvent.MqttMessageReceived -> {
|
is AppEvent.MqttMessageReceived -> {
|
||||||
Timber.d("MQTT消息: type=${event.type}")
|
when (event.type) {
|
||||||
// messageType=3: 解绑 → 清除用户数据,导航到绑定页
|
0 -> {
|
||||||
if (event.type == 3) {
|
// 新任务消息 → 刷新统计数据
|
||||||
|
Timber.d("首页: 收到新任务消息")
|
||||||
|
fetchStatistics()
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
// 解绑 → 清除数据 → 跳绑定页
|
||||||
|
Timber.d("首页: 收到解绑消息")
|
||||||
userPrefs.clear()
|
userPrefs.clear()
|
||||||
findMainNavController().navigate(R.id.action_home_to_bind)
|
findNavController().navigate(R.id.action_home_to_bind)
|
||||||
return@collect
|
|
||||||
}
|
}
|
||||||
updateStatus()
|
4 -> {
|
||||||
|
// 工作状态变更
|
||||||
|
Timber.d("首页: 收到工作状态变更")
|
||||||
|
// TODO: 更新考勤状态,控制蓝牙扫描
|
||||||
}
|
}
|
||||||
else -> {} // 其他事件不处理
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.xiaoqu.watch.ui.home
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页 ViewPager2 适配器
|
||||||
|
* 两个页面:Page 0 = 设置页,Page 1 = 主页
|
||||||
|
* 使用 View(非 Fragment)避免嵌套导航问题
|
||||||
|
*/
|
||||||
|
class HomePagerAdapter(
|
||||||
|
private val pages: List<View>
|
||||||
|
) : RecyclerView.Adapter<HomePagerAdapter.PageViewHolder>() {
|
||||||
|
|
||||||
|
class PageViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
|
||||||
|
return PageViewHolder(pages[viewType])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
|
||||||
|
// View 已在创建时设置好,无需额外绑定
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = pages.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int = position
|
||||||
|
}
|
||||||
146
app/src/main/java/com/xiaoqu/watch/ui/widget/StatusBarView.kt
Normal file
146
app/src/main/java/com/xiaoqu/watch/ui/widget/StatusBarView.kt
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package com.xiaoqu.watch.ui.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import com.xiaoqu.watch.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义状态栏 View(按原型图 V3 样式)
|
||||||
|
* 左侧:蓝牙圆点 + 4G 信号条
|
||||||
|
* 右侧:电池壳 + 填充条
|
||||||
|
*
|
||||||
|
* 使用方式:在布局中引入,通过 update* 方法更新状态
|
||||||
|
*/
|
||||||
|
class StatusBarView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : View(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
// ===== 状态数据 =====
|
||||||
|
private var bluetoothOn = true
|
||||||
|
private var signalLevel = 4 // 0-4
|
||||||
|
private var batteryLevel = 75 // 0-100
|
||||||
|
private var isCharging = false
|
||||||
|
|
||||||
|
// ===== 画笔 =====
|
||||||
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||||
|
|
||||||
|
// ===== 颜色常量 =====
|
||||||
|
private val colorGreen = context.getColor(R.color.success)
|
||||||
|
private val colorOrange = context.getColor(R.color.warning)
|
||||||
|
private val colorRed = context.getColor(R.color.error)
|
||||||
|
private val colorWhite = 0xFFFFFFFF.toInt()
|
||||||
|
private val colorDim = 0x26FFFFFF // 白色 15% 透明度
|
||||||
|
private val colorBorder = 0x59FFFFFF // 白色 35% 透明度
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
val h = height.toFloat()
|
||||||
|
val centerY = h / 2
|
||||||
|
|
||||||
|
// ===== 左侧:蓝牙圆点 + 信号条 =====
|
||||||
|
drawBluetoothDot(canvas, 8f, centerY)
|
||||||
|
drawSignalBars(canvas, 22f, centerY)
|
||||||
|
|
||||||
|
// ===== 右侧:电池 =====
|
||||||
|
drawBattery(canvas, width - 48f, centerY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 绘制蓝牙状态圆点 */
|
||||||
|
private fun drawBluetoothDot(canvas: Canvas, x: Float, centerY: Float) {
|
||||||
|
val radius = 5f
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.color = if (bluetoothOn) colorGreen else colorDim
|
||||||
|
canvas.drawCircle(x, centerY, radius, paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 绘制 4G 信号条(4 个递增高度的竖条) */
|
||||||
|
private fun drawSignalBars(canvas: Canvas, startX: Float, centerY: Float) {
|
||||||
|
val barWidth = 3f
|
||||||
|
val gap = 2f
|
||||||
|
val maxHeight = 16f
|
||||||
|
val heights = floatArrayOf(5f, 8f, 12f, maxHeight)
|
||||||
|
|
||||||
|
for (i in 0..3) {
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.color = if (i < signalLevel) colorWhite else colorDim
|
||||||
|
val x = startX + i * (barWidth + gap)
|
||||||
|
val barH = heights[i]
|
||||||
|
val top = centerY + maxHeight / 2 - barH
|
||||||
|
val rect = RectF(x, top, x + barWidth, centerY + maxHeight / 2)
|
||||||
|
canvas.drawRoundRect(rect, 1.5f, 1.5f, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 绘制电池图标(壳 + 填充条 + 凸起) */
|
||||||
|
private fun drawBattery(canvas: Canvas, startX: Float, centerY: Float) {
|
||||||
|
val shellW = 28f
|
||||||
|
val shellH = 13f
|
||||||
|
val shellTop = centerY - shellH / 2
|
||||||
|
val cornerR = 3.5f
|
||||||
|
|
||||||
|
// 电池壳边框
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.strokeWidth = 1.5f
|
||||||
|
paint.color = colorBorder
|
||||||
|
val shellRect = RectF(startX, shellTop, startX + shellW, shellTop + shellH)
|
||||||
|
canvas.drawRoundRect(shellRect, cornerR, cornerR, paint)
|
||||||
|
|
||||||
|
// 电池填充条
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
paint.color = when {
|
||||||
|
isCharging -> colorGreen
|
||||||
|
batteryLevel <= 10 -> colorRed
|
||||||
|
batteryLevel <= 20 -> colorOrange
|
||||||
|
else -> colorGreen
|
||||||
|
}
|
||||||
|
val fillPadding = 2.5f
|
||||||
|
val fillMaxW = shellW - fillPadding * 2
|
||||||
|
val fillW = fillMaxW * batteryLevel / 100f
|
||||||
|
val fillRect = RectF(
|
||||||
|
startX + fillPadding,
|
||||||
|
shellTop + fillPadding,
|
||||||
|
startX + fillPadding + fillW,
|
||||||
|
shellTop + shellH - fillPadding
|
||||||
|
)
|
||||||
|
canvas.drawRoundRect(fillRect, 2f, 2f, paint)
|
||||||
|
|
||||||
|
// 电池凸起(右侧小矩形)
|
||||||
|
paint.color = colorBorder
|
||||||
|
val nubW = 2.5f
|
||||||
|
val nubH = 6f
|
||||||
|
val nubRect = RectF(
|
||||||
|
startX + shellW + 1f,
|
||||||
|
centerY - nubH / 2,
|
||||||
|
startX + shellW + 1f + nubW,
|
||||||
|
centerY + nubH / 2
|
||||||
|
)
|
||||||
|
canvas.drawRoundRect(nubRect, 1f, 1f, paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 更新方法 =====
|
||||||
|
|
||||||
|
/** 更新蓝牙状态 */
|
||||||
|
fun updateBluetooth(isOn: Boolean) {
|
||||||
|
bluetoothOn = isOn
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新信号强度 (0-4) */
|
||||||
|
fun updateSignal(level: Int) {
|
||||||
|
signalLevel = level.coerceIn(0, 4)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新电池状态 */
|
||||||
|
fun updateBattery(level: Int, charging: Boolean) {
|
||||||
|
batteryLevel = level.coerceIn(0, 100)
|
||||||
|
isCharging = charging
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
9
app/src/main/res/drawable/bg_avatar.xml
Normal file
9
app/src/main/res/drawable/bg_avatar.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 设置页头像:蓝绿渐变圆形背景 -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<gradient
|
||||||
|
android:startColor="#FF3B9EFF"
|
||||||
|
android:endColor="#FF64D2FF"
|
||||||
|
android:angle="135" />
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_quick_blue.xml
Normal file
7
app/src/main/res/drawable/bg_quick_blue.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="#733B9EFF" />
|
||||||
|
<corners android:radius="14dp" />
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_quick_green.xml
Normal file
7
app/src/main/res/drawable/bg_quick_green.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="#734ADE80" />
|
||||||
|
<corners android:radius="14dp" />
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_quick_orange.xml
Normal file
7
app/src/main/res/drawable/bg_quick_orange.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="#73FFB340" />
|
||||||
|
<corners android:radius="14dp" />
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/indicator_dot_active.xml
Normal file
7
app/src/main/res/drawable/indicator_dot_active.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 页面指示器:当前页(白色 85%,圆角矩形) -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#D9FFFFFF" />
|
||||||
|
<corners android:radius="3dp" />
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/indicator_dot_inactive.xml
Normal file
6
app/src/main/res/drawable/indicator_dot_inactive.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 页面指示器:非当前页(白色 15%,圆形) -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#26FFFFFF" />
|
||||||
|
</shape>
|
||||||
@@ -1,67 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- 首页布局:NavBar + 硬件验证 demo -->
|
<!-- 首页容器:ViewPager2 左右滑动(设置页 / 主页) -->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
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:background="@color/background">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 顶部导航栏 -->
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
<include layout="@layout/layout_nav_bar" />
|
android:id="@+id/viewPager"
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<LinearLayout
|
</FrameLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="@dimen/safe_area_left"
|
|
||||||
android:paddingEnd="@dimen/safe_area_right"
|
|
||||||
android:paddingBottom="@dimen/safe_area_bottom">
|
|
||||||
|
|
||||||
<!-- 系统状态信息 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvStatus"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="@color/text_primary"
|
|
||||||
android:textSize="@dimen/text_small"
|
|
||||||
android:lineSpacingExtra="3dp"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_sm" />
|
|
||||||
|
|
||||||
<!-- 屏幕控制测试 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/btnScreenOff"
|
|
||||||
style="@style/ActionButton.Warning"
|
|
||||||
android:text="熄屏测试(3秒后亮屏)"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_sm" />
|
|
||||||
|
|
||||||
<!-- 振动测试 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/btnVibrate"
|
|
||||||
style="@style/ActionButton.Primary"
|
|
||||||
android:text="振动测试"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_sm" />
|
|
||||||
|
|
||||||
<!-- NFC 测试 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/btnNfcScan"
|
|
||||||
style="@style/ActionButton.Success"
|
|
||||||
android:text="NFC 读卡测试"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_sm" />
|
|
||||||
|
|
||||||
<!-- 弹窗测试 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/btnShowTip"
|
|
||||||
style="@style/ActionButton.Grey"
|
|
||||||
android:text="提示弹窗测试"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_sm" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|||||||
141
app/src/main/res/layout/page_config.xml
Normal file
141
app/src/main/res/layout/page_config.xml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 设置页(ViewPager2 Page 0):状态栏 + 用户信息 + 设备信息 + 手表信息入口 -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/safe_area_left"
|
||||||
|
android:paddingTop="@dimen/safe_area_top"
|
||||||
|
android:paddingEnd="@dimen/safe_area_right"
|
||||||
|
android:paddingBottom="@dimen/safe_area_bottom">
|
||||||
|
|
||||||
|
<!-- 自定义状态栏 -->
|
||||||
|
<com.xiaoqu.watch.ui.widget.StatusBarView
|
||||||
|
android:id="@+id/statusBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="18dp" />
|
||||||
|
|
||||||
|
<!-- 可滚动内容区 -->
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:scrollbars="none">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 用户信息区域(居中,可点击触发调试模式) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/userBlock"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<!-- 圆形头像(渐变背景 + 首字母) -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginBottom="6dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bg_avatar" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAvatarLetter"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- 姓名 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUserName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- 手机号(脱敏) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUserPhone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginTop="2dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 设备信息列表 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 设备型号 -->
|
||||||
|
<LinearLayout style="@style/ConfigRow">
|
||||||
|
<TextView style="@style/ConfigLabel" android:text="设备型号" />
|
||||||
|
<TextView android:id="@+id/tvModel" style="@style/ConfigValue" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 系统版本 -->
|
||||||
|
<LinearLayout style="@style/ConfigRow">
|
||||||
|
<TextView style="@style/ConfigLabel" android:text="系统版本" />
|
||||||
|
<TextView android:id="@+id/tvOsVersion" style="@style/ConfigValue" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- IMEI -->
|
||||||
|
<LinearLayout style="@style/ConfigRow">
|
||||||
|
<TextView style="@style/ConfigLabel" android:text="IMEI" />
|
||||||
|
<TextView android:id="@+id/tvImei" style="@style/ConfigValue" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- App 版本 -->
|
||||||
|
<LinearLayout style="@style/ConfigRow">
|
||||||
|
<TextView style="@style/ConfigLabel" android:text="App 版本" />
|
||||||
|
<TextView android:id="@+id/tvAppVersion" style="@style/ConfigValue" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- 页面指示器 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/indConfig0"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/indicator_dot_active" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/indConfig1"
|
||||||
|
android:layout_width="5dp"
|
||||||
|
android:layout_height="5dp"
|
||||||
|
android:background="@drawable/indicator_dot_inactive" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
176
app/src/main/res/layout/page_main.xml
Normal file
176
app/src/main/res/layout/page_main.xml
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 主页(ViewPager2 Page 1):状态栏 + 时钟 + 日期 + 快捷区 + 指示器
|
||||||
|
用户群体为老年人,字体尽可能大 -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/safe_area_left"
|
||||||
|
android:paddingTop="@dimen/safe_area_top"
|
||||||
|
android:paddingEnd="@dimen/safe_area_right"
|
||||||
|
android:paddingBottom="@dimen/safe_area_bottom">
|
||||||
|
|
||||||
|
<!-- 自定义状态栏(蓝牙圆点 + 信号条 + 电池壳) -->
|
||||||
|
<com.xiaoqu.watch.ui.widget.StatusBarView
|
||||||
|
android:id="@+id/statusBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:layout_marginBottom="2dp" />
|
||||||
|
|
||||||
|
<!-- 时钟区域(居中撑满) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 时钟 HH:mm -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvClock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="14:30"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:letterSpacing="0.05" />
|
||||||
|
|
||||||
|
<!-- 日期 + 星期 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="4月23日 周三"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:layout_marginTop="6dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 快捷区:3 个彩色卡片 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<!-- 接单池(蓝色) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/cardPool"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:background="@drawable/bg_quick_blue"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPoolNum"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="@color/primary"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="接单池"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 待打卡(橙色) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/cardPunch"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:background="@drawable/bg_quick_orange"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPunchNum"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="@color/warning"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="待打卡"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 待完成(绿色) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/cardComplete"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:background="@drawable/bg_quick_green"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCompleteNum"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="@color/success"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="待完成"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 页面指示器 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/indMain0"
|
||||||
|
android:layout_width="5dp"
|
||||||
|
android:layout_height="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/indicator_dot_inactive" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/indMain1"
|
||||||
|
android:layout_width="14dp"
|
||||||
|
android:layout_height="5dp"
|
||||||
|
android:background="@drawable/indicator_dot_active" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -39,4 +39,34 @@
|
|||||||
<style name="ActionButton.Half">
|
<style name="ActionButton.Half">
|
||||||
<item name="android:layout_width">@dimen/button_half_width</item>
|
<item name="android:layout_width">@dimen/button_half_width</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- ===== 设置页行样式 ===== -->
|
||||||
|
|
||||||
|
<!-- 设置页信息行容器 -->
|
||||||
|
<style name="ConfigRow">
|
||||||
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:orientation">horizontal</item>
|
||||||
|
<item name="android:gravity">center_vertical</item>
|
||||||
|
<item name="android:paddingTop">8dp</item>
|
||||||
|
<item name="android:paddingBottom">8dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- 设置页标签(左侧) -->
|
||||||
|
<style name="ConfigLabel">
|
||||||
|
<item name="android:layout_width">0dp</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:layout_weight">1</item>
|
||||||
|
<item name="android:textColor">@color/text_secondary</item>
|
||||||
|
<item name="android:textSize">13sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- 设置页值(右侧) -->
|
||||||
|
<style name="ConfigValue">
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:textColor">@color/text_primary</item>
|
||||||
|
<item name="android:textSize">13sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user