Compare commits
2 Commits
055b9aecce
..
1.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b34b6c8b6 | |||
| f8ffb32bb0 |
@@ -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
|
||||||
|
|||||||
@@ -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>
|
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
|
||||||
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>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -885,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,相信缓存
|
// 直接返回 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