Compare commits
5 Commits
45c69c8ec4
...
1.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b34b6c8b6 | |||
| f8ffb32bb0 | |||
| 8e93cbeed2 | |||
| 7ba2e612f1 | |||
| c7ee563e84 |
@@ -1,20 +1,22 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!--<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />-->
|
<!--<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />-->
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@drawable/logo"
|
android:icon="@drawable/logo"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true"
|
||||||
<activity
|
android:theme="@style/LaunchTheme"> <!-- 全局先用 LaunchTheme -->
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true"
|
<activity
|
||||||
android:launchMode="singleTop"
|
android:name=".MainActivity"
|
||||||
android:taskAffinity=""
|
android:exported="true"
|
||||||
android:theme="@style/LaunchTheme"
|
android:launchMode="singleTop"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:taskAffinity=""
|
||||||
android:hardwareAccelerated="true"
|
android:theme="@style/AppTheme"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.BatteryManager
|
import android.os.BatteryManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -20,18 +21,17 @@ import java.io.BufferedReader
|
|||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
// ---------- 充电状态 ----------
|
|
||||||
private val CHARGE_CHANNEL = "com.lxh.battery_health/charge"
|
private val CHARGE_CHANNEL = "com.lxh.battery_health/charge"
|
||||||
private val EVENT_CHANNEL = "com.lxh.battery_health/charge_events"
|
private val EVENT_CHANNEL = "com.lxh.battery_health/charge_events"
|
||||||
private var eventSink: EventChannel.EventSink? = null
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
// ---------- SAF 相关 ----------
|
|
||||||
private val SAF_CHANNEL = "com.lxh.battery_health/saf"
|
private val SAF_CHANNEL = "com.lxh.battery_health/saf"
|
||||||
private var safResult: MethodChannel.Result? = null
|
private var safResult: MethodChannel.Result? = null
|
||||||
private var safUri: Uri? = null
|
private var safUri: Uri? = null
|
||||||
private val FOLDER_PICKER_REQUEST = 1001
|
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() {
|
private val chargeReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
intent ?: return
|
intent ?: return
|
||||||
@@ -42,6 +42,46 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
android.util.Log.d("BatteryHealth", "onCreate 开始")
|
||||||
|
|
||||||
|
try {
|
||||||
|
val prefs: SharedPreferences = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||||
|
val uriString = prefs.getString(KEY_SAF_URI, null)
|
||||||
|
android.util.Log.d("BatteryHealth", "SharedPreferences URI: $uriString")
|
||||||
|
if (uriString != null) {
|
||||||
|
safUri = Uri.parse(uriString)
|
||||||
|
android.util.Log.d("BatteryHealth", "恢复 SAF URI 成功: $safUri")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("BatteryHealth", "恢复 URI 异常: ${e.message}")
|
||||||
|
}
|
||||||
|
android.util.Log.d("BatteryHealth", "onCreate 结束,safUri = $safUri")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 restoreUri(uriString: String) {
|
||||||
|
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) {
|
||||||
|
android.util.Log.e("BatteryHealth", "restoreUri 失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendChargeState(isCharging: Boolean) {
|
private fun sendChargeState(isCharging: Boolean) {
|
||||||
val chargeType = if (isCharging) {
|
val chargeType = if (isCharging) {
|
||||||
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||||
@@ -66,7 +106,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
status == BatteryManager.BATTERY_STATUS_FULL
|
status == BatteryManager.BATTERY_STATUS_FULL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- SAF 方法 ----------
|
|
||||||
private fun openFolderPicker() {
|
private fun openFolderPicker() {
|
||||||
try {
|
try {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||||
@@ -100,7 +139,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
safUri = uri
|
safUri = uri
|
||||||
android.util.Log.d("BatteryHealth", "SAF URI: $uri")
|
saveUri(uri)
|
||||||
|
android.util.Log.d("BatteryHealth", "SAF 授权成功 URI: $uri")
|
||||||
safResult?.success(uri.toString())
|
safResult?.success(uri.toString())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("BatteryHealth", "SAF 授权失败: ${e.message}")
|
android.util.Log.e("BatteryHealth", "SAF 授权失败: ${e.message}")
|
||||||
@@ -118,9 +158,13 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun listFiles(): List<String> {
|
private fun listFiles(): List<String> {
|
||||||
|
android.util.Log.d("BatteryHealth", "listFiles 被调用,safUri = $safUri")
|
||||||
val result = mutableListOf<String>()
|
val result = mutableListOf<String>()
|
||||||
val docId = getTreeDocId()
|
val docId = getTreeDocId()
|
||||||
if (docId == null || docId.isEmpty()) return result
|
if (docId == null || docId.isEmpty()) {
|
||||||
|
android.util.Log.e("BatteryHealth", "docId 为空")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId)
|
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId)
|
||||||
@@ -134,7 +178,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("BatteryHealth", "列出文件失败: ${e.message}")
|
android.util.Log.e("BatteryHealth", "列出文件失败: ${e.message}", e)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -178,11 +222,9 @@ class MainActivity : FlutterActivity() {
|
|||||||
return fileUri?.let { readFile(it) }
|
return fileUri?.let { readFile(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Flutter 通道配置 ----------
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
// 充电状态通道
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL)
|
||||||
.setMethodCallHandler { call, result ->
|
.setMethodCallHandler { call, result ->
|
||||||
if (call.method == "getChargeState") {
|
if (call.method == "getChargeState") {
|
||||||
@@ -205,10 +247,18 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAF 通道
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
|
||||||
.setMethodCallHandler { call, result ->
|
.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
|
"restoreUri" -> {
|
||||||
|
val uriString = call.argument<String>("uri")
|
||||||
|
if (uriString != null) {
|
||||||
|
restoreUri(uriString)
|
||||||
|
result.success(true)
|
||||||
|
} else {
|
||||||
|
result.error("INVALID_ARGUMENT", "uri is required", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
"selectFolder" -> {
|
"selectFolder" -> {
|
||||||
safResult = result
|
safResult = result
|
||||||
openFolderPicker()
|
openFolderPicker()
|
||||||
@@ -233,7 +283,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 充电状态事件通道
|
|
||||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
|
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
|
||||||
.setStreamHandler(
|
.setStreamHandler(
|
||||||
object : EventChannel.StreamHandler {
|
object : EventChannel.StreamHandler {
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<!-- 状态栏背景白色 -->
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<item name="android:statusBarColor">@android:color/white</item>
|
||||||
the Flutter engine draws its first frame -->
|
<!-- 状态栏图标亮色(深色图标) -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
</resources>
|
||||||
This theme determines the color of the Android Window while your
|
|
||||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
|
||||||
running.
|
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:developer' as dev;
|
import 'dart:developer' as dev;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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_provider/path_provider.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
//import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../models/device_matching.dart';
|
import '../models/device_matching.dart';
|
||||||
import '../utils/charge_utils.dart';
|
import '../utils/charge_utils.dart';
|
||||||
@@ -84,8 +85,10 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Database> _initDatabase() async {
|
Future<Database> _initDatabase() async {
|
||||||
|
dev.log('🔵 _initDatabase 开始', name: 'BatteryHealth');
|
||||||
Directory appDocDir = await getApplicationDocumentsDirectory();
|
Directory appDocDir = await getApplicationDocumentsDirectory();
|
||||||
String dbPath = path.join(appDocDir.path, 'battery_health.db');
|
String dbPath = path.join(appDocDir.path, 'battery_health.db');
|
||||||
|
dev.log('🔵 数据库路径: $dbPath', name: 'BatteryHealth');
|
||||||
return await openDatabase(
|
return await openDatabase(
|
||||||
dbPath,
|
dbPath,
|
||||||
version: 2,
|
version: 2,
|
||||||
@@ -136,15 +139,18 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
|
|
||||||
// ---------- 设备信息 ----------
|
// ---------- 设备信息 ----------
|
||||||
Future<void> _getDeviceInfo() async {
|
Future<void> _getDeviceInfo() async {
|
||||||
|
dev.log('🔵 _getDeviceInfo 开始', name: 'BatteryHealth');
|
||||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||||
try {
|
try {
|
||||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||||
String model = androidInfo.model;
|
String model = androidInfo.model;
|
||||||
|
dev.log('🔵 设备型号: $model', name: 'BatteryHealth');
|
||||||
setState(() {
|
setState(() {
|
||||||
_deviceModel = DeviceMatching.getModelName(model);
|
_deviceModel = DeviceMatching.getModelName(model);
|
||||||
_designCap = DeviceMatching.getDesignCapacity(model);
|
_designCap = DeviceMatching.getDesignCapacity(model);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
dev.log('🔵 获取设备信息失败: $e', name: 'BatteryHealth');
|
||||||
setState(() {
|
setState(() {
|
||||||
_deviceModel = '魅族21 Pro (默认)';
|
_deviceModel = '魅族21 Pro (默认)';
|
||||||
_designCap = 5050;
|
_designCap = 5050;
|
||||||
@@ -152,23 +158,25 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 扫描目录 (SAF 模式) ----------
|
// ---------- 扫描目录 (SAF) ----------
|
||||||
Future<void> _scanAndUpdateDB() async {
|
Future<void> _scanAndUpdateDB() async {
|
||||||
// 检查 SAF 授权状态
|
dev.log('🔵 _scanAndUpdateDB 开始', name: 'BatteryHealth');
|
||||||
_safState = await _safService.getPermissionState();
|
_safState = await _safService.getPermissionState();
|
||||||
if (_safState != SafPermissionState.authorized) {
|
if (_safState != SafPermissionState.authorized) {
|
||||||
_showSafAuthorizationDialog();
|
dev.log('🔵 _scanAndUpdateDB: 未授权', name: 'BatteryHealth');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final files = await _safService.listFiles();
|
final files = await _safService.listFiles();
|
||||||
|
dev.log('🔵 扫描文件列表: $files', name: 'BatteryHealth');
|
||||||
RegExp filePattern = RegExp(r'mbattery_charger_log_(\d{4}_\d{2}_\d{2})$');
|
RegExp filePattern = RegExp(r'mbattery_charger_log_(\d{4}_\d{2}_\d{2})$');
|
||||||
|
|
||||||
for (String fileName in files) {
|
for (String fileName in files) {
|
||||||
Match? match = filePattern.firstMatch(fileName);
|
Match? match = filePattern.firstMatch(fileName);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
String dateStr = match.group(1)!.replaceAll('_', '-');
|
String dateStr = match.group(1)!.replaceAll('_', '-');
|
||||||
|
dev.log('🔵 处理文件: $fileName, 日期: $dateStr', name: 'BatteryHealth');
|
||||||
try {
|
try {
|
||||||
List<String> lines = await _safService.readLogFile(fileName);
|
List<String> lines = await _safService.readLogFile(fileName);
|
||||||
if (lines.isEmpty) continue;
|
if (lines.isEmpty) continue;
|
||||||
@@ -197,11 +205,14 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
if (maxVolt == null || minVolt == null) continue;
|
if (maxVolt == null || minVolt == null) continue;
|
||||||
|
|
||||||
await _insertOrUpdate(dateStr, cap, maxVolt, minVolt);
|
await _insertOrUpdate(dateStr, cap, maxVolt, minVolt);
|
||||||
} catch (_) {}
|
dev.log('🔵 插入数据库: $dateStr, cap=$cap', name: 'BatteryHealth');
|
||||||
|
} catch (e) {
|
||||||
|
dev.log('🔵 处理文件 $fileName 异常: $e', name: 'BatteryHealth');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dev.log('[_scanAndUpdateDB] ❌ 扫描异常: $e', name: 'BatteryHealth');
|
dev.log('🔵 _scanAndUpdateDB 异常: $e', name: 'BatteryHealth');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +222,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
String year = nowUtc.year.toString();
|
String year = nowUtc.year.toString();
|
||||||
String month = nowUtc.month.toString().padLeft(2, '0');
|
String month = nowUtc.month.toString().padLeft(2, '0');
|
||||||
String day = nowUtc.day.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<BatteryHealthPage>
|
|||||||
|
|
||||||
// ---------- SAF 授权引导 ----------
|
// ---------- SAF 授权引导 ----------
|
||||||
void _showSafAuthorizationDialog() {
|
void _showSafAuthorizationDialog() {
|
||||||
|
dev.log('🔵 _showSafAuthorizationDialog 被调用', name: 'BatteryHealth');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
showDialog(
|
showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
@@ -274,7 +288,8 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
dev.log('🔵 用户取消授权', name: 'BatteryHealth');
|
||||||
|
Navigator.pop(context, 'cancel');
|
||||||
setState(() {
|
setState(() {
|
||||||
_safState = SafPermissionState.denied;
|
_safState = SafPermissionState.denied;
|
||||||
});
|
});
|
||||||
@@ -282,37 +297,44 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
child: const Text('取消'),
|
child: const Text('取消'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
// 触发关闭
|
dev.log('🔵 用户点击选择文件夹', name: 'BatteryHealth');
|
||||||
Navigator.pop(context);
|
Navigator.pop(context, 'select');
|
||||||
|
|
||||||
// 延迟确保动画完成
|
|
||||||
await Future.delayed(const Duration(milliseconds: 150));
|
|
||||||
|
|
||||||
final result = await _safService.requestPermission();
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
setState(() {
|
|
||||||
_safState = SafPermissionState.authorized;
|
|
||||||
});
|
|
||||||
readBatteryCapacity();
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(result.errorMessage ?? '授权失败,请重试'),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_showSafAuthorizationDialog();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: const Text('选择文件夹'),
|
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定调 ----------
|
// ---------- 核心:系统API定调 ----------
|
||||||
@@ -520,10 +542,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 SAF 授权
|
|
||||||
_safState = await _safService.getPermissionState();
|
_safState = await _safService.getPermissionState();
|
||||||
if (_safState != SafPermissionState.authorized) {
|
if (_safState != SafPermissionState.authorized) {
|
||||||
if (mounted) _showSafAuthorizationDialog();
|
dev.log('[_fetchChargeDataFromLog] 未授权,跳过', name: 'BatteryHealth');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,9 +630,17 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
|
|
||||||
// ---------- 读取当前数据 ----------
|
// ---------- 读取当前数据 ----------
|
||||||
Future<void> readBatteryCapacity() async {
|
Future<void> readBatteryCapacity() async {
|
||||||
|
dev.log(
|
||||||
|
'🔵 ========== readBatteryCapacity 开始 ==========',
|
||||||
|
name: 'BatteryHealth',
|
||||||
|
);
|
||||||
|
|
||||||
// 检查 SAF 授权
|
// 检查 SAF 授权
|
||||||
_safState = await _safService.getPermissionState();
|
_safState = await _safService.getPermissionState();
|
||||||
|
dev.log('🔵 SAF状态: $_safState', name: 'BatteryHealth');
|
||||||
|
|
||||||
if (_safState != SafPermissionState.authorized) {
|
if (_safState != SafPermissionState.authorized) {
|
||||||
|
dev.log('🔵 未授权,弹窗', name: 'BatteryHealth');
|
||||||
if (mounted) _showSafAuthorizationDialog();
|
if (mounted) _showSafAuthorizationDialog();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -620,11 +649,16 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
await _scanAndUpdateDB();
|
await _scanAndUpdateDB();
|
||||||
|
|
||||||
final fileName = getTodayFileName();
|
final fileName = getTodayFileName();
|
||||||
|
dev.log('🔵 今日文件名: $fileName', name: 'BatteryHealth');
|
||||||
|
|
||||||
final exists = await _safService.logFileExists(fileName);
|
final exists = await _safService.logFileExists(fileName);
|
||||||
|
dev.log('🔵 文件存在: $exists', name: 'BatteryHealth');
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
dev.log('🔵 文件不存在,尝试从数据库读取', name: 'BatteryHealth');
|
||||||
List<Map<String, dynamic>> recent = await _queryRecentData(1);
|
List<Map<String, dynamic>> recent = await _queryRecentData(1);
|
||||||
if (recent.isNotEmpty) {
|
if (recent.isNotEmpty) {
|
||||||
|
dev.log('🔵 数据库有数据: ${recent.first}', name: 'BatteryHealth');
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentCap = recent.first['capacity'].toDouble();
|
_currentCap = recent.first['capacity'].toDouble();
|
||||||
_maxVolt = recent.first['volt_max'] ?? 0;
|
_maxVolt = recent.first['volt_max'] ?? 0;
|
||||||
@@ -633,6 +667,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
});
|
});
|
||||||
await _updateTrendChart();
|
await _updateTrendChart();
|
||||||
} else {
|
} else {
|
||||||
|
dev.log('🔵 数据库无数据', name: 'BatteryHealth');
|
||||||
setState(() => _hasData = false);
|
setState(() => _hasData = false);
|
||||||
}
|
}
|
||||||
if (_isCharging) _fetchChargeDataFromLog();
|
if (_isCharging) _fetchChargeDataFromLog();
|
||||||
@@ -643,7 +678,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final lines = await _safService.readLogFile(fileName);
|
final lines = await _safService.readLogFile(fileName);
|
||||||
|
dev.log('🔵 读取到 ${lines.length} 行', name: 'BatteryHealth');
|
||||||
if (lines.isEmpty) {
|
if (lines.isEmpty) {
|
||||||
|
dev.log('🔵 文件为空', name: 'BatteryHealth');
|
||||||
setState(() => _hasData = false);
|
setState(() => _hasData = false);
|
||||||
if (_isCharging) _fetchChargeDataFromLog();
|
if (_isCharging) _fetchChargeDataFromLog();
|
||||||
_chargeAnimationController.reset();
|
_chargeAnimationController.reset();
|
||||||
@@ -651,12 +688,23 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 打印前几行
|
||||||
|
if (lines.isNotEmpty) {
|
||||||
|
dev.log('🔵 第一行: ${lines.first}', name: 'BatteryHealth');
|
||||||
|
if (lines.length > 1) {
|
||||||
|
dev.log('🔵 第二行: ${lines[1]}', name: 'BatteryHealth');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_isCharging) _parseChargeDataFromLog(lines);
|
if (_isCharging) _parseChargeDataFromLog(lines);
|
||||||
|
|
||||||
List<String> validLines = lines
|
List<String> validLines = lines
|
||||||
.where((line) => line.contains('tbat='))
|
.where((line) => line.contains('tbat='))
|
||||||
.toList();
|
.toList();
|
||||||
|
dev.log('🔵 有效行数 (含tbat): ${validLines.length}', name: 'BatteryHealth');
|
||||||
|
|
||||||
if (validLines.isEmpty) {
|
if (validLines.isEmpty) {
|
||||||
|
dev.log('🔵 无有效数据行', name: 'BatteryHealth');
|
||||||
setState(() => _hasData = false);
|
setState(() => _hasData = false);
|
||||||
_chargeAnimationController.reset();
|
_chargeAnimationController.reset();
|
||||||
_updateChargeState();
|
_updateChargeState();
|
||||||
@@ -664,6 +712,8 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
String lastLine = validLines.last;
|
String lastLine = validLines.last;
|
||||||
|
dev.log('🔵 最后一行: $lastLine', name: 'BatteryHealth');
|
||||||
|
|
||||||
RegExp capReg = RegExp(r'learned_cap=(\d+)');
|
RegExp capReg = RegExp(r'learned_cap=(\d+)');
|
||||||
RegExp cycleReg = RegExp(r'cycle=(\d+)');
|
RegExp cycleReg = RegExp(r'cycle=(\d+)');
|
||||||
RegExp tempReg = RegExp(r'tbat=(\d+)');
|
RegExp tempReg = RegExp(r'tbat=(\d+)');
|
||||||
@@ -674,7 +724,13 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
String? tempStr = tempReg.firstMatch(lastLine)?.group(1);
|
String? tempStr = tempReg.firstMatch(lastLine)?.group(1);
|
||||||
String? voltStr = voltageReg.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) {
|
if (capStr == null) {
|
||||||
|
dev.log('🔵 未找到 learned_cap', name: 'BatteryHealth');
|
||||||
setState(() => _hasData = false);
|
setState(() => _hasData = false);
|
||||||
_chargeAnimationController.reset();
|
_chargeAnimationController.reset();
|
||||||
_updateChargeState();
|
_updateChargeState();
|
||||||
@@ -686,6 +742,11 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
double temp = double.parse(tempStr ?? '0');
|
double temp = double.parse(tempStr ?? '0');
|
||||||
double volt = double.parse(voltStr ?? '0') / 1000000;
|
double volt = double.parse(voltStr ?? '0') / 1000000;
|
||||||
|
|
||||||
|
dev.log(
|
||||||
|
'🔵 解析结果: capMah=$capMah, cycle=$cycle, temp=$temp, volt=$volt',
|
||||||
|
name: 'BatteryHealth',
|
||||||
|
);
|
||||||
|
|
||||||
List<double> temps = [];
|
List<double> temps = [];
|
||||||
for (String line in validLines) {
|
for (String line in validLines) {
|
||||||
RegExpMatch? match = tempReg.firstMatch(line);
|
RegExpMatch? match = tempReg.firstMatch(line);
|
||||||
@@ -720,10 +781,20 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
_hasData = true;
|
_hasData = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dev.log(
|
||||||
|
'🔵 数据更新完成: _currentCap=$_currentCap, _cycleCount=$_cycleCount',
|
||||||
|
name: 'BatteryHealth',
|
||||||
|
);
|
||||||
|
|
||||||
await _updateTrendChart();
|
await _updateTrendChart();
|
||||||
_chargeAnimationController.reset();
|
_chargeAnimationController.reset();
|
||||||
_updateChargeState();
|
_updateChargeState();
|
||||||
|
dev.log(
|
||||||
|
'🔵 ========== readBatteryCapacity 完成 ==========',
|
||||||
|
name: 'BatteryHealth',
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
dev.log('🔵 readBatteryCapacity 异常: $e', name: 'BatteryHealth');
|
||||||
setState(() => _hasData = false);
|
setState(() => _hasData = false);
|
||||||
_chargeAnimationController.reset();
|
_chargeAnimationController.reset();
|
||||||
_updateChargeState();
|
_updateChargeState();
|
||||||
@@ -741,6 +812,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
dev.log('🔵 ========== initState 开始 ==========', name: 'BatteryHealth');
|
||||||
|
|
||||||
_chargeAnimationController = AnimationController(
|
_chargeAnimationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
@@ -775,18 +847,29 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 延迟初始化 SAF 并读取数据
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
dev.log('🔵 initState -> addPostFrameCallback 开始', name: 'BatteryHealth');
|
||||||
|
|
||||||
await _safService.init();
|
await _safService.init();
|
||||||
|
dev.log(
|
||||||
|
'🔵 _safService.init() 完成,cachedUri: ${_safService.cachedUri}',
|
||||||
|
name: 'BatteryHealth',
|
||||||
|
);
|
||||||
|
|
||||||
_safState = await _safService.getPermissionState();
|
_safState = await _safService.getPermissionState();
|
||||||
|
dev.log('🔵 _safState: $_safState', name: 'BatteryHealth');
|
||||||
|
|
||||||
if (_safState == SafPermissionState.unauthorized ||
|
if (_safState == SafPermissionState.unauthorized ||
|
||||||
_safState == SafPermissionState.expired) {
|
_safState == SafPermissionState.expired) {
|
||||||
|
dev.log('🔵 需要授权,弹窗', name: 'BatteryHealth');
|
||||||
if (mounted) _showSafAuthorizationDialog();
|
if (mounted) _showSafAuthorizationDialog();
|
||||||
} else {
|
} else {
|
||||||
|
dev.log('🔵 已有授权,调用 readBatteryCapacity', name: 'BatteryHealth');
|
||||||
readBatteryCapacity();
|
readBatteryCapacity();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dev.log('🔵 ========== initState 结束 ==========', name: 'BatteryHealth');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -802,11 +885,17 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle.dark,
|
value: SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.white,
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
statusBarBrightness: Brightness.dark,
|
||||||
|
),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.white, // 🟢 页面背景色与 AppBar 统一
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: const Color.fromARGB(0, 255, 255, 255),
|
backgroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0, // 🟢 去掉阴影
|
||||||
|
scrolledUnderElevation: 0, // 🟢 滚动时也不出现阴影
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'电池健康度',
|
'电池健康度',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/saf_permission_state.dart';
|
import '../models/saf_permission_state.dart';
|
||||||
|
|
||||||
|
/// SAF 存储服务
|
||||||
|
/// 负责管理通过 SAF 授权的文件夹访问
|
||||||
class SafStorageService {
|
class SafStorageService {
|
||||||
static final SafStorageService _instance = SafStorageService._internal();
|
static final SafStorageService _instance = SafStorageService._internal();
|
||||||
factory SafStorageService() => _instance;
|
factory SafStorageService() => _instance;
|
||||||
@@ -14,7 +16,7 @@ class SafStorageService {
|
|||||||
static const String _prefKeySafUri = 'saf_storage_uri';
|
static const String _prefKeySafUri = 'saf_storage_uri';
|
||||||
String? _cachedUri;
|
String? _cachedUri;
|
||||||
|
|
||||||
// 🟢 初始化时从 SharedPreferences 恢复 URI
|
/// 初始化,从 SharedPreferences 恢复 URI
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
@@ -30,14 +32,16 @@ class SafStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取当前授权状态(简化版,直接根据缓存判断)
|
||||||
Future<SafPermissionState> getPermissionState() async {
|
Future<SafPermissionState> getPermissionState() async {
|
||||||
if (_cachedUri == null || _cachedUri!.isEmpty) {
|
if (_cachedUri == null || _cachedUri!.isEmpty) {
|
||||||
return SafPermissionState.unauthorized;
|
return SafPermissionState.unauthorized;
|
||||||
}
|
}
|
||||||
// 🟢 简化:直接返回 authorized,相信本地缓存的 URI
|
// 直接返回 authorized,相信缓存
|
||||||
return SafPermissionState.authorized;
|
return SafPermissionState.authorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 请求 SAF 授权(引导用户选择文件夹)
|
||||||
Future<SafAuthorizationResult> requestPermission() async {
|
Future<SafAuthorizationResult> requestPermission() async {
|
||||||
try {
|
try {
|
||||||
final String? uriString = await _channel.invokeMethod('selectFolder');
|
final String? uriString = await _channel.invokeMethod('selectFolder');
|
||||||
@@ -62,23 +66,57 @@ class SafStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 🟢 将 URI 传递给 Kotlin 端(用于恢复 safUri)
|
||||||
|
Future<bool> restoreUri() async {
|
||||||
|
if (_cachedUri == null || _cachedUri!.isEmpty) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('[SAF] restoreUri: 缓存 URI 为空');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final bool? result = await _channel.invokeMethod('restoreUri', {
|
||||||
|
'uri': _cachedUri,
|
||||||
|
});
|
||||||
|
if (result == true) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('[SAF] restoreUri 成功: $_cachedUri');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('[SAF] restoreUri 失败: 返回 false');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('[SAF] restoreUri 异常: $e');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查指定日期的日志文件是否存在
|
||||||
Future<bool> logFileExists(String fileName) async {
|
Future<bool> logFileExists(String fileName) async {
|
||||||
final state = await getPermissionState();
|
final state = await getPermissionState();
|
||||||
if (state != SafPermissionState.authorized) return false;
|
if (state != SafPermissionState.authorized) return false;
|
||||||
try {
|
try {
|
||||||
final files = await _channel.invokeMethod('listFiles');
|
final files = await listFiles();
|
||||||
if (files is List) {
|
return files.contains(fileName);
|
||||||
return files.contains(fileName);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 读取日志文件内容(返回行列表)
|
||||||
Future<List<String>> readLogFile(String fileName) async {
|
Future<List<String>> readLogFile(String fileName) async {
|
||||||
|
// 🟢 先尝试恢复 URI
|
||||||
|
await restoreUri();
|
||||||
|
|
||||||
final state = await getPermissionState();
|
final state = await getPermissionState();
|
||||||
if (state != SafPermissionState.authorized) return [];
|
if (state != SafPermissionState.authorized) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String? content = await _channel.invokeMethod('readLogFile', {
|
final String? content = await _channel.invokeMethod('readLogFile', {
|
||||||
'fileName': fileName,
|
'fileName': fileName,
|
||||||
@@ -93,9 +131,14 @@ class SafStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取文件夹中的文件列表
|
||||||
Future<List<String>> listFiles() async {
|
Future<List<String>> listFiles() async {
|
||||||
|
// 🟢 先尝试恢复 URI
|
||||||
|
await restoreUri();
|
||||||
|
|
||||||
final state = await getPermissionState();
|
final state = await getPermissionState();
|
||||||
if (state != SafPermissionState.authorized) return [];
|
if (state != SafPermissionState.authorized) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await _channel.invokeMethod('listFiles');
|
final result = await _channel.invokeMethod('listFiles');
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
@@ -110,16 +153,19 @@ class SafStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 验证授权是否有效(简化版)
|
||||||
Future<bool> validatePermission() async {
|
Future<bool> validatePermission() async {
|
||||||
final state = await getPermissionState();
|
final state = await getPermissionState();
|
||||||
return state == SafPermissionState.authorized;
|
return state == SafPermissionState.authorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 清除缓存
|
||||||
void _clearCache() {
|
void _clearCache() {
|
||||||
_cachedUri = null;
|
_cachedUri = null;
|
||||||
_saveUri(null);
|
_saveUri(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 保存 URI 到 SharedPreferences
|
||||||
Future<void> _saveUri(String? uri) async {
|
Future<void> _saveUri(String? uri) async {
|
||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
@@ -141,5 +187,6 @@ class SafStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取缓存的 URI(用于调试)
|
||||||
String? get cachedUri => _cachedUri;
|
String? get cachedUri => _cachedUri;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.3.0-dev.4
|
version: 1.4.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.2
|
sdk: ^3.9.2
|
||||||
|
|||||||
Reference in New Issue
Block a user