From e07a2242a9ff354226a6d51313184901de381e7a Mon Sep 17 00:00:00 2001 From: dongliang Date: Mon, 27 Apr 2026 18:37:06 +0930 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A6=96=E9=A1=B5=E4=B8=8E=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=A3=B3=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增: - 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) --- .../xiaoqu/watch/data/task/AttendanceInfo.kt | 14 + .../xiaoqu/watch/data/task/TaskStatistics.kt | 16 + .../java/com/xiaoqu/watch/di/NetworkModule.kt | 7 + .../com/xiaoqu/watch/network/api/TaskApi.kt | 20 ++ .../com/xiaoqu/watch/ui/home/HomeFragment.kt | 337 ++++++++++-------- .../xiaoqu/watch/ui/home/HomePagerAdapter.kt | 29 ++ .../xiaoqu/watch/ui/widget/StatusBarView.kt | 146 ++++++++ app/src/main/res/drawable/bg_avatar.xml | 9 + app/src/main/res/drawable/bg_quick_blue.xml | 7 + app/src/main/res/drawable/bg_quick_green.xml | 7 + app/src/main/res/drawable/bg_quick_orange.xml | 7 + .../res/drawable/indicator_dot_active.xml | 7 + .../res/drawable/indicator_dot_inactive.xml | 6 + app/src/main/res/layout/fragment_home.xml | 68 +--- app/src/main/res/layout/page_config.xml | 141 ++++++++ app/src/main/res/layout/page_main.xml | 176 +++++++++ app/src/main/res/values/styles.xml | 30 ++ 17 files changed, 824 insertions(+), 203 deletions(-) create mode 100644 app/src/main/java/com/xiaoqu/watch/data/task/AttendanceInfo.kt create mode 100644 app/src/main/java/com/xiaoqu/watch/data/task/TaskStatistics.kt create mode 100644 app/src/main/java/com/xiaoqu/watch/network/api/TaskApi.kt create mode 100644 app/src/main/java/com/xiaoqu/watch/ui/home/HomePagerAdapter.kt create mode 100644 app/src/main/java/com/xiaoqu/watch/ui/widget/StatusBarView.kt create mode 100644 app/src/main/res/drawable/bg_avatar.xml create mode 100644 app/src/main/res/drawable/bg_quick_blue.xml create mode 100644 app/src/main/res/drawable/bg_quick_green.xml create mode 100644 app/src/main/res/drawable/bg_quick_orange.xml create mode 100644 app/src/main/res/drawable/indicator_dot_active.xml create mode 100644 app/src/main/res/drawable/indicator_dot_inactive.xml create mode 100644 app/src/main/res/layout/page_config.xml create mode 100644 app/src/main/res/layout/page_main.xml diff --git a/app/src/main/java/com/xiaoqu/watch/data/task/AttendanceInfo.kt b/app/src/main/java/com/xiaoqu/watch/data/task/AttendanceInfo.kt new file mode 100644 index 0000000..a4f2664 --- /dev/null +++ b/app/src/main/java/com/xiaoqu/watch/data/task/AttendanceInfo.kt @@ -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 +) diff --git a/app/src/main/java/com/xiaoqu/watch/data/task/TaskStatistics.kt b/app/src/main/java/com/xiaoqu/watch/data/task/TaskStatistics.kt new file mode 100644 index 0000000..84db6ee --- /dev/null +++ b/app/src/main/java/com/xiaoqu/watch/data/task/TaskStatistics.kt @@ -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 +) diff --git a/app/src/main/java/com/xiaoqu/watch/di/NetworkModule.kt b/app/src/main/java/com/xiaoqu/watch/di/NetworkModule.kt index 60642a4..49e5471 100644 --- a/app/src/main/java/com/xiaoqu/watch/di/NetworkModule.kt +++ b/app/src/main/java/com/xiaoqu/watch/di/NetworkModule.kt @@ -6,6 +6,7 @@ import com.xiaoqu.watch.network.EnvConfig import com.xiaoqu.watch.network.SignatureInterceptor import com.xiaoqu.watch.network.UnbindInterceptor import com.xiaoqu.watch.network.api.CommonApi +import com.xiaoqu.watch.network.api.TaskApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -62,4 +63,10 @@ object NetworkModule { fun provideCommonApi(retrofit: Retrofit): CommonApi { return retrofit.create(CommonApi::class.java) } + + @Provides + @Singleton + fun provideTaskApi(retrofit: Retrofit): TaskApi { + return retrofit.create(TaskApi::class.java) + } } diff --git a/app/src/main/java/com/xiaoqu/watch/network/api/TaskApi.kt b/app/src/main/java/com/xiaoqu/watch/network/api/TaskApi.kt new file mode 100644 index 0000000..00334b1 --- /dev/null +++ b/app/src/main/java/com/xiaoqu/watch/network/api/TaskApi.kt @@ -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 + + /** 查询当前考勤状态 */ + @GET("watchTask/myCurrentAttendance") + suspend fun getAttendance(): ApiResponse +} diff --git a/app/src/main/java/com/xiaoqu/watch/ui/home/HomeFragment.kt b/app/src/main/java/com/xiaoqu/watch/ui/home/HomeFragment.kt index d8188ce..7cadbcf 100644 --- a/app/src/main/java/com/xiaoqu/watch/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/xiaoqu/watch/ui/home/HomeFragment.kt @@ -4,53 +4,58 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout +import android.widget.TextView import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.xiaoqu.watch.BuildConfig import com.xiaoqu.watch.R import com.xiaoqu.watch.data.prefs.DevicePrefs import com.xiaoqu.watch.data.prefs.UserPrefs 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.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.widget.NavBarHelper -import com.xiaoqu.watch.ui.widget.QuTipDialog +import com.xiaoqu.watch.ui.widget.StatusBarView import com.xiaoqu.watch.util.DateUtil -import com.xiaoqu.watch.util.NetworkUtil import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.delay -import timber.log.Timber import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject /** - * 首页 Fragment - * 当前为硬件验证 demo 页面,展示系统状态 + 硬件控制测试按钮 + * 首页 Fragment(ViewPager2 容器) + * Page 0 = 设置页,Page 1 = 主页(默认显示) */ @AndroidEntryPoint class HomeFragment : BaseFragment() { @Inject lateinit var devicePrefs: DevicePrefs @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 taskApi: TaskApi - /** 提示弹窗 */ - private lateinit var tipDialog: QuTipDialog + // ===== 主页 View 引用 ===== + 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 是否正在扫描 */ - private var nfcScanning = false + // ===== 设置页 View 引用 ===== + 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 batteryCharging = false + // ===== 调试模式 ===== + private var debugTapCount = 0 + private var lastTapTime = 0L override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding { return FragmentHomeBinding.inflate(inflater, container, false) @@ -59,158 +64,206 @@ class HomeFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // 设置 NavBar 为首页模式(initDevicePrefs 和 MQTT 已在 SplashFragment 完成) - NavBarHelper.setupHomePage(binding.root) + // 创建两个页面 View + val inflater = LayoutInflater.from(requireContext()) + val configPage = inflater.inflate(R.layout.page_config, null) + val mainPage = inflater.inflate(R.layout.page_main, null) - // 初始化弹窗 - val dialogContainer = requireActivity().findViewById(R.id.dialog_container) - tipDialog = QuTipDialog(dialogContainer) + // 绑定 View 引用 + bindMainViews(mainPage) + bindConfigViews(configPage) - // 显示状态信息 - updateStatus() + // 设置 ViewPager2 + 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 扫描 - if (nfcScanning) { - nfcController.stopScan() - nfcScanning = false + // ===== 主页 ===== + + /** 绑定主页 View 引用 */ + private fun bindMainViews(page: View) { + mainStatusBar = page.findViewById(R.id.statusBar) + 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(R.id.cardPool)?.setOnClickListener { + Timber.d("点击接单池") + // TODO: navigate to TaskListFragment with tableStatus=2 + } + page.findViewById(R.id.cardPunch)?.setOnClickListener { + Timber.d("点击待打卡") + // TODO: navigate to TaskListFragment with tableStatus=3 + } + page.findViewById(R.id.cardComplete)?.setOnClickListener { + Timber.d("点击待完成") + // TODO: navigate to TaskListFragment with tableStatus=4 } } - /** 更新状态信息显示(try-catch 保护,防止系统关机时 DeadSystemException) */ - private fun updateStatus() { - try { - 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 initMainPage() { + updateClock() } - /** 绑定测试按钮 */ - private fun setupButtons() { - // 熄屏测试:熄屏 3 秒后自动亮屏 - binding.btnScreenOff.setOnClickListener { - screenController.turnOff() - binding.root.postDelayed({ - screenController.turnOn() - updateStatus() - }, 3000) - } - - // 振动测试:执行 planId=4(打卡成功)方案 - binding.btnVibrate.setOnClickListener { - 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 - ) - } + /** 更新时钟和日期 */ + private fun updateClock() { + val info = DateUtil.getDateInfo() + tvClock.text = DateUtil.formatTimeShort() + tvDate.text = "${info.month}月${info.day}日 ${info.week}" } - /** 每秒更新 NavBar 时间显示,跟随 Fragment 生命周期自动取消 */ - private fun startTimeUpdater() { + /** 每秒更新时钟 */ + private fun startClockUpdater() { viewLifecycleOwner.lifecycleScope.launch { while (isActive) { - NavBarHelper.updateTime(binding.root) + updateClock() delay(1000) } } } - /** 监听系统状态事件(电量、蓝牙)���更新 NavBar 和状态显示 */ - private fun observeSystemEvents() { + /** 从 API 获取任务统计数据 */ + 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(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(R.id.tvModel)?.text = + "${devicePrefs.brand} ${devicePrefs.model}" + configView.findViewById(R.id.tvOsVersion)?.text = + "Android ${devicePrefs.osVersion}" + configView.findViewById(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(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 { eventBus.events.collect { event -> when (event) { - // 电量变化:更新 NavBar 电量图标 + 状态文字 + // 电量变化:更新两个状态栏 is AppEvent.BatteryChanged -> { - batteryLevel = event.level - batteryCharging = event.isCharging - NavBarHelper.updateBattery(binding.root, event.level, event.isCharging) - updateStatus() + mainStatusBar.updateBattery(event.level, event.isCharging) + configStatusBar.updateBattery(event.level, event.isCharging) } - // 蓝牙开关:更新 NavBar 蓝牙图标 + // 蓝牙状态变化 is AppEvent.BluetoothStateChanged -> { - NavBarHelper.updateBluetooth(binding.root, event.isOn) - updateStatus() + mainStatusBar.updateBluetooth(event.isOn) + configStatusBar.updateBluetooth(event.isOn) } - // 蓝牙连接/断开 - is AppEvent.BluetoothDeviceConnected -> updateStatus() - is AppEvent.BluetoothDeviceDisconnected -> updateStatus() - // MQTT 连接/断连/消息 - is AppEvent.MqttConnected -> updateStatus() - is AppEvent.MqttDisconnected -> updateStatus() + // MQTT 消息 is AppEvent.MqttMessageReceived -> { - Timber.d("MQTT消息: type=${event.type}") - // messageType=3: 解绑 → 清除用户数据,导航到绑定页 - if (event.type == 3) { - userPrefs.clear() - findMainNavController().navigate(R.id.action_home_to_bind) - return@collect + when (event.type) { + 0 -> { + // 新任务消息 → 刷新统计数据 + Timber.d("首页: 收到新任务消息") + fetchStatistics() + } + 3 -> { + // 解绑 → 清除数据 → 跳绑定页 + Timber.d("首页: 收到解绑消息") + userPrefs.clear() + findNavController().navigate(R.id.action_home_to_bind) + } + 4 -> { + // 工作状态变更 + Timber.d("首页: 收到工作状态变更") + // TODO: 更新考勤状态,控制蓝牙扫描 + } } - updateStatus() } - else -> {} // 其他事件不处理 + else -> {} } } } diff --git a/app/src/main/java/com/xiaoqu/watch/ui/home/HomePagerAdapter.kt b/app/src/main/java/com/xiaoqu/watch/ui/home/HomePagerAdapter.kt new file mode 100644 index 0000000..9d8cc81 --- /dev/null +++ b/app/src/main/java/com/xiaoqu/watch/ui/home/HomePagerAdapter.kt @@ -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 +) : RecyclerView.Adapter() { + + 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 +} diff --git a/app/src/main/java/com/xiaoqu/watch/ui/widget/StatusBarView.kt b/app/src/main/java/com/xiaoqu/watch/ui/widget/StatusBarView.kt new file mode 100644 index 0000000..faa78da --- /dev/null +++ b/app/src/main/java/com/xiaoqu/watch/ui/widget/StatusBarView.kt @@ -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() + } +} diff --git a/app/src/main/res/drawable/bg_avatar.xml b/app/src/main/res/drawable/bg_avatar.xml new file mode 100644 index 0000000..e80548c --- /dev/null +++ b/app/src/main/res/drawable/bg_avatar.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_quick_blue.xml b/app/src/main/res/drawable/bg_quick_blue.xml new file mode 100644 index 0000000..7be54db --- /dev/null +++ b/app/src/main/res/drawable/bg_quick_blue.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_quick_green.xml b/app/src/main/res/drawable/bg_quick_green.xml new file mode 100644 index 0000000..b7e96a1 --- /dev/null +++ b/app/src/main/res/drawable/bg_quick_green.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_quick_orange.xml b/app/src/main/res/drawable/bg_quick_orange.xml new file mode 100644 index 0000000..a4c30d7 --- /dev/null +++ b/app/src/main/res/drawable/bg_quick_orange.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/indicator_dot_active.xml b/app/src/main/res/drawable/indicator_dot_active.xml new file mode 100644 index 0000000..89e51d0 --- /dev/null +++ b/app/src/main/res/drawable/indicator_dot_active.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/indicator_dot_inactive.xml b/app/src/main/res/drawable/indicator_dot_inactive.xml new file mode 100644 index 0000000..73b4899 --- /dev/null +++ b/app/src/main/res/drawable/indicator_dot_inactive.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index c601981..180ce7a 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,67 +1,13 @@ - - + + android:background="@color/background"> - - - - - + android:layout_height="match_parent" /> - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/page_config.xml b/app/src/main/res/layout/page_config.xml new file mode 100644 index 0000000..8d0975a --- /dev/null +++ b/app/src/main/res/layout/page_config.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/page_main.xml b/app/src/main/res/layout/page_main.xml new file mode 100644 index 0000000..87bcd69 --- /dev/null +++ b/app/src/main/res/layout/page_main.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d8468df..378fa22 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -39,4 +39,34 @@ + + + + + + + + + + +