mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
Save preferences to same directory to binary file on Windows
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "utils"]
|
||||||
|
path = utils
|
||||||
|
url = https://github.com/lifegpc/c-utils
|
||||||
11
lib/config/base.dart
Normal file
11
lib/config/base.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
abstract interface class Config {
|
||||||
|
Future<bool> clear();
|
||||||
|
bool containsKey(String key);
|
||||||
|
Object? get(String key);
|
||||||
|
String? getString(String key);
|
||||||
|
Future<bool> setString(String key, String value);
|
||||||
|
int? getInt(String key);
|
||||||
|
Future<bool> setInt(String key, int value);
|
||||||
|
bool? getBool(String key);
|
||||||
|
Future<bool> setBool(String key, bool value);
|
||||||
|
}
|
||||||
43
lib/config/shared_preferences.dart
Normal file
43
lib/config/shared_preferences.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import 'base.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class SharedPreferencesConfig implements Config {
|
||||||
|
SharedPreferencesConfig(this._prefs);
|
||||||
|
final SharedPreferences _prefs;
|
||||||
|
@override
|
||||||
|
Future<bool> clear() {
|
||||||
|
return _prefs.clear();
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
bool containsKey(String key) {
|
||||||
|
return _prefs.containsKey(key);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Object? get(String key) {
|
||||||
|
return _prefs.get(key);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
String? getString(String key) {
|
||||||
|
return _prefs.getString(key);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Future<bool> setString(String key, String value) {
|
||||||
|
return _prefs.setString(key, value);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
int? getInt(String key) {
|
||||||
|
return _prefs.getInt(key);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Future<bool> setInt(String key, int value) {
|
||||||
|
return _prefs.setInt(key, value);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
bool? getBool(String key) {
|
||||||
|
return _prefs.getBool(key);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Future<bool> setBool(String key, bool value) {
|
||||||
|
return _prefs.setBool(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
108
lib/config/windows.dart
Normal file
108
lib/config/windows.dart
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import 'dart:convert' show json;
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/local.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'base.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
|
||||||
|
final _log = Logger("WindowsConfig");
|
||||||
|
|
||||||
|
class WindowsConfig implements Config {
|
||||||
|
WindowsConfig();
|
||||||
|
FileSystem fs = const LocalFileSystem();
|
||||||
|
Map<String, Object>? _cachedPreferences;
|
||||||
|
File? _filePath;
|
||||||
|
Future<File?> filePath() async {
|
||||||
|
if (_filePath != null) return _filePath;
|
||||||
|
final String? exe = await platformPath.getCurrentExe();
|
||||||
|
if (exe == null) return null;
|
||||||
|
final dir = path.dirname(exe);
|
||||||
|
final name = path.basenameWithoutExtension(exe);
|
||||||
|
return _filePath = fs.file(path.join(dir, "$name.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, Object>> reload() async {
|
||||||
|
Map<String, Object> preferences = <String, Object>{};
|
||||||
|
final File? localDataFile = await filePath();
|
||||||
|
if (localDataFile != null && localDataFile.existsSync()) {
|
||||||
|
final String stringMap = localDataFile.readAsStringSync();
|
||||||
|
if (stringMap.isNotEmpty) {
|
||||||
|
final Object? data = json.decode(stringMap);
|
||||||
|
if (data is Map) {
|
||||||
|
preferences = data.cast<String, Object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_cachedPreferences = preferences;
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, Object>> _readPreferences() async {
|
||||||
|
return _cachedPreferences ?? await reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _writePreferences(Map<String, Object> preferences) async {
|
||||||
|
try {
|
||||||
|
final File? localDataFile = await filePath();
|
||||||
|
if (localDataFile == null) {
|
||||||
|
_log.warning('Unable to determine where to write preferences.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!localDataFile.existsSync()) {
|
||||||
|
localDataFile.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
final String stringMap = json.encode(preferences);
|
||||||
|
localDataFile.writeAsStringSync(stringMap);
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe('Error saving preferences to disk: ', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> clear() async {
|
||||||
|
final Map<String, Object> preferences = await _readPreferences();
|
||||||
|
preferences.clear();
|
||||||
|
return _writePreferences(preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> setValue(String key, Object value) async {
|
||||||
|
final Map<String, Object> preferences = await _readPreferences();
|
||||||
|
preferences[key] = value;
|
||||||
|
return _writePreferences(preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool containsKey(String key) {
|
||||||
|
return _cachedPreferences?.containsKey(key) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? get(String key) => _cachedPreferences?[key];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? getString(String key) => _cachedPreferences?[key] as String?;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> setString(String key, String value) {
|
||||||
|
return setValue(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int? getInt(String key) => _cachedPreferences?[key] as int?;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> setInt(String key, int value) {
|
||||||
|
return setValue(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? getBool(String key) => _cachedPreferences?[key] as bool?;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> setBool(String key, bool value) {
|
||||||
|
return setValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,13 +9,18 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'api/client.dart';
|
import 'api/client.dart';
|
||||||
import 'auth.dart';
|
import 'auth.dart';
|
||||||
|
import 'config/base.dart';
|
||||||
|
import 'config/shared_preferences.dart';
|
||||||
|
import 'config/windows.dart';
|
||||||
|
import 'platform/path.dart';
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
final dio = Dio()
|
final dio = Dio()
|
||||||
..options.validateStatus = (int? _) {
|
..options.validateStatus = (int? _) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
..options.extra['withCredentials'] = true;
|
..options.extra['withCredentials'] = true;
|
||||||
SharedPreferences? _prefs;
|
Config? _prefs;
|
||||||
EHApi? _api;
|
EHApi? _api;
|
||||||
|
|
||||||
Future<void> prepareJar() async {
|
Future<void> prepareJar() async {
|
||||||
@@ -28,10 +33,20 @@ Future<void> prepareJar() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> preparePrefs() async {
|
Future<void> preparePrefs() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
if (isWindows) {
|
||||||
|
try {
|
||||||
|
var tmp = WindowsConfig();
|
||||||
|
tmp.reload();
|
||||||
|
_prefs = tmp;
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_prefs = SharedPreferencesConfig(await SharedPreferences.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences get prefs {
|
Config get prefs {
|
||||||
if (_prefs == null) {
|
if (_prefs == null) {
|
||||||
throw Exception('SharedPreferences not initialized');
|
throw Exception('SharedPreferences not initialized');
|
||||||
}
|
}
|
||||||
@@ -77,3 +92,4 @@ EHApi get api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final AuthInfo auth = AuthInfo();
|
final AuthInfo auth = AuthInfo();
|
||||||
|
final Path platformPath = Path();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ Future<void> initLogger() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
if (!kIsWeb) WidgetsFlutterBinding.ensureInitialized();
|
||||||
bool? usePathUrl = const bool.fromEnvironment("usePathUrl");
|
bool? usePathUrl = const bool.fromEnvironment("usePathUrl");
|
||||||
if (usePathUrl == true && kIsWeb) {
|
if (usePathUrl == true && kIsWeb) {
|
||||||
usePathUrlStrategy();
|
usePathUrlStrategy();
|
||||||
@@ -49,7 +50,6 @@ void main() async {
|
|||||||
if (!kIsWeb) await prepareJar();
|
if (!kIsWeb) await prepareJar();
|
||||||
await preparePrefs();
|
await preparePrefs();
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
}
|
}
|
||||||
await initLogger();
|
await initLogger();
|
||||||
|
|||||||
28
lib/platform/path.dart
Normal file
28
lib/platform/path.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import '../utils.dart';
|
||||||
|
|
||||||
|
final Logger _log = Logger("platformPath");
|
||||||
|
|
||||||
|
class Path {
|
||||||
|
static const platform = MethodChannel("lifegpc.eh_downloader_flutter/path");
|
||||||
|
String? _currentExe;
|
||||||
|
bool _currentExeLoaded = false;
|
||||||
|
|
||||||
|
String? get currentExe => _currentExe;
|
||||||
|
|
||||||
|
Future<String?> getCurrentExe() async {
|
||||||
|
if (_currentExeLoaded) return _currentExe;
|
||||||
|
try {
|
||||||
|
final String result = await platform.invokeMethod("getCurrentExe");
|
||||||
|
_currentExe = result;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
if (isWindows) {
|
||||||
|
_log.warning("Failed to get current exe path:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentExeLoaded = true;
|
||||||
|
return _currentExe;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -234,7 +234,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||||
@@ -438,7 +438,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ dependencies:
|
|||||||
cryptography_flutter: ^2.3.0
|
cryptography_flutter: ^2.3.0
|
||||||
dio: ^5.3.2
|
dio: ^5.3.2
|
||||||
dio_cookie_manager: ^3.1.0+1
|
dio_cookie_manager: ^3.1.0+1
|
||||||
|
file: ^6.1.4
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_hooks: ^0.20.0
|
flutter_hooks: ^0.20.0
|
||||||
@@ -21,6 +22,7 @@ dependencies:
|
|||||||
intl: any
|
intl: any
|
||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
logging: ^1.2.0
|
logging: ^1.2.0
|
||||||
|
path: ^1.8.3
|
||||||
path_provider: ^2.1.0
|
path_provider: ^2.1.0
|
||||||
retrofit: ^4.0.1
|
retrofit: ^4.0.1
|
||||||
shared_preferences: ^2.2.0
|
shared_preferences: ^2.2.0
|
||||||
|
|||||||
1
utils
Submodule
1
utils
Submodule
Submodule utils added at 63992e1238
@@ -1,6 +1,10 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(runner LANGUAGES CXX)
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.")
|
||||||
|
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../utils" "${CMAKE_BINARY_DIR}/utils")
|
||||||
|
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../utils")
|
||||||
|
|
||||||
# Define the application target. To change its name, change BINARY_NAME in the
|
# Define the application target. To change its name, change BINARY_NAME in the
|
||||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||||
# work.
|
# work.
|
||||||
@@ -34,6 +38,7 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
|||||||
# dependencies here.
|
# dependencies here.
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
|
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE utils)
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
|
||||||
# Run the Flutter tool portions of the build. This must not be removed.
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
|
#include <flutter/event_channel.h>
|
||||||
|
#include <flutter/event_sink.h>
|
||||||
|
#include <flutter/event_stream_handler_functions.h>
|
||||||
|
#include <flutter/method_channel.h>
|
||||||
|
#include <flutter/standard_method_codec.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include "wchar_util.h"
|
||||||
|
|
||||||
|
#define MAX_PATH_SIZE 32768
|
||||||
|
|
||||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||||
: project_(project) {}
|
: project_(project) {}
|
||||||
|
|
||||||
@@ -25,6 +35,30 @@ bool FlutterWindow::OnCreate() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RegisterPlugins(flutter_controller_->engine());
|
RegisterPlugins(flutter_controller_->engine());
|
||||||
|
|
||||||
|
flutter::MethodChannel<> channel(
|
||||||
|
flutter_controller_->engine()->messenger(), "lifegpc.eh_downloader_flutter/path",
|
||||||
|
&flutter::StandardMethodCodec::GetInstance());
|
||||||
|
channel.SetMethodCallHandler(
|
||||||
|
[](const flutter::MethodCall<>& call,
|
||||||
|
std::unique_ptr<flutter::MethodResult<>> result) {
|
||||||
|
if (call.method_name() == "getCurrentExe") {
|
||||||
|
std::string current;
|
||||||
|
wchar_t tmp[MAX_PATH_SIZE];
|
||||||
|
if (!GetModuleFileNameW(nullptr, tmp, MAX_PATH_SIZE)) {
|
||||||
|
result->Error("UNAVAILABLE", "Failed to get module file name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!wchar_util::wstr_to_str(current, tmp, CP_UTF8)) {
|
||||||
|
result->Error("UNAVAILABLE", "Failed to convert module file name to UTF-8.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result->Success(current);
|
||||||
|
} else {
|
||||||
|
result->NotImplemented();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||||
|
|
||||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||||
|
|||||||
Reference in New Issue
Block a user