diff --git a/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt b/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt index c52c87c..ba416b5 100644 --- a/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt +++ b/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.SharedPreferences import android.net.Uri import android.os.BatteryManager import android.os.Build @@ -20,18 +21,17 @@ import java.io.BufferedReader import java.io.InputStreamReader class MainActivity : FlutterActivity() { - // ---------- 充电状态 ---------- private val CHARGE_CHANNEL = "com.lxh.battery_health/charge" private val EVENT_CHANNEL = "com.lxh.battery_health/charge_events" private var eventSink: EventChannel.EventSink? = null - // ---------- SAF 相关 ---------- private val SAF_CHANNEL = "com.lxh.battery_health/saf" private var safResult: MethodChannel.Result? = null private var safUri: Uri? = null private val FOLDER_PICKER_REQUEST = 1001 + private val PREF_NAME = "battery_health_prefs" + private val KEY_SAF_URI = "saf_uri" - // ---------- 充电广播接收器 ---------- private val chargeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent ?: return @@ -42,6 +42,32 @@ class MainActivity : FlutterActivity() { } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // 🟢 从 SharedPreferences 恢复 URI + val prefs: SharedPreferences = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + val uriString = prefs.getString(KEY_SAF_URI, null) + if (uriString != null) { + try { + safUri = Uri.parse(uriString) + android.util.Log.d("BatteryHealth", "恢复 SAF URI: $safUri") + } catch (e: Exception) { + android.util.Log.e("BatteryHealth", "恢复 URI 失败: ${e.message}") + } + } + } + + private fun saveUri(uri: Uri) { + try { + val prefs: SharedPreferences = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + prefs.edit().putString(KEY_SAF_URI, uri.toString()).apply() + android.util.Log.d("BatteryHealth", "保存 SAF URI: $uri") + } catch (e: Exception) { + android.util.Log.e("BatteryHealth", "保存 URI 失败: ${e.message}") + } + } + private fun sendChargeState(isCharging: Boolean) { val chargeType = if (isCharging) { val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) @@ -66,7 +92,6 @@ class MainActivity : FlutterActivity() { status == BatteryManager.BATTERY_STATUS_FULL } - // ---------- SAF 方法 ---------- private fun openFolderPicker() { try { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { @@ -100,6 +125,8 @@ class MainActivity : FlutterActivity() { val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.takePersistableUriPermission(uri, takeFlags) safUri = uri + // 🟢 保存到 SharedPreferences + saveUri(uri) android.util.Log.d("BatteryHealth", "SAF URI: $uri") safResult?.success(uri.toString()) } catch (e: Exception) { @@ -114,27 +141,36 @@ class MainActivity : FlutterActivity() { } private fun getTreeDocId(): String? { + android.util.Log.d("BatteryHealth", "getTreeDocId: safUri = $safUri") return safUri?.let { DocumentsContract.getTreeDocumentId(it) } } private fun listFiles(): List { + android.util.Log.d("BatteryHealth", "listFiles 被调用") val result = mutableListOf() val docId = getTreeDocId() - if (docId == null || docId.isEmpty()) return result + android.util.Log.d("BatteryHealth", "docId = $docId") + if (docId == null || docId.isEmpty()) { + android.util.Log.e("BatteryHealth", "docId 为空") + return result + } try { val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId) + android.util.Log.d("BatteryHealth", "childrenUri = $childrenUri") contentResolver.query(childrenUri, null, null, null, null)?.use { cursor -> val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) while (cursor.moveToNext()) { val name = cursor.getString(nameIndex) if (name != null) { + android.util.Log.d("BatteryHealth", "找到文件: $name") result.add(name) } } } + android.util.Log.d("BatteryHealth", "共找到 ${result.size} 个文件") } catch (e: Exception) { - android.util.Log.e("BatteryHealth", "列出文件失败: ${e.message}") + android.util.Log.e("BatteryHealth", "列出文件失败: ${e.message}", e) } return result } @@ -178,11 +214,9 @@ class MainActivity : FlutterActivity() { return fileUri?.let { readFile(it) } } - // ---------- Flutter 通道配置 ---------- override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - // 充电状态通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL) .setMethodCallHandler { call, result -> if (call.method == "getChargeState") { @@ -205,7 +239,6 @@ class MainActivity : FlutterActivity() { } } - // SAF 通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { @@ -233,7 +266,6 @@ class MainActivity : FlutterActivity() { } } - // 充电状态事件通道 EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL) .setStreamHandler( object : EventChannel.StreamHandler { diff --git a/lib/pages/battery_health_page.dart b/lib/pages/battery_health_page.dart index 3c000c2..0a5b51b 100644 --- a/lib/pages/battery_health_page.dart +++ b/lib/pages/battery_health_page.dart @@ -1,5 +1,5 @@ -import 'dart:io'; import 'dart:async'; +import 'dart:io'; import 'dart:developer' as dev; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -8,6 +8,7 @@ import 'package:sqflite/sqflite.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:device_info_plus/device_info_plus.dart'; +//import 'package:shared_preferences/shared_preferences.dart'; import '../models/device_matching.dart'; import '../utils/charge_utils.dart'; @@ -84,8 +85,10 @@ class _BatteryHealthPageState extends State } Future _initDatabase() async { + dev.log('🔵 _initDatabase 开始', name: 'BatteryHealth'); Directory appDocDir = await getApplicationDocumentsDirectory(); String dbPath = path.join(appDocDir.path, 'battery_health.db'); + dev.log('🔵 数据库路径: $dbPath', name: 'BatteryHealth'); return await openDatabase( dbPath, version: 2, @@ -136,15 +139,18 @@ class _BatteryHealthPageState extends State // ---------- 设备信息 ---------- Future _getDeviceInfo() async { + dev.log('🔵 _getDeviceInfo 开始', name: 'BatteryHealth'); DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); try { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; String model = androidInfo.model; + dev.log('🔵 设备型号: $model', name: 'BatteryHealth'); setState(() { _deviceModel = DeviceMatching.getModelName(model); _designCap = DeviceMatching.getDesignCapacity(model); }); } catch (e) { + dev.log('🔵 获取设备信息失败: $e', name: 'BatteryHealth'); setState(() { _deviceModel = '魅族21 Pro (默认)'; _designCap = 5050; @@ -152,23 +158,25 @@ class _BatteryHealthPageState extends State } } - // ---------- 扫描目录 (SAF 模式) ---------- + // ---------- 扫描目录 (SAF) ---------- Future _scanAndUpdateDB() async { - // 检查 SAF 授权状态 + dev.log('🔵 _scanAndUpdateDB 开始', name: 'BatteryHealth'); _safState = await _safService.getPermissionState(); if (_safState != SafPermissionState.authorized) { - _showSafAuthorizationDialog(); + dev.log('🔵 _scanAndUpdateDB: 未授权', name: 'BatteryHealth'); return; } try { final files = await _safService.listFiles(); + dev.log('🔵 扫描文件列表: $files', name: 'BatteryHealth'); RegExp filePattern = RegExp(r'mbattery_charger_log_(\d{4}_\d{2}_\d{2})$'); for (String fileName in files) { Match? match = filePattern.firstMatch(fileName); if (match != null) { String dateStr = match.group(1)!.replaceAll('_', '-'); + dev.log('🔵 处理文件: $fileName, 日期: $dateStr', name: 'BatteryHealth'); try { List lines = await _safService.readLogFile(fileName); if (lines.isEmpty) continue; @@ -197,11 +205,14 @@ class _BatteryHealthPageState extends State if (maxVolt == null || minVolt == null) continue; await _insertOrUpdate(dateStr, cap, maxVolt, minVolt); - } catch (_) {} + dev.log('🔵 插入数据库: $dateStr, cap=$cap', name: 'BatteryHealth'); + } catch (e) { + dev.log('🔵 处理文件 $fileName 异常: $e', name: 'BatteryHealth'); + } } } } catch (e) { - dev.log('[_scanAndUpdateDB] ❌ 扫描异常: $e', name: 'BatteryHealth'); + dev.log('🔵 _scanAndUpdateDB 异常: $e', name: 'BatteryHealth'); } } @@ -211,7 +222,9 @@ class _BatteryHealthPageState extends State String year = nowUtc.year.toString(); String month = nowUtc.month.toString().padLeft(2, '0'); String day = nowUtc.day.toString().padLeft(2, '0'); - return 'mbattery_charger_log_${year}_${month}_$day'; + String fileName = 'mbattery_charger_log_${year}_${month}_$day'; + dev.log('🔵 getTodayFileName: $fileName', name: 'BatteryHealth'); + return fileName; } // ---------- 时间戳工具方法 ---------- @@ -250,9 +263,10 @@ class _BatteryHealthPageState extends State // ---------- SAF 授权引导 ---------- void _showSafAuthorizationDialog() { + dev.log('🔵 _showSafAuthorizationDialog 被调用', name: 'BatteryHealth'); if (!mounted) return; - showDialog( + showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( @@ -274,7 +288,8 @@ class _BatteryHealthPageState extends State actions: [ TextButton( onPressed: () { - Navigator.pop(context); + dev.log('🔵 用户取消授权', name: 'BatteryHealth'); + Navigator.pop(context, 'cancel'); setState(() { _safState = SafPermissionState.denied; }); @@ -282,48 +297,44 @@ class _BatteryHealthPageState extends State child: const Text('取消'), ), ElevatedButton( - onPressed: () async { - // 保存 context 引用 - final ctx = context; - - // 尝试关闭对话框 - Navigator.of(ctx, rootNavigator: true).pop(); - - // 等待动画完成 - await Future.delayed(const Duration(milliseconds: 200)); - - final result = await _safService.requestPermission(); - - if (!mounted) return; - - if (result.success) { - setState(() { - _safState = SafPermissionState.authorized; - }); - - // 🟢 兜底:在下一帧再次尝试关闭 - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - Navigator.of(ctx, rootNavigator: true).pop(); - } - }); - - readBatteryCapacity(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(result.errorMessage ?? '授权失败,请重试'), - backgroundColor: Colors.red, - ), - ); - _showSafAuthorizationDialog(); - } + onPressed: () { + dev.log('🔵 用户点击选择文件夹', name: 'BatteryHealth'); + Navigator.pop(context, 'select'); }, child: const Text('选择文件夹'), ), ], ), - ); + ).then((result) async { + dev.log('🔵 对话框关闭,result: $result', name: 'BatteryHealth'); + if (result == 'select') { + dev.log('🔵 开始请求授权', name: 'BatteryHealth'); + final authResult = await _safService.requestPermission(); + dev.log( + '🔵 授权结果: success=${authResult.success}, state=${authResult.state}', + name: 'BatteryHealth', + ); + + if (!mounted) return; + + if (authResult.success) { + dev.log('🔵 授权成功,调用 readBatteryCapacity', name: 'BatteryHealth'); + setState(() { + _safState = SafPermissionState.authorized; + }); + readBatteryCapacity(); + } else { + dev.log('🔵 授权失败: ${authResult.errorMessage}', name: 'BatteryHealth'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(authResult.errorMessage ?? '授权失败,请重试'), + backgroundColor: Colors.red, + ), + ); + _showSafAuthorizationDialog(); + } + } + }); } // ---------- 核心:系统API定调 ---------- @@ -531,10 +542,9 @@ class _BatteryHealthPageState extends State } } - // 检查 SAF 授权 _safState = await _safService.getPermissionState(); if (_safState != SafPermissionState.authorized) { - if (mounted) _showSafAuthorizationDialog(); + dev.log('[_fetchChargeDataFromLog] 未授权,跳过', name: 'BatteryHealth'); return; } @@ -620,9 +630,17 @@ class _BatteryHealthPageState extends State // ---------- 读取当前数据 ---------- Future readBatteryCapacity() async { + dev.log( + '🔵 ========== readBatteryCapacity 开始 ==========', + name: 'BatteryHealth', + ); + // 检查 SAF 授权 _safState = await _safService.getPermissionState(); + dev.log('🔵 SAF状态: $_safState', name: 'BatteryHealth'); + if (_safState != SafPermissionState.authorized) { + dev.log('🔵 未授权,弹窗', name: 'BatteryHealth'); if (mounted) _showSafAuthorizationDialog(); return; } @@ -631,11 +649,16 @@ class _BatteryHealthPageState extends State await _scanAndUpdateDB(); final fileName = getTodayFileName(); + dev.log('🔵 今日文件名: $fileName', name: 'BatteryHealth'); + final exists = await _safService.logFileExists(fileName); + dev.log('🔵 文件存在: $exists', name: 'BatteryHealth'); if (!exists) { + dev.log('🔵 文件不存在,尝试从数据库读取', name: 'BatteryHealth'); List> recent = await _queryRecentData(1); if (recent.isNotEmpty) { + dev.log('🔵 数据库有数据: ${recent.first}', name: 'BatteryHealth'); setState(() { _currentCap = recent.first['capacity'].toDouble(); _maxVolt = recent.first['volt_max'] ?? 0; @@ -644,6 +667,7 @@ class _BatteryHealthPageState extends State }); await _updateTrendChart(); } else { + dev.log('🔵 数据库无数据', name: 'BatteryHealth'); setState(() => _hasData = false); } if (_isCharging) _fetchChargeDataFromLog(); @@ -654,7 +678,9 @@ class _BatteryHealthPageState extends State try { final lines = await _safService.readLogFile(fileName); + dev.log('🔵 读取到 ${lines.length} 行', name: 'BatteryHealth'); if (lines.isEmpty) { + dev.log('🔵 文件为空', name: 'BatteryHealth'); setState(() => _hasData = false); if (_isCharging) _fetchChargeDataFromLog(); _chargeAnimationController.reset(); @@ -662,12 +688,23 @@ class _BatteryHealthPageState extends State return; } + // 打印前几行 + if (lines.isNotEmpty) { + dev.log('🔵 第一行: ${lines.first}', name: 'BatteryHealth'); + if (lines.length > 1) { + dev.log('🔵 第二行: ${lines[1]}', name: 'BatteryHealth'); + } + } + if (_isCharging) _parseChargeDataFromLog(lines); List validLines = lines .where((line) => line.contains('tbat=')) .toList(); + dev.log('🔵 有效行数 (含tbat): ${validLines.length}', name: 'BatteryHealth'); + if (validLines.isEmpty) { + dev.log('🔵 无有效数据行', name: 'BatteryHealth'); setState(() => _hasData = false); _chargeAnimationController.reset(); _updateChargeState(); @@ -675,6 +712,8 @@ class _BatteryHealthPageState extends State } String lastLine = validLines.last; + dev.log('🔵 最后一行: $lastLine', name: 'BatteryHealth'); + RegExp capReg = RegExp(r'learned_cap=(\d+)'); RegExp cycleReg = RegExp(r'cycle=(\d+)'); RegExp tempReg = RegExp(r'tbat=(\d+)'); @@ -685,7 +724,13 @@ class _BatteryHealthPageState extends State String? tempStr = tempReg.firstMatch(lastLine)?.group(1); String? voltStr = voltageReg.firstMatch(lastLine)?.group(1); + dev.log( + '🔵 capStr=$capStr, cycleStr=$cycleStr, tempStr=$tempStr, voltStr=$voltStr', + name: 'BatteryHealth', + ); + if (capStr == null) { + dev.log('🔵 未找到 learned_cap', name: 'BatteryHealth'); setState(() => _hasData = false); _chargeAnimationController.reset(); _updateChargeState(); @@ -697,6 +742,11 @@ class _BatteryHealthPageState extends State double temp = double.parse(tempStr ?? '0'); double volt = double.parse(voltStr ?? '0') / 1000000; + dev.log( + '🔵 解析结果: capMah=$capMah, cycle=$cycle, temp=$temp, volt=$volt', + name: 'BatteryHealth', + ); + List temps = []; for (String line in validLines) { RegExpMatch? match = tempReg.firstMatch(line); @@ -731,10 +781,20 @@ class _BatteryHealthPageState extends State _hasData = true; }); + dev.log( + '🔵 数据更新完成: _currentCap=$_currentCap, _cycleCount=$_cycleCount', + name: 'BatteryHealth', + ); + await _updateTrendChart(); _chargeAnimationController.reset(); _updateChargeState(); + dev.log( + '🔵 ========== readBatteryCapacity 完成 ==========', + name: 'BatteryHealth', + ); } catch (e) { + dev.log('🔵 readBatteryCapacity 异常: $e', name: 'BatteryHealth'); setState(() => _hasData = false); _chargeAnimationController.reset(); _updateChargeState(); @@ -752,6 +812,7 @@ class _BatteryHealthPageState extends State @override void initState() { super.initState(); + dev.log('🔵 ========== initState 开始 ==========', name: 'BatteryHealth'); _chargeAnimationController = AnimationController( vsync: this, @@ -786,18 +847,29 @@ class _BatteryHealthPageState extends State } }); - // 延迟初始化 SAF 并读取数据 WidgetsBinding.instance.addPostFrameCallback((_) async { + dev.log('🔵 initState -> addPostFrameCallback 开始', name: 'BatteryHealth'); + await _safService.init(); + dev.log( + '🔵 _safService.init() 完成,cachedUri: ${_safService.cachedUri}', + name: 'BatteryHealth', + ); + _safState = await _safService.getPermissionState(); + dev.log('🔵 _safState: $_safState', name: 'BatteryHealth'); if (_safState == SafPermissionState.unauthorized || _safState == SafPermissionState.expired) { + dev.log('🔵 需要授权,弹窗', name: 'BatteryHealth'); if (mounted) _showSafAuthorizationDialog(); } else { + dev.log('🔵 已有授权,调用 readBatteryCapacity', name: 'BatteryHealth'); readBatteryCapacity(); } }); + + dev.log('🔵 ========== initState 结束 ==========', name: 'BatteryHealth'); } @override diff --git a/lib/services/saf_storage_service.dart b/lib/services/saf_storage_service.dart index 50f7f76..96e7d4d 100644 --- a/lib/services/saf_storage_service.dart +++ b/lib/services/saf_storage_service.dart @@ -34,7 +34,7 @@ class SafStorageService { if (_cachedUri == null || _cachedUri!.isEmpty) { return SafPermissionState.unauthorized; } - // 🟢 简化:直接返回 authorized,相信本地缓存的 URI + // 🟢 直接返回 authorized,相信缓存 return SafPermissionState.authorized; }