diff --git a/.cursorrules b/.cursorrules
deleted file mode 100644
index 557d074..0000000
--- a/.cursorrules
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
-# Flutter Expert
-
-# Flutter Expert .cursorrules
-
-## Flexibility Notice
-
-**Note:** This is a recommended project structure, but be flexible and adapt to existing project structures. Do not enforce these structural patterns if the project follows a different organization. Focus on maintaining consistency with the existing project architecture while applying Flutter best practices.
-
-## Flutter Best Practices
-
-```dart
-const flutterBestPractices = [
- "Adapt to existing project architecture while maintaining clean code principles",
- "Use Flutter 3.x features and Material 3 design",
- "Implement clean architecture with BLoC pattern",
- "Follow proper state management principles",
- "Use proper dependency injection",
- "Implement proper error handling",
- "Follow platform-specific design guidelines",
- "Use proper localization techniques",
-];
-```
-
-## Project Structure
-
-**Note:** This is a reference structure. Adapt to the project's existing organization.
-
-```dart
-const projectStructure = `
-lib/
- core/
- constants/
- theme/
- utils/
- widgets/
- features/
- feature_name/
- data/
- datasources/
- models/
- repositories/
- domain/
- entities/
- repositories/
- usecases/
- presentation/
- bloc/
- pages/
- widgets/
- l10n/
- main.dart
-test/
- unit/
- widget/
- integration/
-`;
-```
-
-## Coding Guidelines
-
-```dart
-const codingGuidelines = `
-1. Use proper null safety practices
-2. Implement proper error handling with Either type
-3. Follow proper naming conventions
-4. Use proper widget composition
-5. Implement proper routing using GoRouter
-6. Use proper form validation
-7. Follow proper state management with BLoC
-8. Implement proper dependency injection using GetIt
-9. Use proper asset management
-10. Follow proper testing practices
-`;
-```
-
-## Widget Guidelines
-
-```dart
-const widgetGuidelines = `
-1. Keep widgets small and focused
-2. Use const constructors when possible
-3. Implement proper widget keys
-4. Follow proper layout principles
-5. Use proper widget lifecycle methods
-6. Implement proper error boundaries
-7. Use proper performance optimization techniques
-8. Follow proper accessibility guidelines
-`;
-```
-
-## Performance Guidelines
-
-```dart
-const performanceGuidelines = `
-1. Use proper image caching
-2. Implement proper list view optimization
-3. Use proper build methods optimization
-4. Follow proper state management patterns
-5. Implement proper memory management
-6. Use proper platform channels when needed
-7. Follow proper compilation optimization techniques
-`;
-```
-
-## Testing Guidelines
-
-```dart
-const testingTestingGuidelines = `
-1. Write unit tests for business logic
-2. Implement widget tests for UI components
-3. Use integration tests for feature testing
-4. Implement proper mocking strategies
-5. Use proper test coverage tools
-6. Follow proper test naming conventions
-7. Implement proper CI/CD testing
-`;
-```
diff --git a/.gitignore b/.gitignore
index 0b6a5c9..e1b0f5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@
.svn/
.swiftpm/
migrate_working_dir/
+.metadata
+.cursorrules
# IntelliJ related
*.iml
diff --git a/.metadata b/.metadata
deleted file mode 100644
index a704b37..0000000
--- a/.metadata
+++ /dev/null
@@ -1,30 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: "ea121f8859e4b13e47a8f845e4586164519588bc"
- channel: "stable"
-
-project_type: plugin
-
-# Tracks metadata for the flutter migrate command
-migration:
- platforms:
- - platform: root
- create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- - platform: android
- create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
-
- # User provided section
-
- # List of Local paths (relative to this file) that should be
- # ignored by the migrate tool.
- #
- # Files that are not part of the templates will be ignored by default.
- unmanaged_files:
- - 'lib/main.dart'
- - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/.pubignore b/.pubignore
new file mode 100644
index 0000000..5364ca6
--- /dev/null
+++ b/.pubignore
@@ -0,0 +1,11 @@
+AGENTS.md
+example/AGENTS.md
+ARCHITECTURE_REFACTOR_PLAN.yaml
+PLUGIN_QUALITY_ROADMAP.md
+workflow_context.md
+.cursorrules
+.idea/
+*.iml
+plan/
+scripts/
+integration/channel/contracts/macos_battery_evolution_plan.yaml
diff --git a/AGENTS.md b/AGENTS.md
index 461c15f..cc11b3e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,11 +1,12 @@
# Repository Guidelines
## Project Structure & Module Organization
-- `lib/`: Dart API; `flutter_battery.dart` exposes `FlutterBattery`, and the platform interface plus MethodChannel default live alongside it.
-- `android/`, `ios/`: Native implementations; keep channel names and payloads in sync with the Dart interface and avoid committing `build/`.
-- `example/`: Demo app for manual QA and showcasing notifications; run it on a device/emulator to validate flows.
-- `test/`: Unit tests (`*_test.dart`) covering public API and channel behavior.
-- `integration/channel/contracts/channel_contract.yaml`: Contract for method/event channels; edit together with Dart and native changes.
+- `lib/`: Dart API; `flutter_battery.dart` exposes `FlutterBattery` + typed models; `lib/src/` holds internal contracts (`battery_channel_contract.dart`) and platform capability models (`platform_capabilities.dart`). `flutter_battery_platform_interface.dart` and `flutter_battery_method_channel.dart` live alongside it.
+- `android/`: Native implementation. Channel names and payloads must stay in sync with `lib/src/battery_channel_contract.dart`. Avoid committing `build/`.
+- `macos/`: macOS native implementation. Explicitly returns unsupported capabilities via `getPlatformCapabilities` for notifications/BLE/peer sync.
+- `example/`: Demo app for manual QA and showcasing notifications. IoT demo (`iot/native`, `iot/stream`) is example-only (not part of plugin public API). Run it on a device/emulator to validate flows.
+- `test/`: Unit tests (`*_test.dart`) covering public API, channel behavior, and capability queries.
+- `integration/channel/contracts/channel_contract.yaml`: Single source of truth for method/event channel contracts; edit together with Dart and native changes.
- `scripts/bootstrap_iot.sh`: Recreate integration scaffolding if a clean checkout is missing folders.
## Build, Test, and Development Commands
@@ -18,13 +19,13 @@
## Coding Style & Naming Conventions
- Follow `flutter_lints` (`analysis_options.yaml` relaxes `constant_identifier_names` for platform constants).
- Files use `snake_case.dart`; classes/enums `PascalCase`; members and locals `camelCase`.
-- Keep channel method/event names and payload keys aligned with `FlutterBatteryPlatform`.
-- Favor small, nullable-safe methods and concise comments only where intent is non-obvious.
+- Channel method/event names and payload keys must use `BatteryChannelNames`, `BatteryMethodNames`, `BatteryEventTypes`, `BatteryPayloadKeys` from `lib/src/battery_channel_contract.dart` — no raw string literals outside native registration.
+- Favor small, nullable-safe methods. Comments only where intent is non-obvious.
## Testing Guidelines
- Place new cases beside the feature under test; use descriptive `feature_behavior_test.dart` names.
-- Mock the platform interface for unit coverage of MethodChannel and stream behaviors; avoid hardware dependencies.
-- For native changes, run the `example/` app once on Android (and iOS when available) to verify battery readings and event streams.
+- Mock the platform interface for unit coverage of MethodChannel, stream behaviors, and capability queries; avoid hardware dependencies.
+- For native changes, run the `example/` app on Android and macOS to verify battery readings, event streams, and capability-based feature gating.
## Commit & Pull Request Guidelines
- Use history-aligned prefixes (`feat:`, `style:`, `fix:`, `docs:`) plus an imperative summary.
@@ -35,3 +36,12 @@
- Exclude secrets, keystores, and generated `build/` artifacts.
- Request only minimum permissions when editing manifests.
- When releasing, bump `pubspec.yaml` version and update `CHANGELOG.md` together.
+
+## Key Architecture Decisions (post-refactor)
+- **Channel Contract Constants**: All channel names, method names, event types, and payload keys are centralized in `lib/src/battery_channel_contract.dart`. Raw string literals are banned from business logic.
+- **Platform Capabilities**: `BatteryFeature` enum + `BatteryPlatformCapabilities` value object. Query via `FlutterBattery.getPlatformCapabilities()` or `isFeatureSupported(BatteryFeature)`. macOS explicitly returns `false` for notifications/BLE/peer sync.
+- **Predictable Failure**: `MissingPluginException` for optional features is mapped to `UnsupportedBatteryFeatureException` so consumers never see raw `MissingPluginException`.
+- **Normalized Events**: Event stream payloads always include a `type` field (`BATTERY_LEVEL`, `BATTERY_INFO`, `BATTERY_HEALTH`, `BATTERY_UNAVAILABLE`). Both `level` and `batteryLevel` keys are present for backward compatibility.
+- **macOS Callback Bridge**: `BatteryMonitor` has callback setters wired through `FlutterBatteryPlugin` to invoke MethodChannel methods (`onBatteryLevelChanged`, `onBatteryInfoChanged`, `onBatteryHealthChanged`).
+- **No iOS**: iOS platform declaration removed from `pubspec.yaml` until a native implementation is added.
+- **Example IoT Isolation**: `iot/native` and `iot/stream` channels are example-only, not part of plugin public API. Documented in `channel_contract.yaml` under `example_only_android`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 093c07e..07acb93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+## Unreleased
+
+* 重构平台架构:集中通道常量、新增平台能力查询 API
+* 新增 `BatteryFeature`、`BatteryPlatformCapabilities`、`UnsupportedBatteryFeatureException`
+* macOS 支持事件规范化、回调桥接、显式声明不支持的通知/BLE/IoT 功能
+* 示例页面通过能力对象控制功能入口
+* 移除 iOS 声明(待未来实现)
+* 更新通道契约文档,覆盖所有方法/事件通道与平台支持矩阵
+* 更新 README、AGENTS.md、example/AGENTS.md
+
## 0.0.3
* 新增高级整合API `configureBattery`,一次性配置所有电池监控功能
diff --git a/IOT_UPGRADE_PLAN.md b/IOT_UPGRADE_PLAN.md
deleted file mode 100644
index 3d7df9b..0000000
--- a/IOT_UPGRADE_PLAN.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# Flutter Battery → 标准 Flutter 混生 Android IoT 改造文档
-
-## 0. Repo Snapshot (当前 /mnt/e/flutter_battery)
-- flutter_battery/: 现有插件,Android 入口 `com.example.flutter_battery.FlutterBatteryPlugin`
-- android/: 插件 Android 工程 (AAR);Gradle Wrapper 可沿用
-- example/: Flutter 示例 Runner,可作为新 app/ 的 UI 参考
-- lib/, test/, analysis_options.yaml: 插件 Dart 层逻辑,需保留
-- pubspec.yaml: SDK 约束 `>=3.0.0 <4.0.0`,升级时保持兼容
-
-## 1. 目标目录与职责
-```
-repo-root/
-├─ flutter_battery/ # 电池子系统 SDK,MethodChannel/EventChannel 仅做电池能力
-├─ app/ # Flutter 混生外壳,UI/状态管理 + channel 统一封装
-│ ├─ lib/ # 设备页/仪表盘/曲线/设置 + bloc/provider
-│ ├─ android/app/ # Runner,集成 android-iot-native + flutter_battery
-│ └─ ios/ # 预留,最小依赖 flutter_battery
-├─ android-iot-native/ # Kotlin/Jetpack BLE+Foreground Service+Telemetry Library
-│ ├─ src/main/java|kotlin/… # BLE 扫描/连接/指令、Service、Repository
-│ ├─ src/main/res/ # Foreground 通知、布局、string
-│ └─ src/main/AndroidManifest.xml # Service + permission 声明
-├─ integration/ # Channel contract、proto、bridge 测试桩
-├─ scripts/ # 重组脚本、CI helper、bootstrap
-├─ build.gradle / settings.gradle # 根构建脚本,统一版本 catalog
-└─ .github/workflows/… (可选) # CI,包含 flutter build + gradle lint
-```
-
-## 2. 改造步骤清单
-1. `git mv example app` 或 `flutter create --platforms=android -a kotlin --project-name iot_shell app` (推荐新建,避免插件示例耦合)
-2. `flutter pub add --path ../flutter_battery flutter_battery` (在 app/)
-3. `mkdir -p android-iot-native/src/main/{java,kotlin,res}` 并初始化 `build.gradle.kts`
-4. 根目录建立 `settings.gradle.kts`,`include(":app", ":android-iot-native", ":flutter_battery")`
-5. `app/android/app/build.gradle`:应用插件 `com.android.application`,`implementation(project(":android-iot-native"))`
-6. `android-iot-native` 中实现 `MethodChannel("iot/native")` handler + `EventChannel("iot/stream")` emitter,通过 `BinaryMessenger` 注入 (App Runner 或 FlutterEngine)
-7. `flutter_battery` 暴露的 `FlutterBatteryPlugin` 保持不变;在 app/lib 建立 `BatterySubsystemRepository` 聚合插件 + native stream
-8. `integration/channel` 维护 `channel_contract.yaml`,描述 Method/args/Event payloads,供 Dart/Android 双向验证
-9. 根级 `gradle/libs.versions.toml` 统一版本:`kotlin=1.9.x、agp=8.1.x、coreKtx=1.12.x、lifecycle=2.6.x、room=2.5.x、coroutines=1.7.x`
-10. Android Studio/Gradle Sync,确认 `android-iot-native` 作为 library,`app` 为 application,`flutter_battery` 仍由 Flutter tool 管理
-
-## 3. 模块创建命令 & Gradle 关联
-```bash
-# Flutter 外壳 (repo 根执行)
-flutter create --project-name iot_shell --platforms=android -a kotlin app
-
-# Kotlin 库模块
-mkdir -p android-iot-native/src/main/{java,kotlin,res}
-cat <<'GRADLE' > android-iot-native/build.gradle.kts
-plugins {
- id("com.android.library")
- kotlin("android")
- id("kotlin-kapt")
-}
-android {
- namespace = "com.example.iot.native"
- compileSdk = libs.versions.compileSdk.get().toInt()
- defaultConfig {
- minSdk = 26
- targetSdk = 34
- }
-}
-dependencies {
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.lifecycle.runtime)
- implementation(libs.androidx.activity.ktx)
- implementation(libs.androidx.room.runtime)
- kapt(libs.androidx.room.compiler)
- implementation(libs.kotlinx.coroutines.android)
-}
-GRADLE
-```
-```kotlin
-// 根 settings.gradle.kts
-pluginManagement {
- repositories { google(); mavenCentral(); gradlePluginPortal() }
-}
-include(":app", ":android-iot-native", ":flutter_battery")
-project(":flutter_battery").projectDir = file("flutter_battery")
-```
-```groovy
-// app/android/app/build.gradle (精简)
-plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
-}
-android {
- namespace "com.example.iot.shell"
- compileSdk rootProject.ext.compileSdk
- defaultConfig {
- applicationId "com.example.iot.shell"
- minSdk 26
- targetSdk 34
- versionCode 1
- versionName "1.0"
- }
-}
-dependencies {
- implementation project(':android-iot-native')
- implementation project(':flutter_battery')
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
-}
-```
-
-## 4. 权限与 Manifest 归属 (API 31+)
-| Permission/API | 归属 | 用途/说明 |
-|---------------------------|------|-----------|
-| `android.permission.BLUETOOTH_SCAN` (31)| app Manifest `` + feature `android.hardware.bluetooth_le` |
-| `android.permission.BLUETOOTH_CONNECT` | app Manifest |
-| `android.permission.BLUETOOTH_ADVERTISE` (可选) | app Manifest |
-| `android.permission.ACCESS_FINE_LOCATION` | app Manifest,BLE 扫描需要 |
-| `android.permission.ACCESS_COARSE_LOCATION` | app Manifest,兼容旧机 |
-| `android.permission.ACCESS_BACKGROUND_LOCATION` (maxSdk30) | app Manifest Queries/back-compat |
-| `android.permission.POST_NOTIFICATIONS` | app Manifest,前台 Service 通知 |
-| `android.permission.FOREGROUND_SERVICE` | app Manifest |
-| `android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE` (34+) | 库 Manifest (android-iot-native) |
-| `` | 库 Manifest |
-| `` (若使用 App Startup) | 库 Manifest |
-| `` (BLE 扫描) | app Manifest |
-
-## 5. Channel/交互约束
-- MethodChannel `iot/native`: `scanDevices(args: {filters, timeout})`, `connect(deviceId)`, `startTelemetry(battery=true, sensors=true)`, `stopTelemetry()`, `requestBatterySnapshot()`
-- EventChannel `iot/stream`: payload schema `{type, deviceId, ts, data}`;type 包括 `telemetry`, `battery`, `connection`
-- app/lib 建立 `NativeBridge`,所有 UI 与 native 通信在此模块;flutter_battery 暴露的 `BatteryLevelStream` 作为 `type=battery` 的唯一来源
-- android-iot-native 内部模块:`ble`, `service`, `telemetry`, `batteryreport`,均通过 `ChannelBridge` 统一出口
-
-## 6. Bootstrap 脚本 (scripts/bootstrap_iot.sh)
-```bash
-#!/usr/bin/env bash
-set -euo pipefail
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-mkdir -p app/lib app/android app/ios
-mkdir -p android-iot-native/src/main/{java,kotlin,res}
-mkdir -p integration/channel/contracts
-mkdir -p scripts
-
-: > app/lib/main.dart
-: > app/android/app_build_notes.md
-: > android-iot-native/build.gradle.kts
-: > android-iot-native/src/main/AndroidManifest.xml
-: > integration/channel/contracts/channel_contract.yaml
-```
-
-## 7. 验收 Checklist
-- `./gradlew :app:assembleDebug`, `:android-iot-native:assemble`, `flutter build apk` 均成功
-- Gradle Sync / `./gradlew tasks` 无 module 丢失;`settings.gradle.kts` 含三个模块
-- Manifest Merge report (Android Studio → Analyzer) 无冲突;所需权限全部在最终 merged manifest
-- `./gradlew :app:lintRelease` 与 `:android-iot-native:lint` 通过,BLE 权限告警关闭
-- Flutter `MethodChannel`、`EventChannel` 注册点存在 (`app/android/app/src/main/kotlin/.../MainActivity.kt`);`NativeBridge` Dart 层 API 与 android-iot-native 对齐
-- Telemetry ForegroundService 在 Android 12+ 正常弹出通知 (手动验证);POST_NOTIFICATIONS runtime grant 流程完成
diff --git a/LICENSE b/LICENSE
index ba75c69..4eee2a9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1 +1,21 @@
-TODO: Add your license here.
+MIT License
+
+Copyright (c) 2024 flutter_battery contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index b96f53b..a5c25b6 100644
--- a/README.md
+++ b/README.md
@@ -25,16 +25,12 @@ Flutter插件,用于监控设备电池电量并在电量低于特定阈值时
```mermaid
graph TB
- %% 样式定义
classDef flutter fill:#61DAFB,stroke:#333,stroke-width:1px,color:#333
classDef android fill:#3DDC84,stroke:#333,stroke-width:1px,color:#333
classDef methodChannel fill:#FFA726,stroke:#333,stroke-width:1px,color:#333
classDef core fill:#E57373,stroke:#333,stroke-width:1px,color:#333
- %% Flutter 应用层
FlutterApp["Flutter 应用层"]:::flutter
-
- %% 主动查询模式
FlutterApp -->|"1. getBatteryLevel()"| FlutterBattery["FlutterBattery 类"]:::flutter
FlutterBattery -->|"2. getBatteryLevel()"| PlatformInterface["FlutterBatteryPlatform"]:::flutter
PlatformInterface -->|"3. invokeMethod('getBatteryLevel')"| MethodChannel["MethodChannel"]:::methodChannel
@@ -63,13 +59,11 @@ graph TB
```mermaid
graph TB
- %% 样式定义
classDef flutter fill:#61DAFB,stroke:#333,stroke-width:1px,color:#333
classDef android fill:#3DDC84,stroke:#333,stroke-width:1px,color:#333
classDef eventChannel fill:#66BB6A,stroke:#333,stroke-width:1px,color:#333
classDef core fill:#E57373,stroke:#333,stroke-width:1px,color:#333
- %% Flutter 应用层
FlutterApp["Flutter 应用层"]:::flutter
FlutterBatteryStream["FlutterBattery.batteryInfoStream"]:::flutter
EventChannel["EventChannel"]:::eventChannel
@@ -78,7 +72,6 @@ graph TB
BatteryMonitor["BatteryMonitor"]:::core
AndroidBatteryManager["Android BatteryManager"]:::android
- %% 调用链
FlutterApp -->|"1. batteryInfoStream.listen()"| FlutterBatteryStream
FlutterBatteryStream -->|"2. eventChannel.receiveBroadcastStream()"| EventChannel
EventChannel -->|"3. onListen()"| EventHandler
@@ -97,13 +90,11 @@ graph TB
```mermaid
graph TB
- %% 样式定义
classDef flutter fill:#61DAFB,stroke:#333,stroke-width:1px,color:#333
classDef android fill:#3DDC84,stroke:#333,stroke-width:1px,color:#333
classDef methodChannel fill:#FFA726,stroke:#333,stroke-width:1px,color:#333
classDef core fill:#E57373,stroke:#333,stroke-width:1px,color:#333
- %% 定义节点
FlutterApp["Flutter 应用层"]:::flutter
AndroidBroadcast["Android 电池广播"]:::android
BatteryReceiver["电池广播接收器"]:::android
@@ -114,7 +105,6 @@ graph TB
FlutterMethodChannel["MethodChannelFlutterBattery"]:::flutter
FlutterBattery["FlutterBattery"]:::flutter
- %% 调用链
AndroidBroadcast -->|"1. ACTION_BATTERY_CHANGED"| BatteryReceiver
BatteryReceiver -->|"2. onReceive()"| BatteryMonitor
BatteryMonitor -->|"3. 更新 lastBatteryLevel"| BatteryMonitor
@@ -145,50 +135,24 @@ graph TB
| 实现复杂度 | 较简单 | 较复杂,需处理事件流 |
| 电量影响 | 频繁查询可能增加耗电 | 合理配置可减少耗电 |
-## 关键API调用链
-
-### 主动查询模式
-
-1. Flutter层调用 `FlutterBattery.getBatteryLevel()`
-2. 通过Platform Interface转发到MethodChannel
-3. MethodChannel通过JNI调用Android原生方法
-4. MethodChannelHandler接收并处理请求
-5. 调用BatteryMonitor.getBatteryLevel()
-6. 使用Android BatteryManager获取电池电量
-7. 结果原路返回到Flutter层
-
-### 推送模式
-
-#### EventChannel方式
-1. Flutter层订阅 `FlutterBattery.batteryInfoStream`
-2. EventChannel设置监听器
-3. EventChannelHandler.onListen()被触发
-4. 启动TimerManager定时器
-5. 定时器周期性调用pushBatteryInfo()
-6. 获取电池信息并通过eventSink推送到Flutter
-7. Flutter层的Stream监听器接收数据
-
-#### 广播接收器方式
-1. 注册接收ACTION_BATTERY_CHANGED广播
-2. 电池状态变化时触发onReceive()
-3. 更新电池状态并通过MethodChannel回调通知Flutter
-
-## 项目结构(最新)
+## 项目结构
```
.
├── lib/
-│ ├── flutter_battery.dart # 电池 API、配置与流封装
+│ ├── flutter_battery.dart # 电池 API、配置、流封装与平台能力查询
│ ├── flutter_battery_platform_interface.dart
│ ├── flutter_battery_method_channel.dart
+│ ├── src/
+│ │ ├── battery_channel_contract.dart # 通道常量(名称/方法/事件/负载键)
+│ │ └── platform_capabilities.dart # BatteryFeature 枚举、BatteryPlatformCapabilities、UnsupportedBatteryFeatureException
│ ├── flutter_bluetooth.dart # BLE 门面(扫描/连接/写特征)
│ ├── flutter_bluetooth_platform_interface.dart
│ ├── flutter_bluetooth_method_channel.dart
│ ├── peer_battery_service.dart # Master/Slave 对等电池同步流
│ └── battery_animation.dart # 电池可视化组件
├── android/src/main/
-│ ├── AndroidManifest.xml # 权限声明 + NotificationAlarmReceiver/SyncService 注册
-│ ├── resources/META-INF/services/... # FlutterPlugin SPI 自动注册入口
+│ ├── AndroidManifest.xml
│ └── kotlin/com/example/
│ ├── flutter_battery/
│ │ ├── FlutterBatteryPlugin.kt # 注册电池/BLE/Peer/IoT 通道
@@ -197,19 +161,18 @@ graph TB
│ │ └── ble/ # BleManager、GattServerManager、GattClientManager
│ ├── iot/nativekit/ # Channels、NativeViewModel、仓库与 SyncService
│ └── push_notification/ # PushNotificationManager 与闹钟接收器
+├── macos/flutter_battery/Classes/
+│ ├── FlutterBatteryPlugin.swift # macOS 插件注册 + getPlatformCapabilities + callback bridge
+│ └── BatteryMonitor.swift # 电池读取、事件推送(BATTERY_LEVEL/INFO/HEALTH/UNAVAILABLE)
├── example/lib/ # Dashboard、电池详情、事件日志、角色选择/主从页
-├── integration/channel/contracts/ # 方法/事件通道契约
+├── integration/channel/contracts/ # 方法/事件通道契约(含平台支持矩阵)
├── scripts/bootstrap_iot.sh # 集成目录初始化脚本
-└── test/ # Dart 单元测试
+└── test/ # Dart 单元测试(含能力查询、事件规范化测试)
```
-- `lib/`:`FlutterBattery` 汇总配置/回调/监听,`FlutterBluetooth` 暴露 BLE 能力,`peer_battery_service.dart` 提供对等电池同步流,附带电池动画组件。
-- `android/src/main/`:Manifest 挂载权限与 `NotificationAlarmReceiver`/`SyncService` 组件,`resources/META-INF/services/...` 暴露 `FlutterBatteryPlugin` 以便自动注册,`kotlin/com/example/` 下包含插件所有原生实现与通知/IoT 模块。
-- `android/src/main/kotlin/com/example/flutter_battery/core`:`BatteryMonitor` 读取电量/健康并调度监听,`NotificationHelper` 处理通知权限与展示,`TimerManager` 管理周期任务。
-- `android/src/main/kotlin/com/example/flutter_battery/channel`:`MethodChannelHandler` 统一处理电池、BLE、Peer 方法调用;`EventChannelHandler`/`Ble*EventChannelHandler`/`PeerEventChannelHandler` 推送事件。
-- `android/src/main/kotlin/com/example/flutter_battery/ble`:`BleManager` 扫描/连接/写特征,`GattServerManager`(slave)/`GattClientManager`(master) 同步本地与远端电池并上报 `PeerState`。
-- `android/src/main/kotlin/com/example/iot/nativekit`:`Channels` 挂载 `iot/native` & `iot/stream`,`NativeViewModel` 聚合 Telemetry/Power/BLE 仓库,`SyncService` 后台推送遥测。
-- `example/lib`:仪表盘首页、事件日志、电池详情以及 master/slave 角色切换演示页。
+- `lib/src/battery_channel_contract.dart`:集中管理所有 MethodChannel/EventChannel 名称、方法名、事件类型与 payload key,严禁业务代码使用原始字符串。
+- `lib/src/platform_capabilities.dart`:`BatteryFeature` 枚举定义所有可查询的功能点,`BatteryPlatformCapabilities` 值对象封装平台能力映射,`UnsupportedBatteryFeatureException` 替代原始 `MissingPluginException`。
+- `macos/`:`FlutterBatteryPlugin.swift` 通过 `getPlatformCapabilities` 显式声明不支持 nativeNotifications/blePeerSync/iotExampleBridge,`BatteryMonitor.swift` 发送规范化事件(含 `type` 字段与 `batteryLevel`/`level` 双键),同时通过 callback bridge 驱动 `onBatteryLevelChanged`/`onBatteryInfoChanged`/`onBatteryHealthChanged` 方法回调。
## 功能特性
@@ -221,25 +184,18 @@ graph TB
- 支持定时或即时推送通知
- 电池电量动画组件可视化展示
- 电池性能优化建议、防抖动机制
-- IoT 原生桥接:模拟设备扫描、连接、遥测与电池事件(`iot/native` + `iot/stream`)
-- 线程安全的资源管理和错误处理,跨平台支持(Android)
+- **平台能力查询**:通过 `getPlatformCapabilities()` 查询当前平台支持的功能(macOS 显式返回不支持项)
+- **可预测的失败**:可选功能缺失时抛出 `UnsupportedBatteryFeatureException` 而非 `MissingPluginException`
## 功能模块分区
-- **Battery 核心**:`FlutterBattery` + `BatteryMonitor`,覆盖主动查询、电量/信息/健康推送、低电量监测、优化建议与通知调度。
+- **Battery 核心**:`FlutterBattery` + `lib/src/` 契约/能力模型,覆盖主动查询、电量/信息/健康推送、低电量监测、优化建议与通知调度。
+- **平台能力层**:`BatteryFeature` 枚举 + `BatteryPlatformCapabilities` 值对象,通过 `getPlatformCapabilities()` 统一查询。Android 汇报全部支持,macOS 显式标记 notifications/BLE/peer sync 为不支持。
- **BLE 设备管理**:`FlutterBluetooth` -> `BleManager`,支持按服务过滤的扫描、连接、特征写入与连接事件流。
- **Peer 电池同步**:`PeerBatteryService` + `GattServerManager`/`GattClientManager`,在 master/slave 模式下同步本地与远端电池并推送对等状态。
- **通知体系**:`NotificationHelper`、`PushNotificationManager` 负责权限处理、即时/延迟通知与前台提醒。
-- **IoT 演示层**:`iot/nativekit` 将 Telemetry/Power/BLE 仓库通过 `iot/native` & `iot/stream` 暴露给示例应用。
-- **示例与 UI**:`example/lib` 内置仪表盘、事件流日志、电池详情和角色切换页面,配合 `BatteryAnimation` 展示。
-
-## 数据流流转方案
-
-- **电池主动查询(MethodChannel `flutter_battery`)**:`FlutterBattery.*` -> `FlutterBatteryPlatform` -> `MethodChannelHandler` -> `BatteryMonitor` -> Android BatteryManager -> 结果返回 Flutter。
-- **电池推送(EventChannel `flutter_battery/battery_stream`)**:`EventChannelHandler` 通过 `TimerManager` 轮询;默认推送 `{batteryLevel,timestamp}`,开启 info/health 后携带 `type == BATTERY_INFO/BATTERY_HEALTH` 的完整字段,频率由 `setPushInterval` / `setBatteryInfoPush` / `setBatteryHealthPush` 控制。
-- **BLE 扫描/连接(`flutter_battery/ble_methods`)**:Flutter 调用 `startScan/connect/writeCharacteristic` -> `MethodChannelHandler` -> `BleManager`;扫描结果经 `ble_scan_events` 推送设备列表,连接状态经 `ble_connection_events` 推送。
-- **对等电池同步(`flutter_battery/peer_methods` + `peer_events`)**:`startSlaveMode` 启动 GATT Server 广播本地电池;`startMasterMode/masterConnectToDevice` 启动 GATT Client 读取远端电池并写入本地电量;`PeerEventChannelHandler` 将 `{role,localBattery,remoteBattery,connected}` 推送给 Flutter `peerBatteryStream`。
-- **IoT 演示流(`iot/native` -> `iot/stream`)**:示例调用 MethodChannel 控制扫描/连接/SyncService;`Channels` 订阅 `NativeViewModel` 的 devices/telemetry/battery Flow,并以 `{type: devices|telemetry|battery, payload: ...}` 形式推送到 `EventChannel('iot/stream')`。
+- **IoT 演示层(仅示例)**:`iot/nativekit` 将 Telemetry/Power/BLE 仓库通过 `iot/native` & `iot/stream` 暴露给示例应用,**非插件公共 API**。
+- **示例与 UI**:`example/lib` 内置仪表盘、事件流日志、电池详情和角色切换页面,配合 `BatteryAnimation` 展示,通过能力对象控制功能入口启用/禁用。
## 原生通道与接口说明
@@ -247,78 +203,55 @@ graph TB
| 方法 | 说明 | 参数 | 返回 |
| --- | --- | --- | --- |
-| `getPlatformVersion()` | 返回 Android 版本 | - | `String` |
+| `getPlatformVersion()` | 返回平台版本 | - | `String` |
+| `getPlatformCapabilities()` | 返回平台能力映射 | - | `Map` |
| `getBatteryLevel()` | 获取当前电量 | - | `int` (0-100) |
-| `getBatteryInfo()` | 获取完整电池信息 | - | `Map` `{level,isCharging,temperature,voltage,state,timestamp}` |
-| `getBatteryHealth()` | 获取电池健康状态 | - | `Map` `{state,statusLabel,riskLevel,recommendations,...}` |
+| `getBatteryInfo()` | 获取完整电池信息 | - | `Map` |
+| `getBatteryHealth()` | 获取电池健康状态 | - | `Map` |
| `getBatteryOptimizationTips()` | 返回优化建议 | - | `List` |
-| `setBatteryLevelThreshold()` | 启用低电量监控 | `threshold,title,message,intervalMinutes,useFlutterRendering` | `bool` |
+| `setBatteryLevelThreshold()` | 启用低电量监控 | threshold,title,message,... | `bool` |
| `stopBatteryMonitoring()` | 停止低电量监控 | - | `bool` |
-| `setPushInterval()` | 设置推送间隔与防抖 | `intervalMs,enableDebounce` | `bool` |
+| `setPushInterval()` | 设置推送间隔与防抖 | intervalMs,enableDebounce | `bool` |
| `startBatteryLevelListening()` / `stopBatteryLevelListening()` | 开关电量广播监听 | - | `bool` |
-| `startBatteryInfoListening()` / `stopBatteryInfoListening()` | 开关完整信息推送 | `intervalMs` | `bool` |
-| `startBatteryHealthListening()` / `stopBatteryHealthListening()` | 开关电池健康推送 | `intervalMs` | `bool` |
-| `scheduleNotification()` / `showNotification()` / `sendNotification()` | 调度或立即显示系统通知 | `title,message,delay/delayMinutes` | `bool` |
-
-> Flutter 侧的 `FlutterBattery.configureBattery / configureBatteryMonitor / configureBatteryCallbacks` 封装了上表中的多个原生调用,推荐优先使用高阶 API。
+| `startBatteryInfoListening()` / `stopBatteryInfoListening()` | 开关完整信息推送 | intervalMs | `bool` |
+| `startBatteryHealthListening()` / `stopBatteryHealthListening()` | 开关电池健康推送 | intervalMs | `bool` |
+| `scheduleNotification()` / `showNotification()` / `sendNotification()` | 调度或立即显示系统通知 | title,message,delay | `bool` |
#### `flutter_battery/battery_stream` EventChannel
-- 默认 payload:`{batteryLevel: int, timestamp: int}`(电量心跳)
-- `type == "BATTERY_INFO"`:携带 `BatteryInfo` 字段
-- `type == "BATTERY_HEALTH"`:携带 `BatteryHealth` 字段(风险等级、建议列表等)
-- 所有事件均由 `EventChannelHandler` 管理,支持 `setPushInterval` 和 `setBatteryInfoPush / setBatteryHealthPush` 控制频率。
-
-### `flutter_battery/ble_methods` MethodChannel (BLE 能力)
-
-| 方法 | 说明 | 参数 | 返回 |
-| --- | --- | --- | --- |
-| `isBleAvailable()` | 检查设备是否支持 BLE | - | `bool` |
-| `isBleEnabled()` | 检查 BLE 是否已开启 | - | `bool` |
-| `startScan()` / `stopScan()` | 开始/停止扫描,支持 service UUID 过滤 | `serviceUuid?` | `void` |
-| `connect()` / `disconnect()` | 连接或断开指定设备 | `deviceId, autoConnect?` / `deviceId?` | `void` |
-| `writeCharacteristic()` | 写特征值(默认带响应) | `deviceId,serviceUuid,characteristicUuid,value[],withResponse` | `bool` |
-| `subscribeToCharacteristic()` / `unsubscribeFromCharacteristic()` | Dart 端 API 已暴露,Android 端暂未实现订阅逻辑 | 同 write 参数 | `Stream>` / `void` |
-
-#### `flutter_battery/ble_scan_events` & `flutter_battery/ble_connection_events` EventChannel
+规范化事件 payload,始终包含 `type` 字段:
-- `ble_scan_events`:推送扫描到的设备列表,payload `[ {id,name,rssi} ]`。
-- `ble_connection_events`:推送连接状态,payload `{state: connected|connecting|disconnecting|disconnected, deviceId, error?}`。
+| 事件类型 | 必填字段 | 可选字段 |
+|---------|---------|---------|
+| `BATTERY_LEVEL` | type, timestamp | batteryLevel, level, unavailableReason |
+| `BATTERY_INFO` | type, timestamp, isCharging, state | level, batteryLevel, temperature, voltage, ... |
+| `BATTERY_HEALTH` | type, timestamp, state, statusLabel, isGood, riskLevel, recommendations | level, batteryLevel, healthPercentage, maxCapacity, ... |
+| `BATTERY_UNAVAILABLE` | type, timestamp | unavailableReason |
+| `BATTERY_ERROR` | type, timestamp | error |
-### `flutter_battery/peer_methods` MethodChannel (对等电池同步)
+### 蓝牙与对等电池同步通道
-| 方法 | 说明 | 参数 | 返回 |
-| --- | --- | --- | --- |
-| `startMasterMode()` | 启动 GATT Client 并扫描 slave | - | `void` |
-| `startSlaveMode()` | 启动 GATT Server 广播本地电池 | - | `void` |
-| `stopMasterMode()` / `stopSlaveMode()` | 停止 master/slave 模式 | - | `void` |
-| `stopAllPeerModes()` | 同时关闭 master 与 slave | - | `void` |
-| `masterConnectToDevice()` | master 连接指定 slave 设备 | `deviceId` | `void` |
-
-#### `flutter_battery/peer_events` EventChannel
-
-- payload:`{role: master|slave, localBattery: int, remoteBattery: int?, connected: bool}`。
-
-### `iot/native` MethodChannel (IoT 模块)
-
-| 方法 | 说明 | 参数 | 返回 |
-| --- | --- | --- | --- |
-| `scanDevices` | 开始扫描模拟 BLE 设备 | - | `void` |
-| `stopScan` | 停止扫描 | - | `void` |
-| `connect` | 连接指定设备 | `deviceId` | `void` |
-| `disconnect` | 断开当前设备 | - | `void` |
-| `startSync` | 启动前台同步服务(推送遥测) | - | `void` |
-| `stopSync` | 停止同步服务 | - | `void` |
+见原生层文档(Android 可选功能,macOS 不支持)。
-#### `iot/stream` EventChannel
+### `iot/native` & `iot/stream`(示例专用)
-事件 payload 统一结构 `{type: String, payload: ...}`:
+**非插件公共 API**。仅 Android 示例应用使用,用于演示 MethodChannel/EventChannel 通信。
-- `type == "devices"`:`payload` 为设备列表(字段 `id,name,rssi,connected`)。
-- `type == "telemetry"`:`payload` 为 `Telemetry` Map(`timestamp,speed,batteryPct`)。
-- `type == "battery"`:`payload` 为 `{value: Int}`,模拟远端设备电量。
+## 平台支持矩阵
-> 插件内部已在 `FlutterBatteryPlugin` 中调用 `IotNativeInitializer.attach()`,示例应用只需订阅 `EventChannel('iot/stream')` 即可。
+| 能力 | Android | macOS |
+|-----|---------|-------|
+| batteryLevel | ✅ | ✅ |
+| batteryInfo | ✅ | ✅ |
+| batteryHealth | ✅ | ✅ |
+| batteryLevelStream | ✅ | ✅ |
+| batteryInfoStream | ✅ | ✅ |
+| batteryHealthStream | ✅ | ✅ |
+| lowBatteryMonitoring | ✅ | ✅ |
+| nativeNotifications | ✅ | ❌ |
+| scheduledNotifications | ✅ | ❌ |
+| blePeerSync | ✅ | ❌ |
+| iotExampleBridge | ✅ | ❌ |
## 安装
@@ -340,6 +273,21 @@ dependencies:
import 'package:flutter_battery/flutter_battery.dart';
```
+### 查询平台能力
+
+```dart
+final plugin = FlutterBattery();
+
+// 获取完整能力对象
+final capabilities = await plugin.getPlatformCapabilities();
+if (capabilities.isSupported(BatteryFeature.nativeNotifications)) {
+ // 支持原生通知
+}
+
+// 快捷查询
+final hasBlePeerSync = await plugin.isFeatureSupported(BatteryFeature.blePeerSync);
+```
+
### 初始化插件
```dart
@@ -348,81 +296,38 @@ final flutterBatteryPlugin = FlutterBattery();
### 快速集成(推荐)
-使用一次性配置方法设置所有电池监控功能:
-
```dart
-// 配置所有电池监控功能
await flutterBatteryPlugin.configureBattery(
BatteryConfiguration(
- // 基本监听配置
monitorConfig: BatteryMonitorConfig(
- monitorBatteryLevel: true, // 是否监控电池电量
- monitorBatteryInfo: true, // 是否监控电池完整信息
- intervalMs: 1000, // 电量更新间隔(毫秒)
- batteryInfoIntervalMs: 5000, // 电池信息更新间隔(毫秒)
- enableDebounce: true, // 启用防抖动
+ monitorBatteryLevel: true,
+ monitorBatteryInfo: true,
+ intervalMs: 1000,
+ batteryInfoIntervalMs: 5000,
+ enableDebounce: true,
),
-
- // 低电量监控配置
lowBatteryConfig: BatteryLevelMonitorConfig(
- enable: true, // 启用低电量监控
- threshold: 20, // 电量阈值(%)
- title: '电池电量低', // 通知标题
- message: '您的电池电量低于20%', // 通知内容
- intervalMinutes: 15, // 检查间隔
- useFlutterRendering: true, // 使用Flutter UI
+ enable: true,
+ threshold: 20,
+ title: '电池电量低',
+ message: '您的电池电量低于20%',
+ intervalMinutes: 15,
+ useFlutterRendering: true,
),
-
- // 回调函数设置
onBatteryLevelChange: (batteryLevel) {
print('电池电量变化: $batteryLevel%');
},
-
onBatteryInfoChange: (info) {
print('电池信息更新: $info');
},
-
onLowBattery: (batteryLevel) {
// 处理低电量事件
- showDialog(
- context: context,
- builder: (context) => AlertDialog(
- title: Text('电池电量低'),
- content: Text('当前电量: $batteryLevel%'),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: Text('确定'),
- ),
- ],
- ),
- );
},
),
);
```
-### 配置特定功能
-
-如果需要单独配置特定功能,可以使用以下方法:
-
-#### 配置电池监听
-
-```dart
-await flutterBatteryPlugin.configureBatteryMonitor(
- BatteryMonitorConfig(
- monitorBatteryLevel: true,
- monitorBatteryInfo: true,
- monitorBatteryHealth: true,
- intervalMs: 2000,
- batteryInfoIntervalMs: 10000,
- batteryHealthIntervalMs: 15000,
- enableDebounce: true,
- ),
-);
-```
-
-#### 配置电池回调
+### 配置电池回调
```dart
flutterBatteryPlugin.configureBatteryCallbacks(
@@ -441,56 +346,28 @@ flutterBatteryPlugin.configureBatteryCallbacks(
);
```
-#### 配置低电量监控
-
-```dart
-await flutterBatteryPlugin.configureBatteryMonitoring(
- BatteryLevelMonitorConfig(
- enable: true,
- threshold: 15,
- title: '电量不足提醒',
- message: '电池电量低于15%,请及时充电',
- intervalMinutes: 30,
- useFlutterRendering: false,
- ),
-);
-```
-
-### 基本操作
-
-#### 获取电池电量
+### 获取电池电量
```dart
final batteryLevel = await flutterBatteryPlugin.getBatteryLevel();
print('当前电池电量: $batteryLevel%');
```
-#### 获取完整电池信息
+### 获取完整电池信息
```dart
final batteryInfo = await flutterBatteryPlugin.getBatteryInfo();
print('电池信息: $batteryInfo');
-// 输出: 电池信息: BatteryInfo(level: 85%, isCharging: true, temperature: 37.5°C, voltage: 4.35V, state: BatteryState.CHARGING)
```
-#### 获取电池健康
+### 获取电池健康
```dart
final batteryHealth = await flutterBatteryPlugin.getBatteryHealth();
print('电池健康: $batteryHealth');
-// BatteryHealth(state: BatteryHealthState.good, risk: LOW, temp: 32.0°C)
-```
-
-#### 获取电池优化建议
-
-```dart
-final tips = await flutterBatteryPlugin.getBatteryOptimizationTips();
-for (final tip in tips) {
- print('电池优化建议: $tip');
-}
```
-#### 发送通知
+### 发送通知
```dart
// 立即发送通知
@@ -500,20 +377,14 @@ await flutterBatteryPlugin.sendNotification(
delay: 0,
);
-// 延迟发送通知
+// 延迟发送通知(仅 Android)
await flutterBatteryPlugin.sendNotification(
title: '延迟通知',
message: '这条通知将在5分钟后显示',
- delay: 5, // 5分钟后发送
+ delay: 5,
);
```
-#### 停止电池监控
-
-```dart
-await flutterBatteryPlugin.stopBatteryMonitoring();
-```
-
### 使用电池动画组件
```dart
@@ -522,15 +393,19 @@ BatteryAnimation(
width: 150,
height: 300,
isCharging: true,
- showPercentage: true, // 显示百分比
- warningLevel: 20, // 设置警告电量阈值
+ showPercentage: true,
+ warningLevel: 20,
)
```
-## 版本兼容性
+## 架构决策(重构后)
-- **0.0.3 及以上版本**: 使用配置类和整合API (本文档中的所有示例)
-- **0.0.1-0.0.2 版本**: 仍支持老API,但建议升级到最新版本以获得更好的性能和简化的API
+- **通道常量集中化**:所有通道名称、方法名、事件类型和 payload key 集中在 `lib/src/battery_channel_contract.dart`,业务代码禁止使用原始字符串。
+- **平台能力查询**:通过 `BatteryFeature` 枚举 + `BatteryPlatformCapabilities` 值对象查询平台支持,macOS 显式返回 notifications/BLE/peer sync 为不支持。
+- **可预测的失败处理**:`MissingPluginException` 映射为 `UnsupportedBatteryFeatureException`,消费者无需捕获底层异常。
+- **事件规范化**:事件流 payload 始终包含 `type` 字段(`BATTERY_LEVEL`/`BATTERY_INFO`/`BATTERY_HEALTH`/`BATTERY_UNAVAILABLE`),`level` 和 `batteryLevel` 双键共存保证向后兼容。
+- **macOS 回调桥接**:`BatteryMonitor` 通过 callback setters 连接 `FlutterBatteryPlugin`,驱动 `onBatteryLevelChanged`/`onBatteryInfoChanged`/`onBatteryHealthChanged` 方法通道回调。
+- **IoT 示例隔离**:`iot/native` 和 `iot/stream` 仅限示例应用使用,不属插件公共 API。
## 常见问题
@@ -542,9 +417,9 @@ BatteryAnimation(
在 Android 13 及以上版本,需要动态请求通知权限。本插件会自动处理权限请求,但用户可能拒绝授予权限。
-### 3. 如何高效监控电池?
+### 3. macOS 上哪些功能不可用?
-推荐使用`configureBattery()`方法一次性配置所有需要的电池监控功能,减少多次API调用。
+macOS 不支持原生通知(nativeNotifications)、定时通知(scheduledNotifications)、蓝牙对等同步(blePeerSync)和 IoT 示例桥接(iotExampleBridge)。可通过 `getPlatformCapabilities()` 查询当前平台能力。
## 许可证
diff --git a/android/src/main/kotlin/com/example/flutter_battery/channel/MethodChannelHandler.kt b/android/src/main/kotlin/com/example/flutter_battery/channel/MethodChannelHandler.kt
index bf74110..69ab5b4 100644
--- a/android/src/main/kotlin/com/example/flutter_battery/channel/MethodChannelHandler.kt
+++ b/android/src/main/kotlin/com/example/flutter_battery/channel/MethodChannelHandler.kt
@@ -190,6 +190,9 @@ class MethodChannelHandler(
gattServerManager.stopSlaveMode()
result.success(null)
}
+ "getPlatformCapabilities" -> {
+ result.success(getPlatformCapabilities())
+ }
"getPlatformVersion" -> {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
}
@@ -414,6 +417,22 @@ class MethodChannelHandler(
return false
}
+ private fun getPlatformCapabilities(): Map {
+ return mapOf(
+ "batteryLevel" to true,
+ "batteryInfo" to true,
+ "batteryHealth" to true,
+ "batteryLevelStream" to true,
+ "batteryInfoStream" to true,
+ "batteryHealthStream" to true,
+ "lowBatteryMonitoring" to true,
+ "nativeNotifications" to true,
+ "scheduledNotifications" to true,
+ "blePeerSync" to true,
+ "iotExampleBridge" to true,
+ )
+ }
+
companion object {
private const val BLE_PERMISSION_REQUEST_CODE = 0xB10
}
diff --git a/example/.metadata b/example/.metadata
deleted file mode 100644
index c24b9a1..0000000
--- a/example/.metadata
+++ /dev/null
@@ -1,30 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694"
- channel: "stable"
-
-project_type: app
-
-# Tracks metadata for the flutter migrate command
-migration:
- platforms:
- - platform: root
- create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
- base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
- - platform: macos
- create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
- base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694
-
- # User provided section
-
- # List of Local paths (relative to this file) that should be
- # ignored by the migrate tool.
- #
- # Files that are not part of the templates will be ignored by default.
- unmanaged_files:
- - 'lib/main.dart'
- - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/example/AGENTS.md b/example/AGENTS.md
new file mode 100644
index 0000000..6031885
--- /dev/null
+++ b/example/AGENTS.md
@@ -0,0 +1,24 @@
+# Example App Guidelines
+
+## Purpose
+Demo application for manual QA and visual verification of the `flutter_battery` plugin. Showcases all battery monitoring features, BLE peer sync, and IoT native bridge demos.
+
+## Project Structure
+- `lib/main.dart`: App entry point; wires battery bootstrap, IoT event listeners, route generation gated by `BatteryPlatformCapabilities`.
+- `lib/pages/`: Feature demo pages (`dashboard_page.dart`, `battery_details_page.dart`, `low_battery_notification_page.dart`, `iot_controls_page.dart`, `event_stream_page.dart`).
+- `lib/platform/example_platform_adapter.dart`: Platform adapter returning `BatteryPlatformCapabilities` per platform. Android reports all features supported; other platforms report unsupported for BLE peer sync and IoT bridge.
+- `lib/routes.dart`: Route constants for all demo pages.
+- `test/widget_test.dart`: Widget test verifying dashboard disables features based on capability object.
+
+## Key Patterns
+- **Capability-gated routing**: Routes check `BatteryPlatformCapabilities.isSupported()` before navigating; unsupported features show `_UnsupportedFeaturePage` or disable the ListTile.
+- **IoT isolation**: `iot/native` and `iot/stream` channels are example-only, accessed exclusively through `ExamplePlatformAdapter`. Not part of the plugin's public API.
+- **Battery bootstrap**: `_bootstrapBattery()` configures all callbacks and monitoring via `configureBatteryCallbacks` + `configureBatteryMonitor`.
+
+## Testing
+- `cd example && flutter test`: Run widget tests.
+- `cd example && flutter run -d `: Manual QA on device/emulator.
+
+## Development Workflow
+- After modifying plugin Dart code, run `flutter pub get` in both root and example.
+- Use `BatteryFeature` enum values from `package:flutter_battery/flutter_battery.dart` (re-exported via `lib/src/platform_capabilities.dart`) for capability checks — never hardcode platform strings.
diff --git a/example/lib/main.dart b/example/lib/main.dart
index e17c46f..39b84df 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -10,6 +10,7 @@ import 'pages/event_stream_page.dart';
import 'pages/iot_controls_page.dart';
import 'pages/low_battery_notification_page.dart';
import 'perflab/perflab_channel.dart';
+import 'platform/example_platform_adapter.dart';
import 'role_selection_page.dart';
import 'routes.dart';
import 'startup_trace.dart';
@@ -23,30 +24,35 @@ void main() {
runApp(const FlutterBatteryExampleApp());
}
-// Entry point for the demo app: wires battery monitoring, IoT stubs, and sample pages.
class FlutterBatteryExampleApp extends StatefulWidget {
const FlutterBatteryExampleApp({super.key});
@override
- State createState() => _FlutterBatteryExampleAppState();
+ State createState() =>
+ _FlutterBatteryExampleAppState();
}
class _FlutterBatteryExampleAppState extends State {
+ final GlobalKey _navigatorKey = GlobalKey();
+ final ExamplePlatformAdapter _platform = ExamplePlatformAdapter.current();
final FlutterBattery _plugin = FlutterBattery();
final ValueNotifier _levelListenable = ValueNotifier(null);
- final ValueNotifier _infoListenable = ValueNotifier(null);
- final ValueNotifier _healthListenable = ValueNotifier(null);
- final ValueNotifier> _iotEventsListenable = ValueNotifier>([]);
+ final ValueNotifier _infoListenable =
+ ValueNotifier(null);
+ final ValueNotifier _healthListenable =
+ ValueNotifier(null);
+ final ValueNotifier> _iotEventsListenable =
+ ValueNotifier>([]);
int? _batteryLevel;
BatteryInfo? _batteryInfo;
BatteryHealth? _batteryHealth;
- static const MethodChannel _iotMethod = MethodChannel('iot/native');
- static const EventChannel _iotEvent = EventChannel('iot/stream');
StreamSubscription? _iotSub;
List _iotEvents = [];
+ BatteryPlatformCapabilities get _capabilities => _platform.capabilities;
+
@override
void initState() {
super.initState();
@@ -63,7 +69,6 @@ class _FlutterBatteryExampleAppState extends State {
super.dispose();
}
- // Configure the plugin callbacks and start native-side monitoring streams.
void _bootstrapBattery() {
_refresh();
_plugin.configureBatteryCallbacks(
@@ -89,9 +94,8 @@ class _FlutterBatteryExampleAppState extends State {
);
}
- // IoT section: demo EventChannel/MethodChannel usage unrelated to battery.
void _listenToIotEvents() {
- _iotSub = _iotEvent.receiveBroadcastStream().listen((dynamic e) {
+ _iotSub = _platform.iotEvents.listen((dynamic e) {
_recordIotEvent('event', e);
}, onError: (Object err) {
_recordIotEvent('error', err);
@@ -99,6 +103,7 @@ class _FlutterBatteryExampleAppState extends State {
}
void _recordIotEvent(String kind, Object? payload) {
+ if (!mounted) return;
final stamp = DateTime.now().toIso8601String().substring(11, 19);
final entry = '$stamp $kind: $payload';
setState(() {
@@ -126,16 +131,36 @@ class _FlutterBatteryExampleAppState extends State {
}
}
- Future _startScan() => _iotMethod.invokeMethod('scanDevices');
- Future _stopScan() => _iotMethod.invokeMethod('stopScan');
- Future _connect() => _iotMethod.invokeMethod('connect', {'deviceId': 'demo-001'});
- Future _disconnect() => _iotMethod.invokeMethod('disconnect');
- Future _startSync() => _iotMethod.invokeMethod('startSync');
- Future _stopSync() => _iotMethod.invokeMethod('stopSync');
+ Future _startScan() => _invokeIotMethod('scanDevices');
+ Future _stopScan() => _invokeIotMethod('stopScan');
+ Future _connect() =>
+ _invokeIotMethod('connect', {'deviceId': 'demo-001'});
+ Future _disconnect() => _invokeIotMethod('disconnect');
+ Future _startSync() => _invokeIotMethod('startSync');
+ Future _stopSync() => _invokeIotMethod('stopSync');
+
+ Future _invokeIotMethod(String method, [Object? arguments]) async {
+ if (!_capabilities.isSupported(BatteryFeature.iotExampleBridge)) {
+ _showUnsupportedFeatureMessage(BatteryFeature.iotExampleBridge);
+ return;
+ }
+ try {
+ await _platform.invokeIotMethod(method, arguments);
+ } on MissingPluginException catch (err) {
+ _recordIotEvent('error', err);
+ _showUnsupportedFeatureMessage(BatteryFeature.iotExampleBridge);
+ } on UnsupportedBatteryFeatureException catch (err) {
+ _recordIotEvent('error', err);
+ _showUnsupportedFeatureMessage(BatteryFeature.iotExampleBridge);
+ } on PlatformException catch (err) {
+ _recordIotEvent('error', err);
+ }
+ }
@override
Widget build(BuildContext context) {
return MaterialApp(
+ navigatorKey: _navigatorKey,
initialRoute: AppRoutes.dashboard,
onGenerateRoute: _onGenerateRoute,
);
@@ -161,24 +186,36 @@ class _FlutterBatteryExampleAppState extends State {
case AppRoutes.peerSelection:
return MaterialPageRoute(
settings: settings,
- builder: (_) => const RoleSelectionPage(),
+ builder: (_) => _capabilities.isSupported(BatteryFeature.blePeerSync)
+ ? const RoleSelectionPage()
+ : const _UnsupportedFeaturePage(
+ title: '蓝牙电量同步',
+ feature: BatteryFeature.blePeerSync,
+ ),
);
case AppRoutes.iotControls:
return MaterialPageRoute(
settings: settings,
- builder: (_) => IotControlsPage(
- startScan: _startScan,
- stopScan: _stopScan,
- connect: _connect,
- disconnect: _disconnect,
- startSync: _startSync,
- stopSync: _stopSync,
- ),
+ builder: (_) =>
+ _capabilities.isSupported(BatteryFeature.iotExampleBridge)
+ ? IotControlsPage(
+ startScan: _startScan,
+ stopScan: _stopScan,
+ connect: _connect,
+ disconnect: _disconnect,
+ startSync: _startSync,
+ stopSync: _stopSync,
+ )
+ : const _UnsupportedFeaturePage(
+ title: 'IoT native controls',
+ feature: BatteryFeature.iotExampleBridge,
+ ),
);
case AppRoutes.eventLog:
return MaterialPageRoute(
settings: settings,
- builder: (_) => EventStreamPage(eventsListenable: _iotEventsListenable),
+ builder: (_) =>
+ EventStreamPage(eventsListenable: _iotEventsListenable),
);
case AppRoutes.dashboard:
default:
@@ -192,6 +229,7 @@ class _FlutterBatteryExampleAppState extends State {
eventCount: _iotEvents.length,
onRefresh: _refresh,
onBootstrap: _bootstrapBattery,
+ capabilities: _capabilities,
onOpenBatteryDetails: () => _pushNamed(AppRoutes.batteryDetails),
onOpenLowBatteryAlerts: () => _pushNamed(AppRoutes.lowBattery),
onOpenPeerBatterySync: () => _pushNamed(AppRoutes.peerSelection),
@@ -203,6 +241,61 @@ class _FlutterBatteryExampleAppState extends State {
}
void _pushNamed(String route) {
- Navigator.of(context).pushNamed(route);
+ final navigator = _navigatorKey.currentState;
+ if (navigator == null) return;
+
+ final feature = _featureForRoute(route);
+ if (feature != null && !_capabilities.isSupported(feature)) {
+ _showUnsupportedFeatureMessage(feature);
+ return;
+ }
+
+ navigator.pushNamed(route);
+ }
+
+ BatteryFeature? _featureForRoute(String route) {
+ switch (route) {
+ case AppRoutes.peerSelection:
+ return BatteryFeature.blePeerSync;
+ case AppRoutes.iotControls:
+ return BatteryFeature.iotExampleBridge;
+ default:
+ return null;
+ }
+ }
+
+ void _showUnsupportedFeatureMessage(BatteryFeature feature) {
+ final scaffoldMessenger = _navigatorKey.currentContext == null
+ ? null
+ : ScaffoldMessenger.maybeOf(_navigatorKey.currentContext!);
+ scaffoldMessenger?.showSnackBar(
+ SnackBar(content: Text('$feature is not supported on this platform.')),
+ );
+ }
+}
+
+class _UnsupportedFeaturePage extends StatelessWidget {
+ const _UnsupportedFeaturePage({
+ required this.title,
+ required this.feature,
+ });
+
+ final String title;
+ final BatteryFeature feature;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: Text(title)),
+ body: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(24),
+ child: Text(
+ '$feature is not supported on this platform.',
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ );
}
}
diff --git a/example/lib/master_page.dart b/example/lib/master_page.dart
index c378b4f..10f2237 100644
--- a/example/lib/master_page.dart
+++ b/example/lib/master_page.dart
@@ -34,7 +34,8 @@ class _MasterPageState extends State {
if (!mounted) return;
setState(() => _state = state);
});
- _scanSub = _bluetooth.scanDevices(serviceUuid: _peerServiceUuid).listen((devices) {
+ _scanSub =
+ _bluetooth.scanDevices(serviceUuid: _peerServiceUuid).listen((devices) {
if (!mounted) return;
final merged = {for (final d in _devices) d.id: d};
for (final device in devices) {
@@ -85,7 +86,8 @@ class _MasterPageState extends State {
Card(
child: ListTile(
leading: const Icon(Icons.battery_std_outlined),
- title: Text('本机电量(Master):${localBattery >= 0 ? '$localBattery%' : '--'}'),
+ title: Text(
+ '本机电量(Master):${localBattery >= 0 ? '$localBattery%' : '--'}'),
subtitle: Text(
'对方电量(Slave):${remoteBattery != null ? '$remoteBattery%' : '--'}',
),
diff --git a/example/lib/pages/battery_details_page.dart b/example/lib/pages/battery_details_page.dart
index 0f8f068..3f1ecec 100644
--- a/example/lib/pages/battery_details_page.dart
+++ b/example/lib/pages/battery_details_page.dart
@@ -27,9 +27,11 @@ class BatteryDetailsPage extends StatelessWidget {
],
),
body: AnimatedBuilder(
- animation: Listenable.merge([levelListenable, infoListenable, healthListenable]),
+ animation: Listenable.merge(
+ [levelListenable, infoListenable, healthListenable]),
builder: (context, _) {
- final level = levelListenable.value ?? infoListenable.value?.level ?? 0;
+ final level =
+ levelListenable.value ?? infoListenable.value?.level ?? 0;
final info = infoListenable.value;
final health = healthListenable.value;
return ListView(
@@ -56,20 +58,25 @@ class BatteryDetailsPage extends StatelessWidget {
ListTile(
leading: const Icon(Icons.thermostat_auto_outlined),
title: const Text('Temperature'),
- subtitle: Text(info != null ? '${info.temperature.toStringAsFixed(1)}°C' : '--'),
+ subtitle: Text(info != null
+ ? '${info.temperature.toStringAsFixed(1)}°C'
+ : '--'),
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.speed_outlined),
title: const Text('Voltage'),
- subtitle: Text(info != null ? '${info.voltage.toStringAsFixed(2)}V' : '--'),
+ subtitle: Text(info != null
+ ? '${info.voltage.toStringAsFixed(2)}V'
+ : '--'),
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.electric_bike_outlined),
title: const Text('State'),
subtitle: Text(info?.state.name ?? 'unknown'),
- trailing: Text(info?.isCharging == true ? 'Charging' : 'Idle'),
+ trailing:
+ Text(info?.isCharging == true ? 'Charging' : 'Idle'),
),
],
),
@@ -96,7 +103,9 @@ class BatteryDetailsPage extends StatelessWidget {
),
const SizedBox(height: 6),
...health.recommendations
- .map((tip) => Text('• $tip', style: Theme.of(context).textTheme.bodySmall))
+ .map((tip) => Text('• $tip',
+ style:
+ Theme.of(context).textTheme.bodySmall))
.toList(),
],
),
diff --git a/example/lib/pages/dashboard_page.dart b/example/lib/pages/dashboard_page.dart
index 49ba40f..fa3e7d6 100644
--- a/example/lib/pages/dashboard_page.dart
+++ b/example/lib/pages/dashboard_page.dart
@@ -22,6 +22,7 @@ class DashboardPage extends StatefulWidget {
required this.eventCount,
required this.onRefresh,
required this.onBootstrap,
+ required this.capabilities,
required this.onOpenBatteryDetails,
required this.onOpenLowBatteryAlerts,
required this.onOpenPeerBatterySync,
@@ -36,6 +37,7 @@ class DashboardPage extends StatefulWidget {
final int eventCount;
final Future Function() onRefresh;
final VoidCallback onBootstrap;
+ final BatteryPlatformCapabilities capabilities;
final VoidCallback onOpenBatteryDetails;
final VoidCallback onOpenLowBatteryAlerts;
final VoidCallback onOpenPeerBatterySync;
@@ -136,7 +138,8 @@ class _DashboardPageState extends State {
),
_MetricChip(
icon: Icons.shield_outlined,
- label: widget.batteryHealth?.riskLevel ?? 'Health unknown',
+ label:
+ widget.batteryHealth?.riskLevel ?? 'Health unknown',
),
],
),
@@ -151,7 +154,8 @@ class _DashboardPageState extends State {
ListTile(
leading: const Icon(Icons.battery_std_outlined),
title: const Text('Battery details'),
- subtitle: const Text('Level, state, health, temperature, and manual refresh'),
+ subtitle: const Text(
+ 'Level, state, health, temperature, and manual refresh'),
trailing: const Icon(Icons.chevron_right),
onTap: widget.onOpenBatteryDetails,
),
@@ -167,23 +171,49 @@ class _DashboardPageState extends State {
ListTile(
leading: const Icon(Icons.hub_outlined),
title: const Text('蓝牙电量同步'),
- subtitle: const Text('选择主/从机后进行电量互通'),
- trailing: const Icon(Icons.chevron_right),
- onTap: widget.onOpenPeerBatterySync,
+ subtitle: Text(
+ widget.capabilities.isSupported(BatteryFeature.blePeerSync)
+ ? '选择主/从机后进行电量互通'
+ : '当前平台不支持',
+ ),
+ trailing: widget.capabilities
+ .isSupported(BatteryFeature.blePeerSync)
+ ? const Icon(Icons.chevron_right)
+ : const Icon(Icons.block_outlined),
+ enabled: widget.capabilities
+ .isSupported(BatteryFeature.blePeerSync),
+ onTap: widget.capabilities
+ .isSupported(BatteryFeature.blePeerSync)
+ ? widget.onOpenPeerBatterySync
+ : null,
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.memory_outlined),
title: const Text('IoT native controls'),
- subtitle: const Text('Scan, connect, and sync via MethodChannel'),
- trailing: const Icon(Icons.chevron_right),
- onTap: widget.onOpenIotControls,
+ subtitle: Text(
+ widget.capabilities
+ .isSupported(BatteryFeature.iotExampleBridge)
+ ? 'Scan, connect, and sync via MethodChannel'
+ : '当前平台不支持',
+ ),
+ trailing: widget.capabilities
+ .isSupported(BatteryFeature.iotExampleBridge)
+ ? const Icon(Icons.chevron_right)
+ : const Icon(Icons.block_outlined),
+ enabled: widget.capabilities
+ .isSupported(BatteryFeature.iotExampleBridge),
+ onTap: widget.capabilities
+ .isSupported(BatteryFeature.iotExampleBridge)
+ ? widget.onOpenIotControls
+ : null,
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.event_note_outlined),
title: const Text('Event stream log'),
- subtitle: Text('${widget.eventCount} recent entries from iot/stream'),
+ subtitle: Text(
+ '${widget.eventCount} recent entries from iot/stream'),
trailing: const Icon(Icons.chevron_right),
onTap: widget.onOpenEventLog,
),
@@ -303,7 +333,8 @@ class _BatteryGaugePainter extends CustomPainter {
return;
}
final center = Offset(size.width / 2, size.height / 2);
- final baseRadius = math.max(56.0, (math.min(size.width, size.height) / 2 - 12));
+ final baseRadius =
+ math.max(56.0, (math.min(size.width, size.height) / 2 - 12));
final middleRadius = math.max(40.0, baseRadius - 30);
final innerRadius = math.max(32.0, baseRadius - 60);
final levelRatio = _clamp01(level / 100);
@@ -384,7 +415,8 @@ class _BatteryGaugePainter extends CustomPainter {
final valuePainter = TextPainter(
text: TextSpan(
text: value,
- style: TextStyle(color: color, fontSize: 13, fontWeight: FontWeight.w600),
+ style:
+ TextStyle(color: color, fontSize: 13, fontWeight: FontWeight.w600),
),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
@@ -392,7 +424,8 @@ class _BatteryGaugePainter extends CustomPainter {
final offsetY = center.dy + radius - thickness / 2 - 10;
textPainter.paint(
canvas,
- Offset(center.dx - textPainter.width / 2, offsetY - textPainter.height - 2),
+ Offset(
+ center.dx - textPainter.width / 2, offsetY - textPainter.height - 2),
);
valuePainter.paint(
canvas,
@@ -439,21 +472,27 @@ class _BatteryGaugePainter extends CustomPainter {
final healthPainter = TextPainter(
text: TextSpan(
text: healthLabel,
- style: TextStyle(color: healthColor, fontSize: 13, fontWeight: FontWeight.w600),
+ style: TextStyle(
+ color: healthColor, fontSize: 13, fontWeight: FontWeight.w600),
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center,
)..layout(maxWidth: 240);
- final startY = center.dy - (levelPainter.height + statePainter.height + healthPainter.height + 8) / 2;
- levelPainter.paint(canvas, Offset(center.dx - levelPainter.width / 2, startY));
+ final startY = center.dy -
+ (levelPainter.height + statePainter.height + healthPainter.height + 8) /
+ 2;
+ levelPainter.paint(
+ canvas, Offset(center.dx - levelPainter.width / 2, startY));
statePainter.paint(
canvas,
- Offset(center.dx - statePainter.width / 2, startY + levelPainter.height + 4),
+ Offset(
+ center.dx - statePainter.width / 2, startY + levelPainter.height + 4),
);
healthPainter.paint(
canvas,
- Offset(center.dx - healthPainter.width / 2, startY + levelPainter.height + statePainter.height + 8),
+ Offset(center.dx - healthPainter.width / 2,
+ startY + levelPainter.height + statePainter.height + 8),
);
}
diff --git a/example/lib/pages/event_stream_page.dart b/example/lib/pages/event_stream_page.dart
index c6c5eb4..8288f34 100644
--- a/example/lib/pages/event_stream_page.dart
+++ b/example/lib/pages/event_stream_page.dart
@@ -15,7 +15,9 @@ class EventStreamPage extends StatelessWidget {
valueListenable: eventsListenable,
builder: (context, events, _) {
if (events.isEmpty) {
- return const Center(child: Text('No events yet. Trigger IoT actions to populate the stream.'));
+ return const Center(
+ child: Text(
+ 'No events yet. Trigger IoT actions to populate the stream.'));
}
return ListView.separated(
padding: const EdgeInsets.all(16),
diff --git a/example/lib/pages/iot_controls_page.dart b/example/lib/pages/iot_controls_page.dart
index fb28bf4..f366e7a 100644
--- a/example/lib/pages/iot_controls_page.dart
+++ b/example/lib/pages/iot_controls_page.dart
@@ -37,12 +37,30 @@ class IotControlsPage extends StatelessWidget {
spacing: 12,
runSpacing: 12,
children: [
- ElevatedButton.icon(onPressed: startScan, icon: const Icon(Icons.search), label: const Text('Scan')),
- ElevatedButton.icon(onPressed: stopScan, icon: const Icon(Icons.close), label: const Text('Stop Scan')),
- ElevatedButton.icon(onPressed: connect, icon: const Icon(Icons.usb), label: const Text('Connect')),
- ElevatedButton.icon(onPressed: disconnect, icon: const Icon(Icons.link_off), label: const Text('Disconnect')),
- ElevatedButton.icon(onPressed: startSync, icon: const Icon(Icons.cloud_upload_outlined), label: const Text('Start Sync')),
- ElevatedButton.icon(onPressed: stopSync, icon: const Icon(Icons.cloud_off_outlined), label: const Text('Stop Sync')),
+ ElevatedButton.icon(
+ onPressed: startScan,
+ icon: const Icon(Icons.search),
+ label: const Text('Scan')),
+ ElevatedButton.icon(
+ onPressed: stopScan,
+ icon: const Icon(Icons.close),
+ label: const Text('Stop Scan')),
+ ElevatedButton.icon(
+ onPressed: connect,
+ icon: const Icon(Icons.usb),
+ label: const Text('Connect')),
+ ElevatedButton.icon(
+ onPressed: disconnect,
+ icon: const Icon(Icons.link_off),
+ label: const Text('Disconnect')),
+ ElevatedButton.icon(
+ onPressed: startSync,
+ icon: const Icon(Icons.cloud_upload_outlined),
+ label: const Text('Start Sync')),
+ ElevatedButton.icon(
+ onPressed: stopSync,
+ icon: const Icon(Icons.cloud_off_outlined),
+ label: const Text('Stop Sync')),
],
),
const SizedBox(height: 24),
diff --git a/example/lib/pages/low_battery_notification_page.dart b/example/lib/pages/low_battery_notification_page.dart
index eebcd6a..9ac65dc 100644
--- a/example/lib/pages/low_battery_notification_page.dart
+++ b/example/lib/pages/low_battery_notification_page.dart
@@ -9,11 +9,14 @@ class LowBatteryNotificationPage extends StatefulWidget {
final FlutterBattery plugin;
@override
- State createState() => _LowBatteryNotificationPageState();
+ State createState() =>
+ _LowBatteryNotificationPageState();
}
-class _LowBatteryNotificationPageState extends State {
- final TextEditingController _titleController = TextEditingController(text: '电池电量低');
+class _LowBatteryNotificationPageState
+ extends State {
+ final TextEditingController _titleController =
+ TextEditingController(text: '电池电量低');
final TextEditingController _messageController =
TextEditingController(text: '当前电池电量已低于预设阈值,请注意充电');
@@ -50,13 +53,17 @@ class _LowBatteryNotificationPageState extends State
BatteryLevelMonitorConfig(
enable: enable,
threshold: _threshold.round(),
- title: _titleController.text.trim().isEmpty ? '电池电量低' : _titleController.text.trim(),
+ title: _titleController.text.trim().isEmpty
+ ? '电池电量低'
+ : _titleController.text.trim(),
message: _messageController.text.trim().isEmpty
? '当前电池电量已低于预设阈值,请注意充电'
: _messageController.text.trim(),
intervalMinutes: _intervalMinutes.round(),
useFlutterRendering: _useFlutterRendering,
- onLowBattery: _useFlutterRendering ? (int level) => _showSnack('电量低至 $level%') : null,
+ onLowBattery: _useFlutterRendering
+ ? (int level) => _showSnack('电量低至 $level%')
+ : null,
),
);
@@ -64,9 +71,7 @@ class _LowBatteryNotificationPageState extends State
setState(() {
_monitoringEnabled = enable && (success ?? false);
_status = success == true
- ? (enable
- ? '监控已开启,低于 ${_threshold.round()}% 将通过系统通知提示'
- : '监控已关闭')
+ ? (enable ? '监控已开启,低于 ${_threshold.round()}% 将通过系统通知提示' : '监控已关闭')
: '操作未生效,请检查日志';
});
} catch (err) {
@@ -97,7 +102,8 @@ class _LowBatteryNotificationPageState extends State
);
if (!mounted) return;
setState(() {
- _status = ok == true ? '通知已${delayMinutes == 0 ? '发送' : '调度'}' : '通知触发失败';
+ _status =
+ ok == true ? '通知已${delayMinutes == 0 ? '发送' : '调度'}' : '通知触发失败';
});
} catch (err) {
if (!mounted) return;
@@ -142,7 +148,10 @@ class _LowBatteryNotificationPageState extends State
value: _useFlutterRendering,
title: const Text('同时使用 Flutter 回调'),
subtitle: const Text('打开后低电量会先回调 Dart,关闭则直接走原生系统通知'),
- onChanged: _busy ? null : (value) => setState(() => _useFlutterRendering = value),
+ onChanged: _busy
+ ? null
+ : (value) =>
+ setState(() => _useFlutterRendering = value),
),
const SizedBox(height: 12),
_LabeledSlider(
@@ -152,7 +161,9 @@ class _LowBatteryNotificationPageState extends State
min: 5,
max: 50,
divisions: 9,
- onChanged: _busy ? null : (value) => setState(() => _threshold = value),
+ onChanged: _busy
+ ? null
+ : (value) => setState(() => _threshold = value),
),
const SizedBox(height: 8),
_LabeledSlider(
@@ -162,7 +173,9 @@ class _LowBatteryNotificationPageState extends State
min: 1,
max: 60,
divisions: 59,
- onChanged: _busy ? null : (value) => setState(() => _intervalMinutes = value),
+ onChanged: _busy
+ ? null
+ : (value) => setState(() => _intervalMinutes = value),
),
const SizedBox(height: 12),
TextField(
@@ -198,7 +211,9 @@ class _LowBatteryNotificationPageState extends State
label: const Text('开启监控'),
),
OutlinedButton.icon(
- onPressed: (_busy || !_monitoringEnabled) ? null : () => _toggleMonitoring(false),
+ onPressed: (_busy || !_monitoringEnabled)
+ ? null
+ : () => _toggleMonitoring(false),
icon: const Icon(Icons.stop),
label: const Text('停止监控'),
),
@@ -208,7 +223,8 @@ class _LowBatteryNotificationPageState extends State
label: const Text('立即测试通知'),
),
OutlinedButton.icon(
- onPressed: _busy ? null : () => _sendTestNotification(delayMinutes: 1),
+ onPressed:
+ _busy ? null : () => _sendTestNotification(delayMinutes: 1),
icon: const Icon(Icons.schedule),
label: const Text('1 分钟后提醒'),
),
diff --git a/example/lib/perflab/perflab_channel.dart b/example/lib/perflab/perflab_channel.dart
index 49e7e1f..6c6c8c7 100644
--- a/example/lib/perflab/perflab_channel.dart
+++ b/example/lib/perflab/perflab_channel.dart
@@ -33,7 +33,11 @@ class PerfLabChannel {
static Future