33 Commits
Author SHA1 Message Date
lxh2875931338 4829cd1da2 小调整,去掉部分冗余无用句 2026-10-11 16:08:45 +08:00
lxh2875931338 7989495521 深色模式代码第二轮补充 2026-10-11 16:03:06 +08:00
lxh2875931338 1d033670f7 新增夜间模式相关代码 2026-10-11 15:44:44 +08:00
lxh2875931338 2512f88f30 更新日志补充changelog 2026-09-16 22:54:42 +08:00
lxh2875931338 1f94114d26 增加电池temp_avg参数,UI端与数据库端同步实现更新 2026-09-16 22:32:46 +08:00
lxh2875931338 75e9e85848 准备更新temp_avg相关字段能力 2026-09-16 20:47:19 +08:00
lxh2875931338 68e7be98ed 1.7.0确认更新、路线进度更迭优化 2026-07-31 21:18:57 +08:00
lxh2875931338 7c5bcd22f0 关于页确定完成设置 2026-07-31 21:08:23 +08:00
lxh2875931338 a8a96c1d38 移除本项目的跨平台开发工具,并更新部分git排除信息 2026-07-31 20:47:54 +08:00
lxh2875931338 8f635e65a3 更新项目targetSDK至36 2026-07-31 20:42:26 +08:00
lxh2875931338 f83969ed9c 准备测试关于页 2026-07-31 20:39:07 +08:00
lxh2875931338 636873e449 确认回退到此版本进行重新整合开发 2026-07-31 00:32:44 +08:00
lxh2875931338 dac9529f96 数据入库增加校验再记录 2026-07-30 22:00:06 +08:00
lxh2875931338 c8717e5e1f 增加作弊数据检测 2026-07-30 21:57:08 +08:00
lxh2875931338 c85a523ec0 机型匹配情况 2026-07-21 19:21:05 +08:00
lxh2875931338 067f0d17e8 机型匹配情况说明 2026-07-21 19:19:50 +08:00
lxh2875931338 a63be9f675 20inf最低支持版本说明 2026-07-21 19:15:43 +08:00
lxh2875931338 86c38a97f5 增加机型匹配信息与适配说明 2026-07-21 19:13:27 +08:00
lxh2875931338 2026e55fde readme机型匹配列表更新 2026-07-21 18:57:35 +08:00
lxh2875931338 286162d41b readme机型匹配列表更新 2026-07-21 18:56:49 +08:00
lxh2875931338 4006684466 Merge branch 'main' of http://192.168.3.16:3800/lxh2875931338/MEIZU-Battery-Healthy 2026-07-21 18:54:50 +08:00
lxh2875931338 e5856296d4 readme机型匹配列表更新 2026-07-21 18:54:19 +08:00
lxh2875931338 e049cab9ad readme机型匹配列表更新 2026-07-21 18:49:27 +08:00
lxh2875931338 2b3d9471cd 补充版本号更新:1.6.0-Release 2026-07-21 18:39:26 +08:00
lxh2875931338 76fd0417e7 紧急补充:魅族21note字段更新 2026-07-21 00:09:47 +08:00
lxh2875931338 471d8310d7 更新日志 2026-07-20 23:59:33 +08:00
lxh2875931338 92f6c30699 更新日志说明 2026-07-20 23:58:48 +08:00
lxh2875931338 2e438aa089 针对readme部分做性能优化,目标帧率120达成 2026-07-20 23:57:01 +08:00
lxh2875931338 5e755d860f 更新日志界面优化、主页部分动画调整 2026-07-20 23:34:42 +08:00
lxh2875931338 0d8c6b081d 更新日志铺路部分,dev中 2026-07-20 23:16:39 +08:00
lxh2875931338 c8866a740a 21note字段确认为乱码,撤回支持 2026-07-20 22:49:16 +08:00
lxh2875931338 41f3ea114b 21Note字段更新(字段预测) 2026-07-20 22:39:17 +08:00
lxh2875931338 b5628510f6 readme更新 2026-07-19 20:57:04 +08:00
136 changed files with 2558 additions and 4778 deletions
+1
View File
@@ -33,6 +33,7 @@ migrate_working_dir/
/build/
/coverage/
# Symbolication related
app.*.symbols
-4
View File
@@ -1,4 +0,0 @@
{
"cmake.ignoreCMakeListsMissing": true,
"gitlens.codeLens.enabled": false
}
+35 -8
View File
@@ -26,9 +26,10 @@
| :--- | :--- | :--- |
| 1.5.0 | 功率计算优化 | ✅ 已发布 |
| 1.5.2 | 部分场景字体替换 | ✅ 已发布 |
| 1.6.0 | 更新日志独立页面 | 📋近期开发 |
| 1.7.0 | 适配夜间模式 | 📋近期开发 |
| 2.0.0 | 数据看板扩展等系列更新 | 📋 远景 |
| 1.6.0 | 更新日志独立页面 | ✅ 已发布 |
| 1.7.0 | 关于页与紧急发布log文件数据反作弊能力 | ✅ 已发布 |
| 1.8.0 | 适配夜间模式 | 📋近期开发 |
| 2.0.0 | 数据看板月份显示扩展等系列更新 | 📋 远景 |
| 3.0.0 | 搭配PC上位机记录充电信息等 | 📋 远景 |
---
@@ -40,10 +41,36 @@
---
## 机型匹配情况
目前据群友反馈已知:
- 魅族22无mbattery_charger目录生成,无法实时计算相应百分比,因此已在最近版本去除相关匹配信息
- 魅族18在后续的版本中关闭了mbattery_log生成,疑似无效,老版本可能支持
- 最完美的支持版本至少可覆盖魅族20/21系机型(20inf已确认,机型匹配ID已更新)
| 机型 | 系统版本 | 容量来源 | 设计容量 | 状态 |
|---|---|---|---|---|
| 魅族 22 | Flyme 12.x | 系统未生成 `mbattery_charger` 目录 | 5510 mAh | ❌ 不支持 |
| 魅族 21 | Flyme 12.x | `learned_cap` | 4800 mAh | ✅ 与 21 Pro 同步获得支持 |
| 魅族 21 Pro | Flyme 12.x | `learned_cap` | 5050 mAh | ✅ 初始支持 |
| 魅族 21 Note | Flyme 12.x | `learned_cap` | 5500 mAh | ✅ 1.6.0 新增支持 |
| 魅族 21 Note | Flyme 10 / 11 | 日志格式异常 | — | ❌ 不支持 |
| 魅族 20 | Flyme 12.x | `remaining_cap` + `status=Full` | 4700 mAh | ✅ 1.4.0 首次支持 |
| 魅族 20 Pro | Flyme 12.x | `remaining_cap` + `status=Full` | 5000 mAh | ✅ 1.4.0 首次支持 |
| 魅族 20 INFINITY | Flyme 10.x | `remaining_cap` + `status=Full` | 4800 mAh | ✅ 1.5.3 字段匹配 |
| 魅族 18 / 18s / 18 Pro / 18s Pro / 18X | Flyme 9 | 日志停止生成 | 4000 / 4500 mAh | ❌ 无适配条件 |
| 魅族 18 / 18s / 18 Pro / 18s Pro / 18X | Flyme 10 | 不再主动适配 | 4000 / 4500 mAh | ⚠️ 实验性支持 |
| 魅族 17 / 17Pro | Flyme 8 / 9 / 10 | 等待群友提供字段信息 | 4500 / 4500mAh | ⚠️ 待验证 |
| 魅族 16th / 16th Plus / 16X / 16s / 16sPro / 16Xs / 16T | Flyme 7 / 8 / 9 | 需回退原厂固件验证 | - | ⚠️ 若新版本无日志,则变为实验性支持,不主动适配 |
### 状态说明
| 状态 | 含义 |
|---|---|
| ✅ 完美支持 | 已通过实机验证,所有字段完整解析 |
| ⚠️ 实验性支持 | 理论上可支持,但需要用户协助验证 |
| ⚠️ 待验证 | 尚未收到实机日志反馈,待确认 |
| ❌ 不支持 | 系统未生成 BMS 日志,或日志格式无法解析 |
### 特别说明
- **Flyme 版本差异**:同一机型在不同 Flyme 版本下日志格式可能不同。例如 21 Note 在 Flyme 12.x 下可正常读取 `learned_cap`,但在 Flyme 10/11 下日志格式异常。
- **日志停止生成**:部分机型在升级到新系统后(如 17/18 系升级到 Flyme 10),系统不再向 `mbattery_charger` 目录写入新日志,文件夹虽存在但停止更新。
- **适配策略**:优先支持日志格式稳定的 Flyme 12.x 机型,旧版本系统暂不主动适配,若有用户长期停留在旧版本并愿意协助测试,可酌情增加支持。
---
@@ -54,7 +81,7 @@
flutter pub get
# Debug 版本
flutter run
flutter run --debug
# Release APK
flutter build apk --release --target-platform android-arm64
+5
View File
@@ -7,6 +7,11 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
analyzer:
exclude:
- build/**
- android/**
- web/**
include: package:flutter_lints/flutter.yaml
linter:
+8 -7
View File
@@ -1,12 +1,11 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.lxh.battery_health"
compileSdk = 34
compileSdk = 36
ndkVersion = flutter.ndkVersion
compileOptions {
@@ -14,14 +13,10 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
defaultConfig {
applicationId = "com.lxh.battery_health"
minSdk = flutter.minSdkVersion
targetSdk = 34
targetSdk = 36
versionCode = flutter.versionCode
versionName = flutter.versionName
multiDexEnabled = true
@@ -64,6 +59,12 @@ android {
}
}
kotlin { // ← 新增块
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}
-44
View File
@@ -1,44 +0,0 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.lxh.battery_health"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.lxh.battery_health"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
-73
View File
@@ -1,73 +0,0 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.lxh.battery_health"
compileSdk = 34
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
defaultConfig {
applicationId = "com.lxh.battery_health"
minSdk = flutter.minSdkVersion
targetSdk = 34
versionCode = flutter.versionCode
versionName = flutter.versionName
multiDexEnabled = true
}
signingConfigs {
// 如果没有正式签名,可以用 debug 签名代替(测试用)
// 正式发布时请替换为自己的密钥
getByName("debug") {
// debug 签名已经存在,无需额外配置
}
create("release") {
// 如果有正式签名,在这里配置
// keyAlias = "..."
// keyPassword = "..."
// storeFile = file("...")
// storePassword = "..."
}
}
buildTypes {
debug {
// 🟢 关键:debug 版本包名加 .dev 后缀
applicationIdSuffix = ".dev"
signingConfig = signingConfigs.getByName("debug")
isDebuggable = true
isMinifyEnabled = false
isShrinkResources = false
}
release {
// release 版本不加后缀,保持原包名
signingConfig = signingConfigs.getByName("debug") // 若没有正式签名,先用 debug 签名
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
flutter {
source = "../.."
}
dependencies {
// 无需额外添加 Activity KTX,因为已改用 startActivityForResult
}
+79
View File
@@ -0,0 +1,79 @@
# ============ Flutter 核心 ============
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.embedding.** { *; }
# 保留 Flutter 的 JNI 调用类
-keep class io.flutter.embedding.engine.FlutterJNI { *; }
-dontwarn io.flutter.embedding.**
# ============ Kotlin ============
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
# ============ sqflite / SQLite ============
-keep class com.tekartik.sqflite.** { *; }
-dontwarn com.tekartik.sqflite.**
# ============ shared_preferences ============
-keep class io.flutter.plugins.sharedpreferences.** { *; }
# ============ path_provider ============
-keep class io.flutter.plugins.pathprovider.** { *; }
# ============ permission_handler ============
-keep class com.baseflow.permissionhandler.** { *; }
# ============ url_launcher ============
-keep class io.flutter.plugins.urllauncher.** { *; }
# ============ device_info_plus ============
-keep class dev.fluttercommunity.plus.device_info.** { *; }
# ============ 你的原生通道(重要!) ============
# 你的 MethodChannel / EventChannel 相关类需要保留
-keep class com.lxh.battery_health.** { *; }
# ============ AndroidX 常见 ============
-keep class androidx.lifecycle.** { *; }
-dontwarn androidx.lifecycle.**
# ============ 保留 Parcelable / Serializable ============
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# ============ 异常堆栈保留(方便排查 release 崩溃) ============
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes EnclosingMethod
# ============ 移除日志(可选) ============
# 如果你想在 release 里保留日志,注释掉下面这几行
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
public static *** i(...);
}
+8
View File
@@ -45,6 +45,14 @@
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
</queries>
</manifest>
@@ -7,10 +7,12 @@ import android.content.IntentFilter
import android.content.SharedPreferences
import android.net.Uri
import android.os.BatteryManager
import android.app.Activity
import android.os.Build
import android.os.Bundle
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import android.view.Window // 🟢 必须包含
import androidx.activity.ComponentActivity
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
@@ -32,6 +34,9 @@ class MainActivity : FlutterActivity() {
private val PREF_NAME = "battery_health_prefs"
private val KEY_SAF_URI = "saf_uri"
// 🟢 刷新率控制通道
private val FRAME_RATE_CHANNEL = "com.lxh.battery_health/frame_rate"
private val chargeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent ?: return
@@ -74,7 +79,6 @@ class MainActivity : FlutterActivity() {
try {
safUri = Uri.parse(uriString)
android.util.Log.d("BatteryHealth", "通过 MethodChannel 恢复 URI: $safUri")
// 同时保存到 SharedPreferences
val prefs: SharedPreferences = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
prefs.edit().putString(KEY_SAF_URI, uriString).apply()
} catch (e: Exception) {
@@ -132,7 +136,7 @@ class MainActivity : FlutterActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FOLDER_PICKER_REQUEST && resultCode == ComponentActivity.RESULT_OK) {
if (requestCode == FOLDER_PICKER_REQUEST && resultCode == RESULT_OK) {
val uri = data?.data
if (uri != null) {
try {
@@ -222,9 +226,34 @@ class MainActivity : FlutterActivity() {
return fileUri?.let { readFile(it) }
}
// 🟢 刷新率控制方法
// 🟢 刷新率控制方法(使用 preferredRefreshRate,兼容性更好)
private fun setHighRefreshRate() {
try {
val params = window.attributes
params.preferredRefreshRate = 120f
window.attributes = params
android.util.Log.d("BatteryHealth", "setHighRefreshRate: 设置 120Hz")
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "setHighRefreshRate 失败: ${e.message}")
}
}
private fun clearRefreshRate() {
try {
val params = window.attributes
params.preferredRefreshRate = 0f // 0 表示让系统自动决定
window.attributes = params
android.util.Log.d("BatteryHealth", "clearRefreshRate: 恢复默认")
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "clearRefreshRate 失败: ${e.message}")
}
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 充电状态通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getChargeState") {
@@ -247,6 +276,7 @@ class MainActivity : FlutterActivity() {
}
}
// SAF 通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
@@ -283,6 +313,23 @@ class MainActivity : FlutterActivity() {
}
}
// 🟢 刷新率控制通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, FRAME_RATE_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"setHighRefreshRate" -> {
setHighRefreshRate()
result.success(true)
}
"clearRefreshRate" -> {
clearRefreshRate()
result.success(true)
}
else -> result.notImplemented()
}
}
// 事件通道
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
.setStreamHandler(
object : EventChannel.StreamHandler {
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.12-all.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.3.1-bin.zip
+2 -2
View File
@@ -19,8 +19,8 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
id("com.android.application") version "9.1.0" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")
+185
View File
@@ -0,0 +1,185 @@
## 1.8.0
### 新增
- **夜间模式**:支持浅色 / 深色 / 跟随系统三种模式,可在右上角菜单切换
- **主题系统重构**:统一颜色抽象,所有界面颜色由主题管理
### 优化
- 深色主题下潘通色系重新调色,保证对比度
- AppBar 与内容区背景统一,消除浅色模式下的分界线
----
## [1.7.1] - 2026-09-16
### 新增
- **平均温度显示**:温度面板新增平均温度,与最高/最低温度同排展示(`↑ xx.x℃ ↓ xx.x℃ ⌀ xx.x℃`),更直观了解电池工作温度区间
### 优化
- **数据库升级至 v2**:新增 `temp_avg` 字段,历史数据自动迁移,每日平均温度随扫描自动入库
- **构建工具链同步**:适配最新 Flutter SDK 的 Android 构建要求(AGP 9.0 / Kotlin 2.2 / Gradle 9.3)
- **Release 打包加固**:补齐 ProGuard 混淆规则,release 版本构建更稳定
### 修复
- 修复 Release 版本因缺少混淆规则文件导致打包失败的问题
----
## [1.7.0] - 2026-07-31
### ✨ 新增功能
- **关于页面**
- 右上角三点菜单新增“关于”入口
- 展示应用 Logo、版本号、作者信息及版权声明
- 支持一键跳转源码仓库(Gitea),便于用户反馈问题
- **数据反作弊能力(紧急发布)**
- 容量数据入库前增加有效性校验,拒绝异常值污染趋势图
- 校验规则:容量低于设计容量 60% 或高于 115% 的日志数据将被自动过滤
- UI 层同步增加异常状态检测,容量或电压异常时相关数值自动标红警示
- 该机制同时作用于历史日志扫描与实时数据读取,确保统计结果真实可信
### 🔧 问题修复
- 修复部分机型上 `url_launcher` 无法正常打开链接的问题(Android 11+ 包可见性适配)
----
## [1.6.0] - 2026-07-20
### ✨ 新增
- **魅族 21 Note 机型适配**:新增 `21 Note` 特征字识别,设计容量 5500mAh
- 21 Note 运行 Flyme 12.6 版本,日志格式与 21 系列一致
- 支持 `learned_cap` 字段直接读取,与 21 / 21 Pro 逻辑相同
- 已加入 `DeviceMatching` 机型映射表
- **更新日志页面**:内置 `CHANGELOG.md` 渲染,版本历史一目了然
- 使用 `flutter_markdown` 解析,支持标准 Markdown 格式
- 滚动进度条:实时显示阅读进度,提升浏览体验
- 进入页面自动请求 120Hz 高刷新率,滑动更流畅(Flyme LTPO 适配)
- 滚动时呼吸动画维持高刷感知,停止后自动恢复省电策略
- **AppBar 菜单重构**:刷新按钮左移,右侧新增三点菜单
- `更新日志`:已可用
- `深色模式`:占位(1.7.0 实现)
- `关于`:占位(1.7.0 实现)
- **原生层高刷控制**:新增 `MethodChannel` 通道,支持 Dart 侧动态申请/释放 120Hz
- 进入更新日志页面自动触发
- 退出页面自动释放,不影响系统省电策略
### ✅ 已确认完美支持(更新)
| 机型 | 容量来源 | 设计容量 |
|---|---|---|
| 魅族 20 / 20 Pro / 20 INFINITY | `remaining_cap` + `status=Full` | 4700 / 5000 / 4800 mAh |
| 魅族 21 / 21 Pro | `learned_cap` | 4800 / 5050 mAh |
| **魅族 21 Note(新增)** | `learned_cap` | **5500 mAh** |
### ⚡ 优化
- **UI 拆分**:将 `BatteryHealthPage` 的 UI 部分抽离至独立文件,降低耦合度
- **动画简化**:移除复合动画控制器(`AnimationController`),改用轻量 `AnimatedOpacity` 控制充电条目显隐
- **更新日志性能**:缓存 Markdown 渲染结果,避免重复解析
- **LTPO 适配**:通过进度条微动 + 原生层 `preferredRefreshRate`,解决更新日志页面降频卡顿问题
### 🐛 修复
- 修复更新日志页面因 `ListView` + `shrinkWrap` 导致的 `unbounded height` 渲染异常
- 修复充电条目动画状态残留导致的显示异常
- 修复部分 `TextStyle` 中 `color` 与 `foreground` 混用导致的渲染断言错误
### 📊 影响范围
- `device_matching.dart`:新增 21 Note 机型映射
- `battery_health_page.dart`:AppBar 重构,动画简化
- `battery_data_layout.dart`(新增):UI 布局抽离
- `changelog_page.dart`(新增):更新日志页面
- `MainActivity.kt`:新增原生高刷控制通道
- `pubspec.yaml`:新增 `flutter_markdown` 依赖,添加 `assets/CHANGELOG.md`
### 📌 备注
- 本次更新为 1.6.0 封版版本,后续将聚焦 1.7.0(深色模式 + 关于页面)
- 21 Note 在 Flyme 12.6 上测试通过,日志解析正常 ✅
----
## [1.5.3] - 2026-07-19
- 新增:机型匹配优化
### ✅ 已确认完美支持
- 魅族 20 / 20 Pro / 20 INFINITY(基于remaining_cap + status=full联合判断)
- 魅族 21 / 21 Pro(直接读取learned_cap字段信息)
### ⚠️ 部分支持(测试中)
- 魅族 18 / 18s / 18 Pro / 18s Pro(日志存在,字段解析适配中)
- 魅族 16s(需回退原厂固件验证)
### ❌ 确认不支持
- 魅族 21 Note(星纪团队固件,日志格式乱码无法识别,导致异常)
- 魅族 22(系统未生成 `mbattery_charger` 目录)
### 📌 说明
- 适配状态仅针对 BMS 日志可读性,与手机硬件本身无关
- 部分旧机型(如 16 系)若无日志生成,则无法使用本工具
- 后续若发现其他机型可用,将持续补充
----
## [1.5.2] - 2026-07-14
### 新增
- 浪漫雅圆字体,工程内文件为FlymeFont.ttf
### 🎨 UI 优化
- **字体体系重构**:数字全局替换为 FlymeFont(浪漫雅圆),中文保留 HarmonyOS Sans SC
- 健康度百分比、容量数值、循环次数、电压/温度读数均使用 FlymeFont
- 中文/单位/符号保持 HarmonyOS Sans SC,确保阅读舒适度
- **温标符号修正**:温度显示由 `°C`(度符号 + C)改为专用字符 `℃`(U+2103),与 FlymeFont 原生适配
- **容量行视觉权重优化**:
- 当前容量与设计容量数字使用 FlymeFont(字号 20,字重 300)
- `mAh` 单位改用 HarmonyOS Sans SC(字号 16,字重 500),与 `cycles` 保持一致
- 整行颜色统一为 `rgb(95, 95, 95)`,弱化视觉重量,突出健康度大数字
- **电压/温度极值显示优化**:数值与单位统一使用 FlymeFont,箭头符号保留 HarmonyOS
### 🐛 修复
- 修复 FlymeFont 数字 1 在部分场景下显示为红色的问题(字体内部颜色预设)
- 修复容量行 `designCap`(设计容量)未正确应用 FlymeFont 的问题
- 修复部分 `TextStyle` 中 `color` 与 `foreground` 混用导致的渲染断言错误
- 统一主界面与二级页面状态栏样式
### 📊 影响范围
- `battery_summary.dart`:健康度/循环/容量显示样式重构
- `battery_info_panel.dart`:电压/温度显示字体统一
- `charge_detail_page.dart`:数值字体统一
- `trend_chart.dart`:坐标轴数字字体统一
### 📌 备注
- 本次更新纯 UI 优化,不影响数据读取与计算逻辑
----
## [1.5.0] - 2026-07-09
### 🐛 修复
- 修复有线充电功率计算单位错误(µV × µA 计算时少除 1000,导致功率放大 1000 倍,属于潜藏bug,外面看不出问题)
- 修复魅族 21 系列 `learned_cap` 因受 `status=Full` 限制导致容量未及时更新的问题
- `learned_cap` 优先级提升:在所有状态行中优先匹配,不再局限于 `status=Full`
### ⚡ 优化
- 有线充电功率显示重构:
- 主界面:`≈{实际功率}w/{请求功率}w`(例:`≈30w/65w`)
- 二级页面:拆分为“请求档位”(`≈80w/L-10`)和“实际功率”(`≈30w`)两行
- USB 慢充保持 `≈7.5w` 不变
- UI 统一:
- 统一主界面与二级页面状态栏背景色
- 统一主界面与二级页面 AppBar 背景色为 `Colors.white`
- 状态栏图标保持深色(`Brightness.dark`)
## 更早期日志请移步至项目Git Release页面查看
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1789561763510" class="icon" viewBox="0 0 1282 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5901" xmlns:xlink="http://www.w3.org/1999/xlink" width="250.390625" height="200"><path d="M703.104 952h-2.272c-19.904 0-36.64-7.072-40.448-26.848L531.2 243.808l-139.392 328c-6.848 16.128-22.496 28.704-39.84 28.704H91.456A43.52 43.52 0 0 1 48 556.768a43.52 43.52 0 0 1 43.456-43.744h232.096l184.064-430.528a43.424 43.424 0 0 1 44.672-26.24 43.424 43.424 0 0 1 37.856 35.296l122.528 650.336 111.712-373.44c5.312-17.632 20.832-30.08 39.072-31.168 18.848-0.32 34.976 9.664 42.272 26.4l64.608 149.344h250.176a43.52 43.52 0 0 1 43.488 43.744 43.52 43.52 0 0 1-43.488 43.744h-278.624a43.52 43.52 0 0 1-39.808-26.4l-28.736-66.304-128.736 421.792c-5.472 18.56-22.496 22.4-41.504 22.4z m0 0" fill="#999999" p-id="5902"></path></svg>

After

Width:  |  Height:  |  Size: 972 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

-34
View File
@@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
-1
View File
@@ -1 +0,0 @@
#include "Generated.xcconfig"
-1
View File
@@ -1 +0,0 @@
#include "Generated.xcconfig"
-616
View File
@@ -1,616 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
-13
View File
@@ -1,13 +0,0 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

@@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
-49
View File
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Battery Health</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>battery_health</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
-1
View File
@@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"
-12
View File
@@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
+65 -45
View File
@@ -1,11 +1,14 @@
import 'package:flutter/material.dart';
import 'pages/battery_health_page.dart';
import 'services/saf_storage_service.dart';
import 'theme/app_colors.dart';
import 'theme/theme_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SafStorageService().init();
await ThemeService.init(); // 🆕
runApp(const MyApp());
}
@@ -15,53 +18,70 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '电池健康度',
theme: ThemeData(
// 🟢 中文用 HarmonyOS Sans SC
fontFamily: 'HarmonyOS_Sans_SC',
// 🟢 数字回退到 FlymeFont
fontFamilyFallback: const ['Flyme'],
// 🟢 统一文字颜色为黑色
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFFFFFFF),
).copyWith(onSurface: Colors.black),
textTheme: const TextTheme(
// 大标题/数字(健康度、容量等)
displayMedium: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black,
),
// 标题(如“容量百分比变动趋势”)
headlineMedium: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black,
),
// 正文(电压、温度、循环次数等)
bodyMedium: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black,
),
// 小字(日期、辅助信息)
bodySmall: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black87,
),
// 数字专用(大号数字,如健康度百分比)
displayLarge: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: ['Flyme'],
color: Colors.black,
),
return ValueListenableBuilder<ThemeMode>(
valueListenable: ThemeService.notifier,
builder: (context, mode, _) {
return MaterialApp(
title: '电池健康度',
themeMode: mode,
theme: _buildTheme(Brightness.light, kLightColors),
darkTheme: _buildTheme(Brightness.dark, kDarkColors),
home: const BatteryHealthPage(),
debugShowCheckedModeBanner: false,
);
},
);
}
ThemeData _buildTheme(Brightness brightness, AppColors colors) {
return ThemeData(
useMaterial3: true,
brightness: brightness,
scaffoldBackgroundColor: colors.background,
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: const ['Flyme'],
colorScheme: ColorScheme.fromSeed(
seedColor: colors.background,
brightness: brightness,
),
extensions: [colors], // 🆕 注册 ThemeExtension
appBarTheme: AppBarTheme(
backgroundColor: colors.background, // ← 关键:和 Scaffold 一致
foregroundColor: colors.textPrimary,
// ← 关掉 M3 的滑动变色
elevation: 0,
iconTheme: IconThemeData(color: colors.textPrimary),
),
textTheme: TextTheme(
displayMedium: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: const ['Flyme'],
color: colors.textPrimary,
),
useMaterial3: true,
headlineMedium: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: const ['Flyme'],
color: colors.textPrimary,
),
bodyMedium: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: const ['Flyme'],
color: colors.textPrimary,
),
bodySmall: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: const ['Flyme'],
color: colors.textSecondary,
),
displayLarge: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontFamilyFallback: const ['Flyme'],
color: colors.textPrimary,
),
),
home: const BatteryHealthPage(),
debugShowCheckedModeBanner: false,
);
}
}
+13
View File
@@ -0,0 +1,13 @@
enum DataAbnormalType {
/// 无异常
none,
/// 容量低于设计容量 60%
capacityTooLow,
/// 容量高于设计容量 115%
capacityTooHigh,
/// 电压异常(< 3.0V 或 > 5.0V)
voltageAbnormal,
}
+2
View File
@@ -3,6 +3,7 @@ class DeviceMatching {
/// 根据机型返回设计容量(mAh)
static int getDesignCapacity(String model) {
if (model.contains('21 Pro')) return 5050;
if (model.contains('21 Note')) return 5500;
if (model.contains('21')) return 4800;
if (model.contains('20 Pro')) return 5000;
if (model.contains('20 Inf')) return 4800;
@@ -29,6 +30,7 @@ class DeviceMatching {
/// 根据机型返回显示名称
static String getModelName(String model) {
if (model.contains('21 Pro')) return '魅族21 Pro';
if (model.contains('21 Note')) return '魅族21 Note';
if (model.contains('21')) return '魅族21';
if (model.contains('20 Pro')) return '魅族20 Pro';
if (model.contains('20 Inf')) return '魅族20 Infinity';
+158
View File
@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import '../theme/app_colors.dart';
class AboutPage extends StatelessWidget {
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
final c = context.colors;
return Scaffold(
appBar: AppBar(
title: const Text(
'关于',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontWeight: FontWeight.w500,
),
),
backgroundColor: c.background,
elevation: 0,
),
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: c.surface,
),
child: ClipOval(
child: Image.asset(
'assets/logo.png',
width: 120,
height: 120,
fit: BoxFit.contain,
),
),
),
const SizedBox(height: 24),
Text(
'MEIZU Battery Healthy',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 24,
fontWeight: FontWeight.w600,
color: c.textPrimary,
),
),
const SizedBox(height: 8),
Text(
'版本 1.8.0',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 14,
color: c.textTertiary,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 16),
Divider(
color: c.divider,
thickness: 0.5,
indent: 40,
endIndent: 40,
),
const SizedBox(height: 16),
Text(
'专机专配 · 仅针对魅族独家开发',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 14,
color: c.textSecondary,
),
),
const SizedBox(height: 8),
Text(
'通过读取 BMS 日志直接获取电池真实数据',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 13,
color: c.textTertiary,
),
),
const SizedBox(height: 24),
InkWell(
onTap: () => _launchUrl(context),
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
decoration: BoxDecoration(
color: c.surface,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.code, size: 18, color: c.textPrimary),
const SizedBox(width: 8),
Text(
'查看源码 / 反馈',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 14,
color: c.textPrimary,
),
),
const SizedBox(width: 4),
Icon(Icons.open_in_new, size: 14, color: c.textTertiary),
],
),
),
),
const SizedBox(height: 40),
Text(
'Made with ❤️ in Flyme · 2026',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 12,
color: c.textTertiary,
),
),
],
),
),
),
);
}
Future<void> _launchUrl(BuildContext context) async {
const url = 'https://git.whitetop.xyz/lxh2875931338/MEIZU-Battery-Healthy';
final uri = Uri.parse(url);
try {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e) {
debugPrint('打开链接失败: $e');
await Clipboard.setData(const ClipboardData(text: url));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('无法自动打开浏览器,链接已复制到剪贴板。'),
duration: Duration(seconds: 3),
),
);
}
}
}
}
+309 -199
View File
@@ -3,22 +3,27 @@ import 'dart:developer' as dev;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:device_info_plus/device_info_plus.dart';
import '../theme/theme_service.dart';
import '../theme/app_colors.dart';
// 检查这些导入是否正确
import '../models/device_matching.dart'; // ✅ 存在
import '../models/saf_permission_state.dart'; // ✅ 存在
// --- 本地导入 ---
import '../models/device_matching.dart';
import '../models/saf_permission_state.dart';
import '../models/data_abnormal_type.dart'; // 🆕 1.7.0
import '../services/database_service.dart';
import '../services/battery_data_service.dart';
import '../services/saf_storage_service.dart'; // ✅ 存在
import '../logic/charge_parser.dart'; // ✅ 存在
import '../logic/capacity_extractor.dart'; // ✅ 存在(注意:不是 utils/)
import '../utils/time_utils.dart'; // ✅ 存在
import '../utils/charge_utils.dart'; // ✅ 存在
import '../widgets/trend_chart.dart'; // ⚠️ 需要确认文件内容完整
import '../widgets/battery_summary.dart'; // ⚠️ 需要确认文件内容完整
import '../widgets/battery_info_panel.dart'; // ⚠️ 需要确认文件内容完整
import '../widgets/charge_entry.dart'; // ⚠️ 需要确认文件内容完整
import '../widgets/charge_detail_page.dart'; // ✅ 存在
import '../services/saf_storage_service.dart';
import '../logic/charge_parser.dart';
import '../logic/capacity_extractor.dart';
import '../utils/time_utils.dart';
import '../utils/charge_utils.dart';
import '../widgets/battery_summary.dart';
import '../widgets/battery_info_panel.dart';
import '../widgets/charge_entry.dart';
import '../widgets/charge_detail_page.dart';
import '../widgets/trend_chart.dart';
import '../pages/changelog_page.dart';
import '../pages/about_page.dart';
// --- 潘通色定义 ---
const Color kHealthGreen = Color(0xFF00B140);
@@ -32,8 +37,7 @@ class BatteryHealthPage extends StatefulWidget {
State<BatteryHealthPage> createState() => _BatteryHealthPageState();
}
class _BatteryHealthPageState extends State<BatteryHealthPage>
with SingleTickerProviderStateMixin {
class _BatteryHealthPageState extends State<BatteryHealthPage> {
// ---------- 数据 ----------
double _currentCap = 0;
int _cycleCount = 0;
@@ -43,6 +47,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
String _deviceModel = "魅族21 Pro (默认)";
double _maxTemp = 0;
double _minTemp = 0;
double _batteryTempAvg = 0;
double _maxVolt = 0;
double _minVolt = 0;
bool _hasData = false;
@@ -66,11 +71,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
final BatteryDataService _dataService = BatteryDataService();
SafPermissionState _safState = SafPermissionState.unauthorized;
// 动画控制
late AnimationController _chargeAnimationController;
late Animation<double> _bottomSlide;
late Animation<double> _entryFadeIn;
// 平台通道
static const _methodChannel = MethodChannel('com.lxh.battery_health/charge');
static const _eventChannel = EventChannel(
@@ -87,8 +87,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
super.initState();
dev.log('🔵 ========== initState 开始 ==========', name: 'BatteryHealth');
_initAnimations();
_getInitialChargeState();
_chargeEventSubscription = _eventChannel.receiveBroadcastStream().listen((
@@ -134,31 +132,10 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
void dispose() {
_chargeEventSubscription?.cancel();
_pollTimer?.cancel();
_chargeAnimationController.dispose();
_dbService.database.then((db) => db.close()).ignore();
super.dispose();
}
// ---------- 动画初始化 ----------
void _initAnimations() {
_chargeAnimationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_bottomSlide = Tween<double>(begin: 0, end: 24).animate(
CurvedAnimation(
parent: _chargeAnimationController,
curve: Curves.easeInOutCubic,
),
);
_entryFadeIn = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _chargeAnimationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
),
);
}
// ---------- 设备信息 ----------
Future<void> _getDeviceInfo() async {
dev.log('🔵 _getDeviceInfo 开始', name: 'BatteryHealth');
@@ -275,7 +252,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_chargeCurr = 0.0;
_wiredLevel = null;
_chargingStartTime = null;
_updateChargeState();
return;
}
@@ -310,7 +286,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_chargePower = 0.0;
_chargeVolt = 0.0;
_chargeCurr = 0.0;
_updateChargeState();
});
if (_isCharging) {
@@ -332,7 +307,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
await _getDeviceInfo();
await _dataService.scanAndUpdateDB();
await _dataService.scanAndUpdateDB(designCap: _designCap);
final fileName = TimeUtils.getTodayFileName();
dev.log('🔵 今日文件名: $fileName', name: 'BatteryHealth');
@@ -349,7 +324,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_currentCap = recent.first['capacity'].toDouble();
_maxVolt = recent.first['volt_max'] ?? 0;
_minVolt = recent.first['volt_min'] ?? 0;
// 🟢 数据库目前不存温度,后续升级可扩展,暂时保留为 0
_maxTemp = 0;
_minTemp = 0;
_cycleCount = 0;
@@ -361,8 +335,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
setState(() => _hasData = false);
}
if (_isCharging) _fetchChargeDataFromLog();
_chargeAnimationController.reset();
_updateChargeState();
return;
}
@@ -374,45 +346,36 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
dev.log('🔵 文件为空', name: 'BatteryHealth');
setState(() => _hasData = false);
if (_isCharging) _fetchChargeDataFromLog();
_chargeAnimationController.reset();
_updateChargeState();
return;
}
// 🟢 提取所有有效行(包含 tbat=)
final validLines = lines.where((line) => line.contains('tbat=')).toList();
if (validLines.isEmpty) {
dev.log('🔵 无有效数据行', name: 'BatteryHealth');
setState(() => _hasData = false);
_chargeAnimationController.reset();
_updateChargeState();
return;
}
// 🟢 1.4.7 核心改动:字段提取与容量提取解耦
// 无论 cap 是否为 null,都提取常规字段
final lastLine = validLines.last;
final cycle = _extractCycle(lastLine);
final temp = _extractTemp(lastLine);
final volt = _extractVolt(lastLine);
final (maxTemp, minTemp) = _extractTempExtremes(validLines);
final (maxVolt, minVolt) = _extractVoltExtremes(validLines);
final tempAvg = _extractTempAvg(validLines);
dev.log(
'🔵 字段提取: cycle=$cycle, temp=$temp, volt=$volt',
name: 'BatteryHealth',
);
// 🟢 容量提取(可能为 null)
final cap = CapacityExtractor.extractCapacity(lines);
int? finalCap;
if (cap != null) {
// ✅ 今日有充满记录,直接使用
finalCap = cap;
dev.log('🔵 从日志提取容量: $cap', name: 'BatteryHealth');
} else {
// ⚠️ 今日无充满记录,尝试从数据库复制前一天容量
dev.log('🔵 今日无充满记录,尝试复制前一天容量', name: 'BatteryHealth');
final recent = await _dbService.queryRecentData(1);
if (recent.isNotEmpty) {
@@ -424,11 +387,14 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
}
// 🟢 一次性 setState 更新所有字段
// 🟢 1.7.0 容量有效性校验
final validatedCap = _validateCapacity(finalCap, _designCap);
setState(() {
_currentCap = (finalCap ?? _currentCap).toDouble();
_currentCap = (validatedCap ?? _currentCap).toDouble();
_cycleCount = cycle;
_batteryTemp = temp;
_batteryTempAvg = tempAvg;
_batteryVolt = volt;
_maxTemp = maxTemp;
_minTemp = minTemp;
@@ -443,8 +409,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
await _updateTrendChart();
_chargeAnimationController.reset();
_updateChargeState();
dev.log(
'🔵 ========== readBatteryCapacity 完成 ==========',
name: 'BatteryHealth',
@@ -452,11 +416,56 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
} catch (e) {
dev.log('🔵 readBatteryCapacity 异常: $e', name: 'BatteryHealth');
setState(() => _hasData = false);
_chargeAnimationController.reset();
_updateChargeState();
}
}
// ---------- 🆕 1.7.0 容量有效性校验 ----------
int? _validateCapacity(int? capacity, int designCap) {
if (capacity == null) return null;
// 容量必须大于 0
if (capacity <= 0) {
dev.log('⚠️ 容量异常:<= 0,已忽略', name: 'BatteryHealth');
return null;
}
// 容量不能低于设计容量的 60%
if (capacity < designCap * 0.6) {
dev.log(
'⚠️ 容量异常:低于设计容量 60%($capacity < ${designCap * 0.6}),已忽略',
name: 'BatteryHealth',
);
return null;
}
// 容量不能超过设计容量的 115%
if (capacity > designCap * 1.15) {
dev.log(
'⚠️ 容量异常:高于设计容量 115%($capacity > ${designCap * 1.15}),已忽略',
name: 'BatteryHealth',
);
return null;
}
return capacity;
}
// ---------- 🆕 1.7.0 异常检测(供 UI 使用) ----------
DataAbnormalType _checkAbnormalType(
double currentCap,
int designCap,
double voltage,
) {
// 容量异常:高于 115% 或低于 60%
if (currentCap > designCap * 1.15) return DataAbnormalType.capacityTooHigh;
if (currentCap < designCap * 0.6) return DataAbnormalType.capacityTooLow;
// 电压异常:低于 3.0V 或高于 5.0V
if (voltage < 3.0 || voltage > 5.0) return DataAbnormalType.voltageAbnormal;
return DataAbnormalType.none;
}
// ---------- 辅助提取方法 ----------
int _extractCycle(String line) {
final reg = RegExp(r'cycle=(\d+)');
@@ -490,6 +499,20 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
return (maxTemp ?? 0, minTemp ?? 0);
}
double _extractTempAvg(List<String> lines) {
final reg = RegExp(r'tbat=(\d+)');
double sum = 0;
int count = 0;
for (String line in lines) {
final match = reg.firstMatch(line);
if (match != null) {
sum += double.parse(match.group(1)!);
count++;
}
}
return count > 0 ? sum / count : 0;
}
(double, double) _extractVoltExtremes(List<String> lines) {
final reg = RegExp(r'vbat=(\d+)');
double? maxVolt, minVolt;
@@ -587,33 +610,12 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
}
// ---------- 动画控制 ----------
void _startChargeAnimation() {
if (_chargeAnimationController.isAnimating) return;
_chargeAnimationController.forward();
}
void _stopChargeAnimation() {
if (_chargeAnimationController.isAnimating) return;
_chargeAnimationController.reverse();
}
void _updateChargeState() {
if (!mounted) return;
if (_isCharging) {
_startChargeAnimation();
} else {
_stopChargeAnimation();
}
}
// ---------- 显示辅助 ----------
String _getDisplayPower() {
if (!_isCharging) return '';
if (_isWireless) {
return ChargeUtils.getDisplayPowerWireless(_chargePower);
} else {
// 🟢 1.5.0 改用新格式:实际/标定
return ChargeUtils.getMainDisplayPowerWired(
_chargePower,
_wiredLevel,
@@ -626,30 +628,92 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
return ChargeUtils.getChargeTypeLabel(_isWireless, _chargeType);
}
// 🆕 夜间模式切换弹窗 ← 加在这里
void _showThemePicker() {
showModalBottomSheet(
context: context,
builder: (ctx) {
final current = ThemeService.notifier.value;
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.brightness_auto),
title: const Text('跟随系统'),
trailing: current == ThemeMode.system
? const Icon(Icons.check)
: null,
onTap: () {
ThemeService.setMode(ThemeMode.system);
Navigator.pop(ctx);
},
),
ListTile(
leading: const Icon(Icons.light_mode),
title: const Text('浅色'),
trailing: current == ThemeMode.light
? const Icon(Icons.check)
: null,
onTap: () {
ThemeService.setMode(ThemeMode.light);
Navigator.pop(ctx);
},
),
ListTile(
leading: const Icon(Icons.dark_mode),
title: const Text('深色'),
trailing: current == ThemeMode.dark
? const Icon(Icons.check)
: null,
onTap: () {
ThemeService.setMode(ThemeMode.dark);
Navigator.pop(ctx);
},
),
],
),
);
},
);
}
// ---------- Build ----------
@override
Widget build(BuildContext context) {
final double healthPercent = (_designCap > 0)
? (_currentCap / _designCap) * 100
: 0.0;
final String chargeSummary = _isCharging
? '充电中 · ${_getChargeTypeLabel()} · ${_getDisplayPower()}'
: '';
// 🆕 1.7.0 异常检测
final abnormalType = _checkAbnormalType(
_currentCap,
_designCap,
_batteryVolt,
);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
// 🟢 1.5.0 统一状态栏颜色
statusBarColor: Color.fromRGBO(157, 212, 237, 0.1),
value: SystemUiOverlayStyle(
statusBarColor: context.colors.statusBarTint,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.dark,
),
child: Scaffold(
backgroundColor: Colors.white,
// backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
// backgroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
title: const Text(
title: Text(
'电池健康度',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.black,
color: context.colors.textPrimary,
),
),
actions: [
@@ -657,116 +721,162 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
icon: const Icon(Icons.refresh),
onPressed: readBatteryCapacity,
tooltip: '刷新数据',
color: Colors.black,
color: context.colors.icon,
),
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert),
color: context.colors.surface,
onSelected: (value) {
switch (value) {
case 'changelog':
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ChangelogPage(),
),
);
break;
case 'about': // 🟢 新增
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AboutPage(),
),
);
break;
case 'dark_mode':
_showThemePicker();
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem(value: 'changelog', child: Text('更新日志')),
const PopupMenuItem(
value: 'about', // 🟢 新增
child: Text('关于'),
),
const PopupMenuItem(value: 'dark_mode', child: Text('夜间模式')),
],
),
],
),
body: _hasData ? _buildDataLayout() : _buildEmptyState(),
),
);
}
Widget _buildEmptyState() {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.battery_unknown, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无数据', style: TextStyle(fontSize: 18, color: Colors.grey)),
Text('点击右上角刷新尝试', style: TextStyle(fontSize: 14, color: Colors.grey)),
],
),
);
}
Widget _buildDataLayout() {
final healthPercent = (_designCap > 0)
? (_currentCap / _designCap) * 100
: 0;
String chargeSummary = '';
if (_isCharging) {
chargeSummary = '充电中 · ${_getChargeTypeLabel()} · ${_getDisplayPower()}';
}
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_deviceModel,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 4),
BatterySummary(
healthPercent: healthPercent.toDouble(), // 显式转换
cycleCount: _cycleCount,
currentCap: _currentCap,
designCap: _designCap,
),
const SizedBox(height: 24),
TrendChart(data: _trendData, designCap: _designCap),
const SizedBox(height: 8),
AnimatedBuilder(
animation: _chargeAnimationController,
builder: (context, child) {
final entryHeight = 28 * _entryFadeIn.value;
final chargeEntry = Opacity(
opacity: _entryFadeIn.value,
child: ChargeEntry(
summary: chargeSummary,
isCharging: _isCharging,
onTap: () {
if (_isCharging) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChargeDetailPage(
chargeType: _chargeType,
isWireless: _isWireless,
power: _chargePower,
volt: _chargeVolt,
curr: _chargeCurr,
wiredLevel: _wiredLevel,
),
),
);
}
},
),
);
return Column(
body: _hasData
? Column(
children: [
SizedBox(height: entryHeight, child: chargeEntry),
Transform.translate(
offset: Offset(0, _bottomSlide.value),
child: BatteryInfoPanel(
batteryVolt: _batteryVolt,
batteryTemp: _batteryTemp,
maxVolt: _maxVolt,
minVolt: _minVolt,
maxTemp: _maxTemp,
minTemp: _minTemp,
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_deviceModel,
style: TextStyle(
fontSize: 14,
color: context.colors.textTertiary,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 4),
// 🟢 BatterySummary(带异常检测)
RepaintBoundary(
child: BatterySummary(
healthPercent: healthPercent,
cycleCount: _cycleCount,
currentCap: _currentCap,
designCap: _designCap,
abnormalType: abnormalType, // 🆕 1.7.0
),
),
const SizedBox(height: 24),
RepaintBoundary(
child: TrendChart(
data: _trendData,
designCap: _designCap,
),
),
const SizedBox(height: 8),
RepaintBoundary(
child: AnimatedOpacity(
opacity: _isCharging ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: ChargeEntry(
summary: chargeSummary,
isCharging: _isCharging,
onTap: () {
if (_isCharging) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChargeDetailPage(
chargeType: _chargeType,
isWireless: _isWireless,
power: _chargePower,
volt: _chargeVolt,
curr: _chargeCurr,
wiredLevel: _wiredLevel,
),
),
);
}
},
),
),
),
const SizedBox(height: 4),
// 🟢 BatteryInfoPanel(带异常检测)
RepaintBoundary(
child: BatteryInfoPanel(
batteryVolt: _batteryVolt,
batteryTemp: _batteryTemp,
tempAvg: _batteryTempAvg,
maxVolt: _maxVolt,
minVolt: _minVolt,
maxTemp: _maxTemp,
minTemp: _minTemp,
abnormalType: abnormalType, // 🆕 1.7.0
),
),
],
),
),
),
],
);
},
),
const Spacer(),
],
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.battery_unknown,
size: 64,
color: context.colors.textTertiary,
),
const SizedBox(height: 16),
Text(
'暂无数据',
style: TextStyle(
fontSize: 18,
color: context.colors.textTertiary,
),
),
Text(
'点击右上角刷新尝试',
style: TextStyle(
fontSize: 14,
color: context.colors.textTertiary,
),
),
],
),
),
),
);
}
+191
View File
@@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import '../theme/app_colors.dart';
class ChangelogPage extends StatefulWidget {
const ChangelogPage({super.key});
@override
State<ChangelogPage> createState() => _ChangelogPageState();
}
class _ChangelogPageState extends State<ChangelogPage>
with SingleTickerProviderStateMixin {
// ---------- 数据 ----------
String _markdownContent = '';
bool _isLoading = true;
String? _error;
// ---------- 滚动 ----------
final ScrollController _scrollController = ScrollController();
double _scrollProgress = 0.0;
bool _isScrolling = false;
// ---------- 呼吸动画 ----------
late AnimationController _breathController;
late Animation<double> _breathAnimation;
// ---------- 原生刷新率控制 ----------
static final _frameRateChannel = MethodChannel(
'com.lxh.battery_health/frame_rate',
);
@override
void initState() {
super.initState();
_loadChangelog();
_frameRateChannel.invokeMethod('setHighRefreshRate');
_breathController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
)..repeat(reverse: true);
_breathAnimation = Tween<double>(begin: 0.0, end: 0.03).animate(
CurvedAnimation(parent: _breathController, curve: Curves.easeInOut),
);
_scrollController.addListener(() {
_updateProgress();
final isScrolling = _scrollController.position.isScrollingNotifier.value;
if (isScrolling != _isScrolling) {
setState(() {
_isScrolling = isScrolling;
});
if (isScrolling) {
_breathController.repeat(reverse: true);
} else {
_breathController.stop();
_breathController.reset();
}
}
});
}
@override
void dispose() {
_frameRateChannel.invokeMethod('clearRefreshRate');
_scrollController.removeListener(_updateProgress);
_scrollController.dispose();
_breathController.dispose();
super.dispose();
}
void _updateProgress() {
if (!_scrollController.hasClients) return;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
final progress = maxScroll > 0 ? currentScroll / maxScroll : 0.0;
setState(() {
_scrollProgress = progress;
});
}
Future<void> _loadChangelog() async {
try {
final content = await rootBundle.loadString('assets/CHANGELOG.md');
setState(() {
_markdownContent = content;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = '无法加载更新日志: $e';
_isLoading = false;
});
}
}
Widget _buildMarkdown(BuildContext context, String content) {
final c = context.colors;
return Markdown(
data: content,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
styleSheet: MarkdownStyleSheet(
h1: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 24,
fontWeight: FontWeight.w700,
color: c.textPrimary,
),
h2: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 20,
fontWeight: FontWeight.w600,
color: c.textPrimary,
),
h3: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 18,
fontWeight: FontWeight.w600,
color: c.textSecondary,
),
p: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
color: c.textSecondary,
),
listBullet: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
color: c.textSecondary,
),
code: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 14,
color: c.textSecondary,
backgroundColor: c.surface,
),
),
);
}
@override
Widget build(BuildContext context) {
final c = context.colors;
final breathOffset = _isScrolling ? _breathAnimation.value : 0.0;
final displayProgress = (_scrollProgress + breathOffset).clamp(0.0, 1.0);
return Scaffold(
appBar: AppBar(
title: Text(
'更新日志',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: c.textPrimary,
),
),
elevation: 0,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(2.0),
child: LinearProgressIndicator(
value: displayProgress,
backgroundColor: c.divider,
valueColor: AlwaysStoppedAnimation<Color>(c.progressBar),
minHeight: 2.0,
),
),
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(
child: Text(
_error!,
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: c.textTertiary,
),
),
)
: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(16.0),
child: _buildMarkdown(context, _markdownContent),
),
);
}
}
+79 -38
View File
@@ -9,8 +9,40 @@ class BatteryDataService {
final SafStorageService _safService = SafStorageService();
final DatabaseService _dbService = DatabaseService();
/// 🟢 1.7.0 容量有效性校验(入库专用)
int? _validateCapacityForDB(int? capacity, int designCap) {
if (capacity == null) return null;
// 容量必须大于 0
if (capacity <= 0) {
dev.log('⚠️ [DB校验] 容量异常:<= 0,已拒绝入库', name: 'BatteryDataService');
return null;
}
// 容量不能低于设计容量的 60%
if (capacity < designCap * 0.6) {
dev.log(
'⚠️ [DB校验] 容量异常:低于设计容量 60%($capacity < ${designCap * 0.6}),已拒绝入库',
name: 'BatteryDataService',
);
return null;
}
// 容量不能超过设计容量的 115%
if (capacity > designCap * 1.15) {
dev.log(
'⚠️ [DB校验] 容量异常:高于设计容量 115%($capacity > ${designCap * 1.15}),已拒绝入库',
name: 'BatteryDataService',
);
return null;
}
return capacity;
}
/// 扫描所有日志文件并更新数据库
Future<void> scanAndUpdateDB() async {
/// 🟢 1.7.0 增加 designCap 参数,用于入库前校验
Future<void> scanAndUpdateDB({int? designCap}) async {
dev.log('🔵 scanAndUpdateDB 开始', name: 'BatteryHealth');
final state = await _safService.getPermissionState();
@@ -34,56 +66,65 @@ class BatteryDataService {
final lines = await _safService.readLogFile(fileName);
if (lines.isEmpty) continue;
// 🟢 1.4.5 改进:使用 CapacityExtractor 提取容量(仅从 Full 状态提取)
int? cap = CapacityExtractor.extractCapacity(lines);
// 使用 CapacityExtractor 提取容量
final cap = CapacityExtractor.extractCapacity(lines);
// 🟢 如果今天没有充满电(cap == null),尝试从昨天复制数据
if (cap == null) {
dev.log('🔵 日期 $dateStr 无充满记录,尝试复制前一天数据', name: 'BatteryHealth');
// 查询最近一条已有数据
final recentData = await _dbService.queryRecentData(1);
if (recentData.isNotEmpty) {
final yesterdayCap = recentData.first['capacity'] as int;
final yesterdayMaxVolt =
recentData.first['volt_max'] as double? ?? 0.0;
final yesterdayMinVolt =
recentData.first['volt_min'] as double? ?? 0.0;
dev.log(
'🔵 复制前一天数据: cap=$yesterdayCap, date=$dateStr',
name: 'BatteryHealth',
);
// 🟢 复制昨天的容量和电压极值到今天
await _dbService.insertOrUpdate(
dateStr,
yesterdayCap,
yesterdayMaxVolt,
yesterdayMinVolt,
);
} else {
dev.log('🔵 无历史数据可复制,跳过日期 $dateStr', name: 'BatteryHealth');
}
continue; // 已处理,跳过后续电压提取逻辑
// 🟢 1.7.0 如果 designCap 传入,则进行入库前校验
int? validatedCap = cap;
if (designCap != null && designCap > 0) {
validatedCap = _validateCapacityForDB(cap, designCap);
}
// 提取电压极值(从所有有效行提取,不限于 Full)
if (validatedCap == null) {
if (cap != null) {
dev.log(
'🔵 日期 $dateStr 容量校验未通过 ($cap),跳过入库',
name: 'BatteryHealth',
);
} else {
dev.log('🔵 日期 $dateStr 无有效容量数据,跳过入库', name: 'BatteryHealth');
}
continue;
}
// 提取电压极值与平均温度
final voltReg = RegExp(r'vbat=(\d+)');
final tempReg = RegExp(r'tbat=(\d+)');
double? maxVolt, minVolt;
double tempSum = 0;
int tempCount = 0;
for (String line in lines) {
if (!line.contains('tbat=')) continue;
final match = voltReg.firstMatch(line);
if (match != null) {
final v = int.parse(match.group(1)!) / 1000000;
final vMatch = voltReg.firstMatch(line);
if (vMatch != null) {
final v = int.parse(vMatch.group(1)!) / 1000000;
if (maxVolt == null || v > maxVolt) maxVolt = v;
if (minVolt == null || v < minVolt) minVolt = v;
}
final tMatch = tempReg.firstMatch(line);
if (tMatch != null) {
tempSum += double.parse(tMatch.group(1)!);
tempCount++;
}
}
if (maxVolt == null || minVolt == null) continue;
await _dbService.insertOrUpdate(dateStr, cap, maxVolt, minVolt);
dev.log('🔵 插入数据库: $dateStr, cap=$cap', name: 'BatteryHealth');
final tempAvg = tempCount > 0 ? tempSum / tempCount : 0.0;
await _dbService.insertOrUpdate(
dateStr,
validatedCap,
maxVolt,
minVolt,
tempAvg: tempAvg, // 🆕
);
dev.log(
'🔵 插入数据库: $dateStr, cap=$validatedCap',
name: 'BatteryHealth',
);
} catch (e) {
dev.log('🔵 处理文件 $fileName 异常: $e', name: 'BatteryHealth');
}
+20 -6
View File
@@ -6,22 +6,21 @@ import 'package:path/path.dart';
class DatabaseService {
static Database? _database;
static const String _tableName = 'battery_data';
static const int _dbVersion = 2; // 🆕 1.7.1 加入 temp_avg
/// 获取数据库实例(单例)
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
/// 初始化数据库
Future<Database> _initDatabase() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'battery_health.db');
return await openDatabase(
path,
version: 1,
version: _dbVersion,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE $_tableName (
@@ -29,6 +28,7 @@ class DatabaseService {
capacity INTEGER NOT NULL,
volt_max REAL NOT NULL,
volt_min REAL NOT NULL,
temp_avg REAL NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
)
''');
@@ -37,6 +37,15 @@ class DatabaseService {
''');
dev.log('✅ 数据库表创建成功', name: 'DatabaseService');
},
onUpgrade: (db, oldVersion, newVersion) async {
// 🆕 1.7.1 v1 -> v2:添加 temp_avg 字段
if (oldVersion < 2) {
await db.execute(
'ALTER TABLE $_tableName ADD COLUMN temp_avg REAL NOT NULL DEFAULT 0',
);
dev.log('✅ 数据库升级 v1->v2:已添加 temp_avg 字段', name: 'DatabaseService');
}
},
);
}
@@ -45,8 +54,9 @@ class DatabaseService {
String date,
int capacity,
double voltMax,
double voltMin,
) async {
double voltMin, {
double tempAvg = 0, // 🆕
}) async {
final db = await database;
final now = DateTime.now().toIso8601String();
@@ -55,9 +65,13 @@ class DatabaseService {
'capacity': capacity,
'volt_max': voltMax,
'volt_min': voltMin,
'temp_avg': tempAvg,
'updated_at': now,
}, conflictAlgorithm: ConflictAlgorithm.replace);
dev.log('📝 数据已保存: $date, cap=$capacity', name: 'DatabaseService');
dev.log(
'📝 数据已保存: $date, cap=$capacity, tempAvg=${tempAvg.toStringAsFixed(1)}',
name: 'DatabaseService',
);
}
/// 查询最近 N 条数据(按日期倒序)
+164
View File
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
@immutable
class AppColors extends ThemeExtension<AppColors> {
final Color background; // 页面背景
final Color surface; // 卡片/面板背景
final Color textPrimary; // 主文字
final Color textSecondary; // 次要文字
final Color textTertiary; // 弱化文字
final Color divider; // 分割线
final Color icon; // 图标色
final Color healthGreen; // 健康度 ≥80%
final Color healthYellow; // 70~80%
final Color healthOrange;
final Color healthRed; // <70% 或异常
final Color barEmpty; // 无数据柱
final Color voltUp; // 电压↑
final Color voltDown; // 电压↓
final Color tempUp;
final Color tempDown;
final Color progressBar; // 更新日志进度条
final Color statusBarTint; // 状态栏背景
const AppColors({
required this.background,
required this.surface,
required this.textPrimary,
required this.textSecondary,
required this.textTertiary,
required this.divider,
required this.icon,
required this.healthGreen,
required this.healthYellow,
required this.healthOrange,
required this.healthRed,
required this.barEmpty,
required this.voltUp,
required this.voltDown,
required this.tempUp,
required this.tempDown,
required this.progressBar,
required this.statusBarTint,
});
@override
AppColors copyWith({
Color? background,
Color? surface,
Color? textPrimary,
Color? textSecondary,
Color? textTertiary,
Color? divider,
Color? icon,
Color? healthGreen,
Color? healthYellow,
Color? healthOrange,
Color? healthRed,
Color? barEmpty,
Color? voltUp,
Color? voltDown,
Color? tempUp,
Color? tempDown,
Color? progressBar,
Color? statusBarTint,
}) {
return AppColors(
background: background ?? this.background,
surface: surface ?? this.surface,
textPrimary: textPrimary ?? this.textPrimary,
textSecondary: textSecondary ?? this.textSecondary,
textTertiary: textTertiary ?? this.textTertiary,
divider: divider ?? this.divider,
icon: icon ?? this.icon,
healthGreen: healthGreen ?? this.healthGreen,
healthYellow: healthYellow ?? this.healthYellow,
healthOrange: healthOrange ?? this.healthOrange,
healthRed: healthRed ?? this.healthRed,
barEmpty: barEmpty ?? this.barEmpty,
voltUp: voltUp ?? this.voltUp,
voltDown: voltDown ?? this.voltDown,
tempUp: tempUp ?? this.tempUp,
tempDown: tempDown ?? this.tempDown,
progressBar: progressBar ?? this.progressBar,
statusBarTint: statusBarTint ?? this.statusBarTint,
);
}
@override
AppColors lerp(ThemeExtension<AppColors>? other, double t) {
if (other is! AppColors) return this;
return AppColors(
background: Color.lerp(background, other.background, t)!,
surface: Color.lerp(surface, other.surface, t)!,
textPrimary: Color.lerp(textPrimary, other.textPrimary, t)!,
textSecondary: Color.lerp(textSecondary, other.textSecondary, t)!,
textTertiary: Color.lerp(textTertiary, other.textTertiary, t)!,
divider: Color.lerp(divider, other.divider, t)!,
icon: Color.lerp(icon, other.icon, t)!,
healthGreen: Color.lerp(healthGreen, other.healthGreen, t)!,
healthYellow: Color.lerp(healthYellow, other.healthYellow, t)!,
healthOrange: Color.lerp(healthOrange, other.healthOrange, t)!,
healthRed: Color.lerp(healthRed, other.healthRed, t)!,
barEmpty: Color.lerp(barEmpty, other.barEmpty, t)!,
voltUp: Color.lerp(voltUp, other.voltUp, t)!,
voltDown: Color.lerp(voltDown, other.voltDown, t)!,
tempUp: Color.lerp(tempUp, other.tempUp, t)!,
tempDown: Color.lerp(tempDown, other.tempDown, t)!,
progressBar: Color.lerp(progressBar, other.progressBar, t)!,
statusBarTint: Color.lerp(statusBarTint, other.statusBarTint, t)!,
);
}
}
/// 浅色主题(保持现有视觉)
const AppColors kLightColors = AppColors(
background: Colors.white,
surface: Color(0xFFF0F4F8),
textPrimary: Colors.black,
textSecondary: Color(0xFF5F5F5F),
textTertiary: Colors.grey,
divider: Color(0xFFE0E0E0),
icon: Colors.black,
healthGreen: Color(0xFF00B140),
healthYellow: Color(0xFFEFDF00),
healthOrange: Color(0xFFD75A16),
healthRed: Colors.red,
barEmpty: Color(0xFFE0E0E0),
voltUp: Colors.red,
voltDown: Colors.blue,
tempUp: Colors.red,
tempDown: Colors.blue,
progressBar: Color.fromRGBO(157, 212, 237, 1.0),
statusBarTint: Color.fromRGBO(157, 212, 237, 0.1),
);
/// 深色主题(色相保留,明度调整)
const AppColors kDarkColors = AppColors(
background: Color(0xFF121212),
surface: Color(0xFF1E1E1E),
textPrimary: Color(0xFFE8E8E8),
textSecondary: Color(0xFFA0A0A0),
textTertiary: Color(0xFF707070),
divider: Color(0xFF2A2A2A),
icon: Color(0xFFE8E8E8),
healthGreen: Color(0xFF00D14D),
healthYellow: Color(0xFFD4C520),
healthOrange: Color(0xFFFF7838),
healthRed: Color(0xFFFF5252),
barEmpty: Color(0xFF2A2A2A),
voltUp: Color(0xFFFF5252),
voltDown: Color(0xFF5AA9FF),
tempUp: Color(0xFFFF5252),
tempDown: Color(0xFF5AA9FF),
progressBar: Color(0xFF5A9BBD),
statusBarTint: Color(0xFF121212),
);
/// 便捷扩展:context.colors.textPrimary
extension AppColorsX on BuildContext {
AppColors get colors => Theme.of(this).extension<AppColors>()!;
}
+45
View File
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ThemeService {
static const _prefKey = 'theme_mode';
static final ValueNotifier<ThemeMode> notifier = ValueNotifier(
ThemeMode.system,
);
/// 启动时调用(main.dart)
static Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString(_prefKey);
notifier.value = _parse(saved);
}
/// 菜单里调用
static Future<void> setMode(ThemeMode mode) async {
notifier.value = mode;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_prefKey, _name(mode));
}
static ThemeMode _parse(String? s) {
switch (s) {
case 'light':
return ThemeMode.light;
case 'dark':
return ThemeMode.dark;
default:
return ThemeMode.system;
}
}
static String _name(ThemeMode m) {
switch (m) {
case ThemeMode.light:
return 'light';
case ThemeMode.dark:
return 'dark';
case ThemeMode.system:
return 'system';
}
}
}
+151
View File
@@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import '../widgets/trend_chart.dart';
import '../widgets/battery_summary.dart';
import '../widgets/battery_info_panel.dart';
import '../widgets/charge_entry.dart';
import '../widgets/charge_detail_page.dart';
/// 电池数据布局(含数据展示、趋势图、充电状态、电压温度面板)
class BatteryDataLayout extends StatelessWidget {
final String deviceModel;
final double healthPercent;
final int cycleCount;
final double currentCap;
final int designCap;
final List<Map<String, dynamic>> trendData;
final bool isCharging;
final String chargeSummary;
final double batteryVolt;
final double batteryTemp;
final double maxVolt;
final double minVolt;
final double maxTemp;
final double minTemp;
final String chargeType;
final bool isWireless;
final double chargePower;
final double chargeVolt;
final double chargeCurr;
final int? wiredLevel;
const BatteryDataLayout({
super.key,
required this.deviceModel,
required this.healthPercent,
required this.cycleCount,
required this.currentCap,
required this.designCap,
required this.trendData,
required this.isCharging,
required this.chargeSummary,
required this.batteryVolt,
required this.batteryTemp,
required this.maxVolt,
required this.minVolt,
required this.maxTemp,
required this.minTemp,
required this.chargeType,
required this.isWireless,
required this.chargePower,
required this.chargeVolt,
required this.chargeCurr,
this.wiredLevel,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
deviceModel,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 4),
BatterySummary(
healthPercent: healthPercent,
cycleCount: cycleCount,
currentCap: currentCap,
designCap: designCap,
),
const SizedBox(height: 24),
TrendChart(data: trendData, designCap: designCap),
const SizedBox(height: 8),
// 充电状态 + 信息面板(带进场动画)
_buildChargeAndInfoPanel(context),
const Spacer(),
],
),
);
}
Widget _buildChargeAndInfoPanel(BuildContext context) {
return Column(
children: [
// 充电状态入口(保持原有的透明度/位移动画由父级控制)
ChargeEntry(
summary: chargeSummary,
isCharging: isCharging,
onTap: () {
if (isCharging) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChargeDetailPage(
chargeType: chargeType,
isWireless: isWireless,
power: chargePower,
volt: chargeVolt,
curr: chargeCurr,
wiredLevel: wiredLevel,
),
),
);
}
},
),
const SizedBox(height: 4),
BatteryInfoPanel(
batteryVolt: batteryVolt,
batteryTemp: batteryTemp,
maxVolt: maxVolt,
minVolt: minVolt,
maxTemp: maxTemp,
minTemp: minTemp,
),
],
);
}
}
/// 空状态布局(无数据时显示)
class EmptyStateLayout extends StatelessWidget {
const EmptyStateLayout({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.battery_unknown, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无数据', style: TextStyle(fontSize: 18, color: Colors.grey)),
Text('点击右上角刷新尝试', style: TextStyle(fontSize: 14, color: Colors.grey)),
],
),
);
}
}
+76 -32
View File
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../models/data_abnormal_type.dart';
import '../theme/app_colors.dart';
class BatteryInfoPanel extends StatelessWidget {
final double batteryVolt;
@@ -8,6 +10,8 @@ class BatteryInfoPanel extends StatelessWidget {
final double minVolt;
final double maxTemp;
final double minTemp;
final double tempAvg;
final DataAbnormalType abnormalType;
const BatteryInfoPanel({
super.key,
@@ -17,21 +21,31 @@ class BatteryInfoPanel extends StatelessWidget {
required this.minVolt,
required this.maxTemp,
required this.minTemp,
this.tempAvg = 0,
this.abnormalType = DataAbnormalType.none,
});
@override
Widget build(BuildContext context) {
final c = context.colors;
final bool isVoltageAbnormal =
abnormalType == DataAbnormalType.voltageAbnormal;
final Color voltColor = isVoltageAbnormal ? c.healthRed : c.textPrimary;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ---------- 电压列 ----------
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoItem(
_buildIcon('voltage'),
context,
_buildIcon(context, 'voltage'),
'电压',
'${batteryVolt.toStringAsFixed(3)}V',
textColor: voltColor,
),
const SizedBox(height: 8),
Row(
@@ -42,14 +56,14 @@ class BatteryInfoPanel extends StatelessWidget {
fontFamily: 'Flyme',
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.red,
color: c.voltUp,
),
children: [
const TextSpan(
TextSpan(
text: '↑ ',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.red,
color: c.voltUp,
),
),
TextSpan(text: '${maxVolt.toStringAsFixed(3)}V'),
@@ -63,14 +77,14 @@ class BatteryInfoPanel extends StatelessWidget {
fontFamily: 'Flyme',
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.blue,
color: c.voltDown,
),
children: [
const TextSpan(
TextSpan(
text: '↓ ',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.blue,
color: c.voltDown,
),
),
TextSpan(text: '${minVolt.toStringAsFixed(3)}V'),
@@ -82,12 +96,14 @@ class BatteryInfoPanel extends StatelessWidget {
],
),
),
// ---------- 温度列 ----------
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoItem(
_buildIcon('temperature'),
context,
_buildIcon(context, 'temperature'),
'温度',
'${batteryTemp.toStringAsFixed(0)}℃',
),
@@ -98,43 +114,55 @@ class BatteryInfoPanel extends StatelessWidget {
text: TextSpan(
style: TextStyle(
fontFamily: 'Flyme',
fontSize: 14,
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.red,
color: c.tempUp,
),
children: [
const TextSpan(
TextSpan(
text: '↑ ',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.red,
color: c.tempUp,
),
),
TextSpan(text: '${maxTemp.toStringAsFixed(0)}℃'),
TextSpan(text: '${maxTemp.toStringAsFixed(1)}℃'),
],
),
),
const SizedBox(width: 12),
const SizedBox(width: 6),
RichText(
text: TextSpan(
style: TextStyle(
fontFamily: 'Flyme',
fontSize: 14,
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.blue,
color: c.tempDown,
),
children: [
const TextSpan(
TextSpan(
text: '↓ ',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.blue,
color: c.tempDown,
),
),
TextSpan(text: '${minTemp.toStringAsFixed(0)}℃'),
TextSpan(text: '${minTemp.toStringAsFixed(1)}℃'),
],
),
),
const SizedBox(width: 6),
_buildIcon(context, 'avg', size: 14),
const SizedBox(width: 2),
Text(
'${tempAvg.toStringAsFixed(1)}℃',
style: TextStyle(
fontFamily: 'Flyme',
fontSize: 12,
fontWeight: FontWeight.w500,
color: c.textTertiary,
),
),
],
),
],
@@ -144,48 +172,64 @@ class BatteryInfoPanel extends StatelessWidget {
);
}
Widget _buildIcon(String type) {
Widget _buildIcon(BuildContext context, String type, {double size = 18}) {
final c = context.colors;
final filter = ColorFilter.mode(c.textTertiary, BlendMode.srcIn);
switch (type) {
case 'voltage':
return SvgPicture.asset(
'assets/icons/si--lightning-fill.svg',
width: 18,
height: 18,
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: size,
height: size,
colorFilter: filter,
);
case 'temperature':
return SvgPicture.asset(
'assets/icons/mdi--temperature.svg',
width: 18,
height: 18,
colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),
width: size,
height: size,
colorFilter: filter,
);
case 'avg':
return SvgPicture.asset(
'assets/icons/avg.svg',
width: size,
height: size,
colorFilter: filter,
);
default:
return const Icon(Icons.help_outline, size: 18, color: Colors.grey);
return Icon(Icons.help_outline, size: size, color: c.textTertiary);
}
}
Widget _buildInfoItem(Widget icon, String label, String value) {
Widget _buildInfoItem(
BuildContext context,
Widget icon,
String label,
String value, {
Color? textColor,
}) {
final c = context.colors;
return Row(
children: [
icon,
const SizedBox(width: 6),
Text(
label,
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 14,
color: Colors.grey,
color: c.textTertiary,
),
),
const SizedBox(width: 6),
Text(
value,
style: const TextStyle(
style: TextStyle(
fontFamily: 'Flyme',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black,
color: textColor ?? c.textPrimary,
),
),
],
+28 -12
View File
@@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import '../models/data_abnormal_type.dart';
import '../theme/app_colors.dart';
class BatterySummary extends StatelessWidget {
final double healthPercent;
final int cycleCount;
final double currentCap;
final int designCap;
final DataAbnormalType abnormalType;
const BatterySummary({
super.key,
@@ -12,16 +15,28 @@ class BatterySummary extends StatelessWidget {
required this.cycleCount,
required this.currentCap,
required this.designCap,
this.abnormalType = DataAbnormalType.none,
});
@override
Widget build(BuildContext context) {
final bool isLowHealth = healthPercent < 70;
final c = context.colors;
final bool isAbnormal = abnormalType != DataAbnormalType.none;
final bool isCapacityAbnormal =
abnormalType == DataAbnormalType.capacityTooLow ||
abnormalType == DataAbnormalType.capacityTooHigh;
final Color healthColor = isAbnormal
? c.healthRed
: (healthPercent < 70 ? c.healthRed : c.textPrimary);
final Color capacityColor = isCapacityAbnormal
? c.healthRed
: c.textSecondary;
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// 健康度大数字
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
@@ -33,7 +48,7 @@ class BatterySummary extends StatelessWidget {
fontSize: 40,
fontWeight: FontWeight.w500,
height: 1.0,
color: isLowHealth ? Colors.red : Colors.black,
color: healthColor,
),
),
const SizedBox(width: 2),
@@ -44,13 +59,14 @@ class BatterySummary extends StatelessWidget {
fontSize: 20,
fontWeight: FontWeight.w300,
height: 1.0,
color: isLowHealth ? Colors.red : Colors.grey.shade600,
color: healthColor == c.healthRed
? c.healthRed
: c.textSecondary,
),
),
],
),
const Spacer(),
// cycles + mAh
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
@@ -61,15 +77,15 @@ class BatterySummary extends StatelessWidget {
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1.2,
color: Colors.black,
color: c.textPrimary,
),
children: [
TextSpan(text: '$cycleCount'),
TextSpan(
text: ' cycles',
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: Colors.black,
color: c.textPrimary,
),
),
],
@@ -83,7 +99,7 @@ class BatterySummary extends StatelessWidget {
fontSize: 20,
fontWeight: FontWeight.w300,
height: 1.0,
color: const Color(0xFF5F5F5F),
color: capacityColor,
),
children: [
TextSpan(text: currentCap.toStringAsFixed(0)),
@@ -91,17 +107,17 @@ class BatterySummary extends StatelessWidget {
text: ' / ',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
color: const Color(0xFF5F5F5F).withValues(alpha: 0.5),
color: capacityColor.withOpacity(0.5),
),
),
TextSpan(text: designCap.toString()),
TextSpan(
text: ' mAh',
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF5F5F5F),
color: capacityColor,
),
),
],
+42 -38
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../utils/charge_utils.dart';
import '../theme/app_colors.dart';
class ChargeDetailPage extends StatelessWidget {
final String chargeType;
@@ -41,50 +42,48 @@ class ChargeDetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final c = context.colors;
final isDark = Theme.of(context).brightness == Brightness.dark;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Color.fromRGBO(157, 212, 237, 0.1),
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.dark,
value: SystemUiOverlayStyle(
statusBarColor: c.statusBarTint,
statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark,
statusBarBrightness: Theme.of(context).brightness,
),
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('充电详情'),
backgroundColor: Colors.white,
backgroundColor: c.background,
elevation: 0,
scrolledUnderElevation: 0,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
foregroundColor: Colors.black,
iconTheme: const IconThemeData(color: Colors.black),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTextRow('协议', _getProtocolDisplay()),
_buildTextRow(context, '协议', _getProtocolDisplay()),
const Divider(),
if (!isWireless) ...[
_buildTextRow('标定档位', _getRequestedDisplay()),
_buildTextRow(context, '标定档位', _getRequestedDisplay()),
const Divider(),
_buildTextRow('实际功率', _getActualDisplay()),
_buildTextRow(context, '实际功率', _getActualDisplay()),
const Divider(),
] else ...[
_buildTextRow('功率', ChargeUtils.getDisplayPowerWireless(power)),
_buildTextRow(
context,
'功率',
ChargeUtils.getDisplayPowerWireless(power),
),
const Divider(),
],
_buildDetailRow('电压', volt, 'V'),
_buildDetailRow(context, '电压', volt, 'V'),
const Divider(),
_buildDetailRow('电流', curr, 'A'),
_buildDetailRow(context, '电流', curr, 'A'),
const Divider(),
if (isWireless) _buildTextRow('类型', '无线'),
if (!isWireless && wiredLevel == null) _buildTextRow('档位', '自动'),
if (isWireless) _buildTextRow(context, '类型', '无线'),
if (!isWireless && wiredLevel == null)
_buildTextRow(context, '档位', '自动'),
const Spacer(),
Center(
child: Text(
@@ -92,7 +91,7 @@ class ChargeDetailPage extends StatelessWidget {
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 12,
color: Colors.grey.shade500,
color: c.textTertiary,
),
),
),
@@ -103,8 +102,8 @@ class ChargeDetailPage extends StatelessWidget {
);
}
/// 纯文本行(协议、功率、档位等)
Widget _buildTextRow(String label, String value) {
Widget _buildTextRow(BuildContext context, String label, String value) {
final c = context.colors;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row(
@@ -112,20 +111,20 @@ class ChargeDetailPage extends StatelessWidget {
children: [
Text(
label,
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.grey,
color: c.textTertiary,
),
),
Text(
value,
style: const TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', // 复杂字符串保持 Harmony
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
color: c.textPrimary,
),
),
],
@@ -133,8 +132,13 @@ class ChargeDetailPage extends StatelessWidget {
);
}
/// 数值行(电压/电流,数字用 Flyme,单位用 Harmony)
Widget _buildDetailRow(String label, double value, String unit) {
Widget _buildDetailRow(
BuildContext context,
String label,
double value,
String unit,
) {
final c = context.colors;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row(
@@ -142,24 +146,24 @@ class ChargeDetailPage extends StatelessWidget {
children: [
Text(
label,
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.grey,
color: c.textTertiary,
),
),
RichText(
text: TextSpan(
style: const TextStyle(
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
color: c.textPrimary,
),
children: [
TextSpan(
text: value.toStringAsFixed(2),
style: const TextStyle(fontFamily: 'Flyme'), // 🟢 数字用 Flyme
style: const TextStyle(fontFamily: 'Flyme'),
),
TextSpan(
text: unit,
+7 -5
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
class ChargeEntry extends StatelessWidget {
final String summary;
@@ -14,6 +15,7 @@ class ChargeEntry extends StatelessWidget {
@override
Widget build(BuildContext context) {
final c = context.colors;
return GestureDetector(
onTap: isCharging ? onTap : null,
child: Row(
@@ -21,11 +23,11 @@ class ChargeEntry extends StatelessWidget {
children: [
Text(
summary,
style: const TextStyle(
fontFamily: 'HarmonyOS_Sans_SC', // 🟢 混合文本用 Harmony
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black87,
color: c.textPrimary,
),
),
Row(
@@ -36,7 +38,7 @@ class ChargeEntry extends StatelessWidget {
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
color: c.textTertiary,
),
),
const SizedBox(width: 4),
@@ -46,7 +48,7 @@ class ChargeEntry extends StatelessWidget {
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
color: c.textTertiary,
),
),
],
+19 -21
View File
@@ -1,11 +1,6 @@
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
// --- 潘通色定义 ---
const Color kHealthGreen = Color(0xFF00B140);
const Color kHealthYellow = Color(0xFFEFDF00);
const Color kHealthOrange = Color(0xFFD75A16);
/// 趋势图柱状图组件
class TrendChart extends StatelessWidget {
final List<Map<String, dynamic>> data;
final int designCap;
@@ -14,6 +9,8 @@ class TrendChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
final c = context.colors;
List<Map<String, dynamic>> displayData = data.isNotEmpty
? data
: _getMockData();
@@ -48,12 +45,13 @@ class TrendChart extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'容量百分比变动趋势(近15天)',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 16,
fontWeight: FontWeight.w500,
color: c.textPrimary,
),
),
const SizedBox(height: 8),
@@ -64,13 +62,13 @@ class TrendChart extends StatelessWidget {
height: 180,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
children: [
Text(
'100',
style: TextStyle(
fontFamily: 'Flyme',
fontSize: 10,
color: Colors.grey,
color: c.textTertiary,
height: 1.0,
),
),
@@ -79,7 +77,7 @@ class TrendChart extends StatelessWidget {
style: TextStyle(
fontFamily: 'Flyme',
fontSize: 10,
color: Colors.grey,
color: c.textTertiary,
height: 1.0,
),
),
@@ -106,14 +104,14 @@ class TrendChart extends StatelessWidget {
Color barColor;
if (value > 0) {
if (value >= 80) {
barColor = kHealthGreen;
barColor = c.healthGreen;
} else if (value >= 70) {
barColor = kHealthYellow;
barColor = c.healthYellow;
} else {
barColor = Colors.red;
barColor = c.healthRed;
}
} else {
barColor = Colors.grey.shade300;
barColor = c.barEmpty;
}
double clampedValue = value.clamp(70.0, 100.0);
double heightFactor =
@@ -144,10 +142,10 @@ class TrendChart extends StatelessWidget {
bottom: 2,
child: Text(
sevenDaysAgoDate,
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 9,
color: Colors.grey,
color: c.textTertiary,
height: 1.0,
),
),
@@ -158,10 +156,10 @@ class TrendChart extends StatelessWidget {
bottom: 2,
child: Text(
todayDate,
style: const TextStyle(
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 9,
color: Colors.black,
color: c.textPrimary,
height: 1.0,
fontWeight: FontWeight.w600,
),
@@ -176,14 +174,14 @@ class TrendChart extends StatelessWidget {
],
),
if (percents.every((v) => v == 0))
const Padding(
padding: EdgeInsets.only(top: 8.0),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'暂无历史数据,请点击刷新按钮更新',
style: TextStyle(
fontFamily: 'HarmonyOS_Sans_SC',
fontSize: 12,
color: Colors.grey,
color: c.textTertiary,
),
),
),
-1
View File
@@ -1 +0,0 @@
flutter/ephemeral
-128
View File
@@ -1,128 +0,0 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "battery_health")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.lxh.battery_health")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
-88
View File
@@ -1,88 +0,0 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)
@@ -1,11 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}
@@ -1,15 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
-24
View File
@@ -1,24 +0,0 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
jni
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
-26
View File
@@ -1,26 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
-6
View File
@@ -1,6 +0,0 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}
-144
View File
@@ -1,144 +0,0 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Called when first Flutter frame received.
static void first_frame_cb(MyApplication* self, FlView *view)
{
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "battery_health");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "battery_health");
}
gtk_window_set_default_size(window, 1280, 720);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
GdkRGBA background_color;
// Background defaults to black, override it here if necessary, e.g. #00000000 for transparent.
gdk_rgba_parse(&background_color, "#000000");
fl_view_set_background_color(view, &background_color);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
// Show the window when Flutter renders.
// Requires the view to be realized so we can start rendering.
g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self);
gtk_widget_realize(GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GApplication::startup.
static void my_application_startup(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application startup.
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
}
// Implements GApplication::shutdown.
static void my_application_shutdown(GApplication* application) {
//MyApplication* self = MY_APPLICATION(object);
// Perform any actions required at application shutdown.
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
// Set the program name to the application ID, which helps various systems
// like GTK and desktop environments map this running application to its
// corresponding .desktop file. This ensures better integration by allowing
// the application to be recognized beyond its binary name.
g_set_prgname(APPLICATION_ID);
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_
-7
View File
@@ -1,7 +0,0 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/
-1
View File
@@ -1 +0,0 @@
#include "ephemeral/Flutter-Generated.xcconfig"
-1
View File
@@ -1 +0,0 @@
#include "ephemeral/Flutter-Generated.xcconfig"
@@ -1,16 +0,0 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import device_info_plus
import shared_preferences_foundation
import sqflite_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}
-705
View File
@@ -1,705 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* battery_health.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "battery_health.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* battery_health.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* battery_health.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/battery_health.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/battery_health";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/battery_health.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/battery_health";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.batteryHealth.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/battery_health.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/battery_health";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "battery_health.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "battery_health.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "battery_health.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "battery_health.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
-13
View File
@@ -1,13 +0,0 @@
import Cocoa
import FlutterMacOS
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
@@ -1,68 +0,0 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

-343
View File
@@ -1,343 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="EPT-qC-fAb">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>

Some files were not shown because too many files have changed in this diff Show More