SAF初步修改,等待验证
This commit is contained in:
+7
-1
@@ -1,7 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'pages/battery_health_page.dart';
|
||||
import 'services/saf_storage_service.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 🟢 初始化 SAF 服务
|
||||
await SafStorageService().init();
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/// SAF 权限状态
|
||||
enum SafPermissionState {
|
||||
/// 未授权(首次使用)
|
||||
unauthorized,
|
||||
|
||||
/// 已授权,Uri 有效
|
||||
authorized,
|
||||
|
||||
/// 授权已过期或 Uri 失效
|
||||
expired,
|
||||
|
||||
/// 用户拒绝了授权
|
||||
denied,
|
||||
}
|
||||
|
||||
/// SAF 授权结果
|
||||
class SafAuthorizationResult {
|
||||
final bool success;
|
||||
final String? uriString;
|
||||
final SafPermissionState state;
|
||||
final String? errorMessage;
|
||||
|
||||
SafAuthorizationResult(
|
||||
{
|
||||
required this.success,
|
||||
this.uriString,
|
||||
required this.state,
|
||||
this.errorMessage,
|
||||
}
|
||||
);
|
||||
|
||||
factory SafAuthorizationResult.success(String uri) {
|
||||
return SafAuthorizationResult(
|
||||
success: true,
|
||||
uriString: uri,
|
||||
state: SafPermissionState.authorized,
|
||||
);
|
||||
}
|
||||
|
||||
factory SafAuthorizationResult.denied() {
|
||||
return SafAuthorizationResult(
|
||||
success: false,
|
||||
state: SafPermissionState.denied,
|
||||
errorMessage: '用户拒绝了文件夹访问权限',
|
||||
);
|
||||
}
|
||||
|
||||
factory SafAuthorizationResult.expired() {
|
||||
return SafAuthorizationResult(
|
||||
success: false,
|
||||
state: SafPermissionState.expired,
|
||||
errorMessage: '文件夹访问权限已过期,请重新授权',
|
||||
);
|
||||
}
|
||||
|
||||
factory SafAuthorizationResult.error(String message) {
|
||||
return SafAuthorizationResult(
|
||||
success: false,
|
||||
state: SafPermissionState.unauthorized,
|
||||
errorMessage: message,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import 'dart:developer' as dev;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@@ -13,6 +12,8 @@ import 'package:device_info_plus/device_info_plus.dart';
|
||||
import '../models/device_matching.dart';
|
||||
import '../utils/charge_utils.dart';
|
||||
import '../widgets/charge_detail_page.dart';
|
||||
import '../services/saf_storage_service.dart';
|
||||
import '../models/saf_permission_state.dart';
|
||||
|
||||
// --- 潘通色定义 ---
|
||||
const Color kHealthGreen = Color(0xFF00B140);
|
||||
@@ -40,23 +41,24 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
double _maxVolt = 0;
|
||||
double _minVolt = 0;
|
||||
bool _hasData = false;
|
||||
final String _filePath = '/storage/emulated/0/mbattery_charger/';
|
||||
|
||||
// ---------- 趋势图数据 ----------
|
||||
List<Map<String, dynamic>> _trendData = [];
|
||||
|
||||
// ---------- 充电状态 ----------
|
||||
bool _isCharging = false;
|
||||
String _chargeType = ''; // "EPP", "PPS", "SDP", "无线", "MZ_EPP" 等
|
||||
String _chargeType = '';
|
||||
double _chargePower = 0.0;
|
||||
double _chargeVolt = 0.0;
|
||||
double _chargeCurr = 0.0;
|
||||
int? _wiredLevel;
|
||||
bool _isWireless = false;
|
||||
|
||||
// 🟢 1.3.0-dev.3: 充电开始时间(UTC),用于时间窗口判断
|
||||
DateTime? _chargingStartTime;
|
||||
|
||||
// ---------- SAF 状态 ----------
|
||||
final SafStorageService _safService = SafStorageService();
|
||||
SafPermissionState _safState = SafPermissionState.unauthorized;
|
||||
|
||||
// 动画控制
|
||||
late AnimationController _chargeAnimationController;
|
||||
late Animation<double> _bottomSlide;
|
||||
@@ -150,22 +152,25 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 扫描目录 ----------
|
||||
// ---------- 扫描目录 (SAF 模式) ----------
|
||||
Future<void> _scanAndUpdateDB() async {
|
||||
Directory dir = Directory(_filePath);
|
||||
if (!await dir.exists()) return;
|
||||
// 检查 SAF 授权状态
|
||||
_safState = await _safService.getPermissionState();
|
||||
if (_safState != SafPermissionState.authorized) {
|
||||
_showSafAuthorizationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
List<FileSystemEntity> files = await dir.list().toList();
|
||||
RegExp filePattern = RegExp(r'mbattery_charger_log_(\d{4}_\d{2}_\d{2})$');
|
||||
try {
|
||||
final files = await _safService.listFiles();
|
||||
RegExp filePattern = RegExp(r'mbattery_charger_log_(\d{4}_\d{2}_\d{2})$');
|
||||
|
||||
for (FileSystemEntity entity in files) {
|
||||
if (entity is File) {
|
||||
String fileName = path.basename(entity.path);
|
||||
for (String fileName in files) {
|
||||
Match? match = filePattern.firstMatch(fileName);
|
||||
if (match != null) {
|
||||
String dateStr = match.group(1)!.replaceAll('_', '-');
|
||||
try {
|
||||
List<String> lines = await entity.readAsLines();
|
||||
List<String> lines = await _safService.readLogFile(fileName);
|
||||
if (lines.isEmpty) continue;
|
||||
|
||||
List<String> validLines = lines
|
||||
@@ -195,10 +200,12 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dev.log('[_scanAndUpdateDB] ❌ 扫描异常: $e', name: 'BatteryHealth');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 1.3.0-dev.1: 使用 UTC 生成文件名 ----------
|
||||
// ---------- UTC 文件名 ----------
|
||||
String getTodayFileName() {
|
||||
DateTime nowUtc = DateTime.now().toUtc();
|
||||
String year = nowUtc.year.toString();
|
||||
@@ -208,7 +215,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
|
||||
// ---------- 时间戳工具方法 ----------
|
||||
/// 解析日志行中的时间戳,强制解析为 UTC
|
||||
DateTime? _parseLogTimestamp(String line) {
|
||||
RegExp reg = RegExp(r'\[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})\]');
|
||||
Match? match = reg.firstMatch(line);
|
||||
@@ -231,20 +237,79 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
}
|
||||
|
||||
/// 🟢 1.3.0-dev.3: 时间窗口以插入时刻为基准,只向前看
|
||||
bool _isLogTimestampValid(DateTime? logTime) {
|
||||
if (logTime == null) return false;
|
||||
if (_chargingStartTime == null) {
|
||||
// 边缘情况:回退到当前时间 ±3 分钟
|
||||
DateTime nowUtc = DateTime.now().toUtc();
|
||||
Duration diff = nowUtc.difference(logTime);
|
||||
return diff.abs().inMinutes <= 3;
|
||||
}
|
||||
// 日志时间必须在 [充电开始时间, 充电开始时间 + 3 分钟] 范围内
|
||||
DateTime endTime = _chargingStartTime!.add(const Duration(minutes: 3));
|
||||
return logTime.isAfter(_chargingStartTime!) && logTime.isBefore(endTime);
|
||||
}
|
||||
|
||||
// ---------- SAF 授权引导 ----------
|
||||
void _showSafAuthorizationDialog() {
|
||||
if (!mounted) return;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('需要文件夹访问权限'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
Text('请授权访问日志文件夹,以读取电池数据。'),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'目标路径:\n/storage/emulated/0/mbattery_charger/',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text('点击"选择文件夹"后,请在系统文件管理器中找到并选中上述文件夹。'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
setState(() {
|
||||
_safState = SafPermissionState.denied;
|
||||
});
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final ctx = context;
|
||||
Navigator.pop(ctx);
|
||||
final result = await _safService.requestPermission();
|
||||
if (result.success) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_safState = SafPermissionState.authorized;
|
||||
});
|
||||
readBatteryCapacity();
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(result.errorMessage ?? '授权失败,请重试'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
_showSafAuthorizationDialog();
|
||||
}
|
||||
},
|
||||
child: const Text('选择文件夹'), // 🟢 加这行
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- 核心:系统API定调 ----------
|
||||
void _applySystemChargeState(bool isCharging, String systemChargeType) {
|
||||
dev.log(
|
||||
@@ -263,12 +328,11 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
_chargeVolt = 0.0;
|
||||
_chargeCurr = 0.0;
|
||||
_wiredLevel = null;
|
||||
_chargingStartTime = null; // 🟢 清空开始时间
|
||||
_chargingStartTime = null;
|
||||
_updateChargeState();
|
||||
return;
|
||||
}
|
||||
|
||||
// 🟢 1.3.0-dev.3: 记录充电开始时刻(仅当从断开状态变为充电状态时)
|
||||
if (_chargingStartTime == null) {
|
||||
_chargingStartTime = DateTime.now().toUtc();
|
||||
dev.log(
|
||||
@@ -297,7 +361,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
_isWireless = false;
|
||||
_chargeType = "SDP";
|
||||
} else {
|
||||
// UNKNOWN 时清空旧类型,等待日志修正
|
||||
dev.log(
|
||||
'[_applySystemChargeState] ⚠️ 未知类型: $systemChargeType,清空类型等待日志',
|
||||
name: 'BatteryHealth',
|
||||
@@ -324,7 +387,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
name: 'BatteryHealth',
|
||||
);
|
||||
|
||||
// ---------- 1. 收集所有在有效时间窗口内的数据行 ----------
|
||||
List<Map<String, dynamic>> validLines = [];
|
||||
for (String line in lines) {
|
||||
DateTime? timestamp = _parseLogTimestamp(line);
|
||||
@@ -349,10 +411,8 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
return;
|
||||
}
|
||||
|
||||
// 按时间戳排序(从新到旧)
|
||||
validLines.sort((a, b) => b['timestamp'].compareTo(a['timestamp']));
|
||||
|
||||
// 取最新的一条
|
||||
Map<String, dynamic> latest = validLines.first;
|
||||
String latestLine = latest['line'];
|
||||
String latestType = latest['type'];
|
||||
@@ -363,7 +423,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
name: 'BatteryHealth',
|
||||
);
|
||||
|
||||
// ---------- 2. 无线处理 ----------
|
||||
// ---------- 无线 ----------
|
||||
if (latestType == 'wls') {
|
||||
dev.log('[_parseChargeDataFromLog] 处理无线数据', name: 'BatteryHealth');
|
||||
|
||||
@@ -390,7 +450,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------- 3. 有线处理 ----------
|
||||
// ---------- 有线 ----------
|
||||
if (latestType == 'usb') {
|
||||
dev.log('[_parseChargeDataFromLog] 处理有线数据', name: 'BatteryHealth');
|
||||
|
||||
@@ -411,7 +471,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
|
||||
_isWireless = false;
|
||||
|
||||
// 🟢 1.3.0-dev.2: real_type 优先级最高
|
||||
if (realType != null && realType == "PPS") {
|
||||
_chargeType = "PPS";
|
||||
} else if (realType != null &&
|
||||
@@ -438,14 +497,13 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
}
|
||||
|
||||
// 🟢 1.3.0-dev.3: 增加超时保护
|
||||
// ---------- SAF 文件读取 ----------
|
||||
Future<void> _fetchChargeDataFromLog() async {
|
||||
if (!_isCharging) {
|
||||
dev.log('[_fetchChargeDataFromLog] 未充电,跳过读取', name: 'BatteryHealth');
|
||||
return;
|
||||
}
|
||||
|
||||
// 超时检查:如果插入超过 5 分钟还没读到数据,不再频繁报错
|
||||
if (_chargingStartTime != null) {
|
||||
Duration elapsed = DateTime.now().toUtc().difference(_chargingStartTime!);
|
||||
if (elapsed.inMinutes > 5) {
|
||||
@@ -457,24 +515,35 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
}
|
||||
|
||||
String fullPath = _filePath + getTodayFileName();
|
||||
dev.log('[_fetchChargeDataFromLog] 尝试读取: $fullPath', name: 'BatteryHealth');
|
||||
// 检查 SAF 授权
|
||||
_safState = await _safService.getPermissionState();
|
||||
if (_safState != SafPermissionState.authorized) {
|
||||
if (mounted) _showSafAuthorizationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
File file = File(fullPath);
|
||||
if (!await file.exists()) {
|
||||
final fileName = getTodayFileName();
|
||||
dev.log('[_fetchChargeDataFromLog] 尝试读取: $fileName', name: 'BatteryHealth');
|
||||
|
||||
final exists = await _safService.logFileExists(fileName);
|
||||
if (!exists) {
|
||||
dev.log(
|
||||
'[_fetchChargeDataFromLog] ⚠️ 文件不存在: $fullPath',
|
||||
'[_fetchChargeDataFromLog] ⚠️ 文件不存在: $fileName',
|
||||
name: 'BatteryHealth',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> lines = await file.readAsLines();
|
||||
final lines = await _safService.readLogFile(fileName);
|
||||
if (lines.isEmpty) {
|
||||
dev.log('[_fetchChargeDataFromLog] 文件为空', name: 'BatteryHealth');
|
||||
return;
|
||||
}
|
||||
dev.log(
|
||||
'[_fetchChargeDataFromLog] ✅ 读取成功,共 ${lines.length} 行',
|
||||
name: 'BatteryHealth',
|
||||
);
|
||||
if (lines.isEmpty) return;
|
||||
_parseChargeDataFromLog(lines);
|
||||
} catch (e) {
|
||||
dev.log('[_fetchChargeDataFromLog] ❌ 读取异常: $e', name: 'BatteryHealth');
|
||||
@@ -535,19 +604,20 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
|
||||
// ---------- 读取当前数据 ----------
|
||||
Future<void> readBatteryCapacity() async {
|
||||
var status = await Permission.manageExternalStorage.request();
|
||||
if (!status.isGranted) {
|
||||
setState(() => _hasData = false);
|
||||
// 检查 SAF 授权
|
||||
_safState = await _safService.getPermissionState();
|
||||
if (_safState != SafPermissionState.authorized) {
|
||||
if (mounted) _showSafAuthorizationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
await _getDeviceInfo();
|
||||
await _scanAndUpdateDB();
|
||||
|
||||
String fullPath = _filePath + getTodayFileName();
|
||||
File file = File(fullPath);
|
||||
final fileName = getTodayFileName();
|
||||
final exists = await _safService.logFileExists(fileName);
|
||||
|
||||
if (!await file.exists()) {
|
||||
if (!exists) {
|
||||
List<Map<String, dynamic>> recent = await _queryRecentData(1);
|
||||
if (recent.isNotEmpty) {
|
||||
setState(() {
|
||||
@@ -567,7 +637,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> lines = await file.readAsLines();
|
||||
final lines = await _safService.readLogFile(fileName);
|
||||
if (lines.isEmpty) {
|
||||
setState(() => _hasData = false);
|
||||
if (_isCharging) _fetchChargeDataFromLog();
|
||||
@@ -700,8 +770,16 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
readBatteryCapacity();
|
||||
// 延迟初始化 SAF 并读取数据
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _safService.init();
|
||||
_safState = await _safService.getPermissionState();
|
||||
if (_safState == SafPermissionState.unauthorized ||
|
||||
_safState == SafPermissionState.expired) {
|
||||
if (mounted) _showSafAuthorizationDialog();
|
||||
} else {
|
||||
readBatteryCapacity();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -799,9 +877,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.0,
|
||||
color: healthPercent < 70
|
||||
? Colors.red
|
||||
: Colors.black, // 🟢 动态颜色
|
||||
color: healthPercent < 70 ? Colors.red : Colors.black,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
@@ -813,7 +889,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
height: 1.0,
|
||||
color: healthPercent < 70
|
||||
? Colors.red
|
||||
: Colors.grey.shade600, // 🟢 动态颜色
|
||||
: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1047,7 +1123,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
|
||||
} else if (value >= 70) {
|
||||
barColor = kHealthYellow;
|
||||
} else {
|
||||
barColor = Colors.red; // 🟢 小于 70 显示红色
|
||||
barColor = Colors.red;
|
||||
}
|
||||
} else {
|
||||
barColor = Colors.grey.shade300;
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import '../models/saf_permission_state.dart';
|
||||
|
||||
/// 使用 file_picker 实现文件夹访问
|
||||
class SafStorageService {
|
||||
static final SafStorageService _instance = SafStorageService._internal();
|
||||
factory SafStorageService() => _instance;
|
||||
SafStorageService._internal();
|
||||
|
||||
static const String _prefKeySafPath = 'saf_storage_path';
|
||||
String? _cachedPath;
|
||||
|
||||
Future<void> init() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_cachedPath = prefs.getString(_prefKeySafPath);
|
||||
if (kDebugMode) {
|
||||
print('[SAF] 初始化,缓存路径: $_cachedPath');
|
||||
}
|
||||
}
|
||||
|
||||
Future<SafPermissionState> getPermissionState() async {
|
||||
if (_cachedPath == null || _cachedPath!.isEmpty) {
|
||||
return SafPermissionState.unauthorized;
|
||||
}
|
||||
try {
|
||||
final dir = Directory(_cachedPath!);
|
||||
if (!await dir.exists()) {
|
||||
_clearCache();
|
||||
return SafPermissionState.expired;
|
||||
}
|
||||
return SafPermissionState.authorized;
|
||||
} catch (e) {
|
||||
_clearCache();
|
||||
return SafPermissionState.expired;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SafAuthorizationResult> requestPermission() async {
|
||||
try {
|
||||
// 使用 file_picker 选择文件夹
|
||||
String? selectedPath = await FilePicker.platform.getDirectoryPath();
|
||||
|
||||
if (selectedPath == null || selectedPath.isEmpty) {
|
||||
return SafAuthorizationResult.denied();
|
||||
}
|
||||
|
||||
// 验证路径是否包含 mbattery_charger(可选,提高容错)
|
||||
if (!selectedPath.contains('mbattery_charger')) {
|
||||
return SafAuthorizationResult.error('请选择 mbattery_charger 文件夹');
|
||||
}
|
||||
|
||||
_cachedPath = selectedPath;
|
||||
await _savePath(selectedPath);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('[SAF] 授权成功,路径: $selectedPath');
|
||||
}
|
||||
|
||||
return SafAuthorizationResult.success(selectedPath);
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('[SAF] 授权失败: $e');
|
||||
}
|
||||
return SafAuthorizationResult.error('授权失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> logFileExists(String fileName) async {
|
||||
final state = await getPermissionState();
|
||||
if (state != SafPermissionState.authorized) return false;
|
||||
try {
|
||||
final file = File('${_cachedPath!}/$fileName');
|
||||
return await file.exists();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>> readLogFile(String fileName) async {
|
||||
final state = await getPermissionState();
|
||||
if (state != SafPermissionState.authorized) return [];
|
||||
try {
|
||||
final file = File('${_cachedPath!}/$fileName');
|
||||
if (!await file.exists()) return [];
|
||||
return await file.readAsLines();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('[SAF] 读取文件失败: $e');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>> listFiles() async {
|
||||
final state = await getPermissionState();
|
||||
if (state != SafPermissionState.authorized) return [];
|
||||
try {
|
||||
final dir = Directory(_cachedPath!);
|
||||
if (!await dir.exists()) return [];
|
||||
final entities = await dir.list().toList();
|
||||
return entities
|
||||
.where((e) => e is File)
|
||||
.map((e) => e.path.split(Platform.pathSeparator).last)
|
||||
.toList();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('[SAF] 列出文件失败: $e');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> validatePermission() async {
|
||||
final state = await getPermissionState();
|
||||
return state == SafPermissionState.authorized;
|
||||
}
|
||||
|
||||
void _clearCache() {
|
||||
_cachedPath = null;
|
||||
_savePath(null);
|
||||
}
|
||||
|
||||
Future<void> _savePath(String? path) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (path != null && path.isNotEmpty) {
|
||||
await prefs.setString(_prefKeySafPath, path);
|
||||
} else {
|
||||
await prefs.remove(_prefKeySafPath);
|
||||
}
|
||||
}
|
||||
|
||||
String? get cachedPath => _cachedPath;
|
||||
}
|
||||
Reference in New Issue
Block a user