2 Commits

Author SHA1 Message Date
lxh2875931338 45c69c8ec4 SAF测试阶段,验证中 2026-07-05 00:32:28 +08:00
lxh2875931338 dff0f6fb6b SAF实现修改 2026-07-05 00:02:23 +08:00
9 changed files with 362 additions and 200 deletions
+20 -23
View File
@@ -6,7 +6,7 @@ plugins {
android { android {
namespace = "com.lxh.battery_health" namespace = "com.lxh.battery_health"
compileSdk = flutter.compileSdkVersion compileSdk = 34
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
@@ -21,38 +21,39 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.lxh.battery_health" applicationId = "com.lxh.battery_health"
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = 34
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
multiDexEnabled = true multiDexEnabled = true
} }
signingConfigs { signingConfigs {
// 如果没有正式签名,可以用 debug 签名代替(测试用)
// 正式发布时请替换为自己的密钥
getByName("debug") {
// debug 签名已经存在,无需额外配置
}
create("release") { create("release") {
// 这里配置你正式发布的签名 // 如果有正式签名,在这里配置
// 如果还没有正式签名,debug 可以用 debug 签名替代 // keyAlias = "..."
keyAlias = "your_key_alias" // keyPassword = "..."
keyPassword = "your_key_password" // storeFile = file("...")
storeFile = file("your_keystore.jks") // storePassword = "..."
storePassword = "your_store_password"
} }
} }
signingConfigs {
// 如果没有正式签名,可以暂时用 debug 签名
// 正式发布时再替换
}
buildTypes { buildTypes {
debug { debug {
// 🟢 关键:debug 版本包名加 .dev 后缀
applicationIdSuffix = ".dev" applicationIdSuffix = ".dev"
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
isDebuggable = true
isMinifyEnabled = false
isShrinkResources = false
} }
release { release {
applicationIdSuffix = "" // 保持原包名 // release 版本不加后缀,保持原包名
// 🟢 如果还没有正式签名,先用 debug 签名替代 signingConfig = signingConfigs.getByName("debug") // 若没有正式签名,先用 debug 签名
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
proguardFiles( proguardFiles(
@@ -61,16 +62,12 @@ android {
) )
} }
} }
}
// 如果有 productFlavors,可以在这里配置
// 如果没有,忽略即可
}
flutter { flutter {
source = "../.." source = "../.."
} }
dependencies { dependencies {
// 如果项目中有其他依赖,添加在这里 // 无需额外添加 Activity KTX,因为已改用 startActivityForResult
// 注意:sqflite、permission_handler 等 Flutter 插件会自动包含
} }
@@ -1,96 +0,0 @@
package com.lxh.battery_health
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.lxh.battery_health/charge"
private val EVENT_CHANNEL = "com.lxh.battery_health/charge_events"
private var eventSink: EventChannel.EventSink? = null
private val chargeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent ?: return
when (intent.action) {
Intent.ACTION_POWER_CONNECTED -> sendChargeState(true)
Intent.ACTION_POWER_DISCONNECTED -> sendChargeState(false)
}
}
}
private fun sendChargeState(isCharging: Boolean) {
val chargeType = if (isCharging) {
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
android.util.Log.d("BatteryHealth", "plug value: $plug, isCharging: $isCharging")
when (plug) {
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS" // 🟢 关键修复
else -> "UNKNOWN"
}
} else {
"NONE"
}
eventSink?.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getChargeState") {
val isCharging = getBatteryStatus()
val chargeType = if (isCharging) {
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
when (plug) {
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS" // 🟢 关键修复
else -> "UNKNOWN"
}
} else {
"NONE"
}
result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
} else {
result.notImplemented()
}
}
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
val filter = IntentFilter().apply {
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
registerReceiver(chargeReceiver, filter)
}
override fun onCancel(arguments: Any?) {
eventSink = null
unregisterReceiver(chargeReceiver)
}
}
)
}
private fun getBatteryStatus(): Boolean {
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
return status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
}
}
@@ -0,0 +1,256 @@
package com.lxh.battery_health
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import androidx.activity.ComponentActivity
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
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 chargeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent ?: return
when (intent.action) {
Intent.ACTION_POWER_CONNECTED -> sendChargeState(true)
Intent.ACTION_POWER_DISCONNECTED -> sendChargeState(false)
}
}
}
private fun sendChargeState(isCharging: Boolean) {
val chargeType = if (isCharging) {
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
android.util.Log.d("BatteryHealth", "plug value: $plug, isCharging: $isCharging")
when (plug) {
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS"
else -> "UNKNOWN"
}
} else {
"NONE"
}
eventSink?.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
}
private fun getBatteryStatus(): Boolean {
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
return status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
}
// ---------- SAF 方法 ----------
private fun openFolderPicker() {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
val downloadsUri = DocumentsContract.buildDocumentUri(
"com.android.externalstorage.documents",
"primary:Downloads"
)
putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri)
} catch (e: Exception) {
// 忽略
}
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
startActivityForResult(intent, FOLDER_PICKER_REQUEST)
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "打开文件夹选择器失败: ${e.message}")
safResult?.error("SELECT_FOLDER_FAILED", e.message, null)
safResult = null
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FOLDER_PICKER_REQUEST && resultCode == ComponentActivity.RESULT_OK) {
val uri = data?.data
if (uri != null) {
try {
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(uri, takeFlags)
safUri = uri
android.util.Log.d("BatteryHealth", "SAF URI: $uri")
safResult?.success(uri.toString())
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "SAF 授权失败: ${e.message}")
safResult?.error("SAF_FAILED", e.message, null)
}
} else {
safResult?.success(null)
}
safResult = null
}
}
private fun getTreeDocId(): String? {
return safUri?.let { DocumentsContract.getTreeDocumentId(it) }
}
private fun listFiles(): List<String> {
val result = mutableListOf<String>()
val docId = getTreeDocId()
if (docId == null || docId.isEmpty()) return result
try {
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId)
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) {
result.add(name)
}
}
}
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "列出文件失败: ${e.message}")
}
return result
}
private fun getFileUri(fileName: String): Uri? {
val docId = getTreeDocId() ?: return null
try {
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(safUri!!, docId)
contentResolver.query(childrenUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val idIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
while (cursor.moveToNext()) {
val name = cursor.getString(nameIndex)
if (name == fileName) {
val fileDocId = cursor.getString(idIndex)
return DocumentsContract.buildDocumentUriUsingTree(safUri!!, fileDocId)
}
}
}
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "获取文件URI失败: ${e.message}")
}
return null
}
private fun readFile(uri: Uri): String? {
return try {
contentResolver.openInputStream(uri)?.use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader.readText()
}
}
} catch (e: Exception) {
android.util.Log.e("BatteryHealth", "读取文件失败: ${e.message}")
null
}
}
private fun readLogFile(fileName: String): String? {
val fileUri = getFileUri(fileName)
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") {
val isCharging = getBatteryStatus()
val chargeType = if (isCharging) {
val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1
when (plug) {
BatteryManager.BATTERY_PLUGGED_AC -> "AC"
BatteryManager.BATTERY_PLUGGED_USB -> "USB"
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS"
else -> "UNKNOWN"
}
} else {
"NONE"
}
result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType))
} else {
result.notImplemented()
}
}
// SAF 通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"selectFolder" -> {
safResult = result
openFolderPicker()
}
"readLogFile" -> {
val fileName = call.argument<String>("fileName")
if (fileName != null) {
val content = readLogFile(fileName)
result.success(content)
} else {
result.error("INVALID_ARGUMENT", "fileName is required", null)
}
}
"listFiles" -> {
val files = listFiles()
result.success(files)
}
"getUri" -> {
result.success(safUri?.toString())
}
else -> result.notImplemented()
}
}
// 充电状态事件通道
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
.setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
val filter = IntentFilter().apply {
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
registerReceiver(chargeReceiver, filter)
}
override fun onCancel(arguments: Any?) {
eventSink = null
unregisterReceiver(chargeReceiver)
}
}
)
}
}
+4
View File
@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
+3 -1
View File
@@ -20,7 +20,9 @@ class MyApp extends StatelessWidget {
title: '电池健康度', title: '电池健康度',
theme: ThemeData( theme: ThemeData(
fontFamily: 'HarmonyOS_Sans_SC', fontFamily: 'HarmonyOS_Sans_SC',
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 255, 255, 255),
),
useMaterial3: true, useMaterial3: true,
), ),
home: const BatteryHealthPage(), home: const BatteryHealthPage(),
+12 -6
View File
@@ -283,18 +283,23 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
), ),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
final ctx = context; // 触发关闭
Navigator.pop(ctx); Navigator.pop(context);
// 延迟确保动画完成
await Future.delayed(const Duration(milliseconds: 150));
final result = await _safService.requestPermission(); final result = await _safService.requestPermission();
if (result.success) {
if (!mounted) return; if (!mounted) return;
if (result.success) {
setState(() { setState(() {
_safState = SafPermissionState.authorized; _safState = SafPermissionState.authorized;
}); });
readBatteryCapacity(); readBatteryCapacity();
} else { } else {
if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar( SnackBar(
content: Text(result.errorMessage ?? '授权失败,请重试'), content: Text(result.errorMessage ?? '授权失败,请重试'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
@@ -303,7 +308,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_showSafAuthorizationDialog(); _showSafAuthorizationDialog();
} }
}, },
child: const Text('选择文件夹'), // 🟢 加这行 child: const Text('选择文件夹'),
), ),
], ],
), ),
@@ -774,6 +779,7 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
await _safService.init(); await _safService.init();
_safState = await _safService.getPermissionState(); _safState = await _safService.getPermissionState();
if (_safState == SafPermissionState.unauthorized || if (_safState == SafPermissionState.unauthorized ||
_safState == SafPermissionState.expired) { _safState == SafPermissionState.expired) {
if (mounted) _showSafAuthorizationDialog(); if (mounted) _showSafAuthorizationDialog();
+58 -49
View File
@@ -1,65 +1,59 @@
import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:file_picker/file_picker.dart';
import '../models/saf_permission_state.dart'; import '../models/saf_permission_state.dart';
/// 使用 file_picker 实现文件夹访问
class SafStorageService { class SafStorageService {
static final SafStorageService _instance = SafStorageService._internal(); static final SafStorageService _instance = SafStorageService._internal();
factory SafStorageService() => _instance; factory SafStorageService() => _instance;
SafStorageService._internal(); SafStorageService._internal();
static const String _prefKeySafPath = 'saf_storage_path'; static const MethodChannel _channel = MethodChannel(
String? _cachedPath; 'com.lxh.battery_health/saf',
);
static const String _prefKeySafUri = 'saf_storage_uri';
String? _cachedUri;
// 🟢 初始化时从 SharedPreferences 恢复 URI
Future<void> init() async { Future<void> init() async {
try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
_cachedPath = prefs.getString(_prefKeySafPath); _cachedUri = prefs.getString(_prefKeySafUri);
if (kDebugMode) { if (kDebugMode) {
print('[SAF] 初始化,缓存路径: $_cachedPath'); print('[SAF] 初始化,缓存 URI: $_cachedUri');
}
} catch (e) {
if (kDebugMode) {
print('[SAF] 初始化失败: $e');
}
_cachedUri = null;
} }
} }
Future<SafPermissionState> getPermissionState() async { Future<SafPermissionState> getPermissionState() async {
if (_cachedPath == null || _cachedPath!.isEmpty) { if (_cachedUri == null || _cachedUri!.isEmpty) {
return SafPermissionState.unauthorized; return SafPermissionState.unauthorized;
} }
try { // 🟢 简化:直接返回 authorized,相信本地缓存的 URI
final dir = Directory(_cachedPath!);
if (!await dir.exists()) {
_clearCache();
return SafPermissionState.expired;
}
return SafPermissionState.authorized; return SafPermissionState.authorized;
} catch (e) {
_clearCache();
return SafPermissionState.expired;
}
} }
Future<SafAuthorizationResult> requestPermission() async { Future<SafAuthorizationResult> requestPermission() async {
try { try {
// 使用 file_picker 选择文件夹 final String? uriString = await _channel.invokeMethod('selectFolder');
String? selectedPath = await FilePicker.platform.getDirectoryPath();
if (selectedPath == null || selectedPath.isEmpty) { if (uriString == null || uriString.isEmpty) {
return SafAuthorizationResult.denied(); return SafAuthorizationResult.denied();
} }
// 验证路径是否包含 mbattery_charger(可选,提高容错) _cachedUri = uriString;
if (!selectedPath.contains('mbattery_charger')) { await _saveUri(uriString);
return SafAuthorizationResult.error('请选择 mbattery_charger 文件夹');
}
_cachedPath = selectedPath;
await _savePath(selectedPath);
if (kDebugMode) { if (kDebugMode) {
print('[SAF] 授权成功,路径: $selectedPath'); print('[SAF] 授权成功,URI: $uriString');
} }
return SafAuthorizationResult.success(selectedPath); return SafAuthorizationResult.success(uriString);
} catch (e) { } catch (e) {
if (kDebugMode) { if (kDebugMode) {
print('[SAF] 授权失败: $e'); print('[SAF] 授权失败: $e');
@@ -72,8 +66,11 @@ class SafStorageService {
final state = await getPermissionState(); final state = await getPermissionState();
if (state != SafPermissionState.authorized) return false; if (state != SafPermissionState.authorized) return false;
try { try {
final file = File('${_cachedPath!}/$fileName'); final files = await _channel.invokeMethod('listFiles');
return await file.exists(); if (files is List) {
return files.contains(fileName);
}
return false;
} catch (e) { } catch (e) {
return false; return false;
} }
@@ -83,9 +80,11 @@ class SafStorageService {
final state = await getPermissionState(); final state = await getPermissionState();
if (state != SafPermissionState.authorized) return []; if (state != SafPermissionState.authorized) return [];
try { try {
final file = File('${_cachedPath!}/$fileName'); final String? content = await _channel.invokeMethod('readLogFile', {
if (!await file.exists()) return []; 'fileName': fileName,
return await file.readAsLines(); });
if (content == null || content.isEmpty) return [];
return content.split('\n');
} catch (e) { } catch (e) {
if (kDebugMode) { if (kDebugMode) {
print('[SAF] 读取文件失败: $e'); print('[SAF] 读取文件失败: $e');
@@ -98,13 +97,11 @@ class SafStorageService {
final state = await getPermissionState(); final state = await getPermissionState();
if (state != SafPermissionState.authorized) return []; if (state != SafPermissionState.authorized) return [];
try { try {
final dir = Directory(_cachedPath!); final result = await _channel.invokeMethod('listFiles');
if (!await dir.exists()) return []; if (result is List) {
final entities = await dir.list().toList(); return result.cast<String>();
return entities }
.whereType<File>() return [];
.map((e) => e.path.split(Platform.pathSeparator).last)
.toList();
} catch (e) { } catch (e) {
if (kDebugMode) { if (kDebugMode) {
print('[SAF] 列出文件失败: $e'); print('[SAF] 列出文件失败: $e');
@@ -119,18 +116,30 @@ class SafStorageService {
} }
void _clearCache() { void _clearCache() {
_cachedPath = null; _cachedUri = null;
_savePath(null); _saveUri(null);
} }
Future<void> _savePath(String? path) async { Future<void> _saveUri(String? uri) async {
try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (path != null && path.isNotEmpty) { if (uri != null && uri.isNotEmpty) {
await prefs.setString(_prefKeySafPath, path); await prefs.setString(_prefKeySafUri, uri);
if (kDebugMode) {
print('[SAF] 保存 URI 成功: $uri');
}
} else { } else {
await prefs.remove(_prefKeySafPath); await prefs.remove(_prefKeySafUri);
if (kDebugMode) {
print('[SAF] 清除 URI');
}
}
} catch (e) {
if (kDebugMode) {
print('[SAF] 保存 URI 失败: $e');
}
} }
} }
String? get cachedPath => _cachedPath; String? get cachedUri => _cachedUri;
} }
-16
View File
@@ -113,14 +113,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.2.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -134,14 +126,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.0" version: "5.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.35"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
+2 -2
View File
@@ -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.2 version: 1.3.0-dev.4
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2
@@ -38,7 +38,7 @@ dependencies:
path: ^1.9.0 path: ^1.9.0
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
shared_preferences: ^2.2.0 shared_preferences: ^2.2.0
file_picker: ^6.0.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.