初始化提交
This commit is contained in:
Executable
+62
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
|
||||
/// AES加密解密工具
|
||||
class AESUtils {
|
||||
// Encrypt function: Takes plaintext and key, returns base64 ciphertext and nonce
|
||||
Map<String, String> encryptData(String plainText, String keyStr) {
|
||||
// Generate timestamp as nonce
|
||||
String nonce = DateTime.now().toIso8601String();
|
||||
|
||||
// Generate key and IV
|
||||
final key = generateKey(keyStr);
|
||||
final iv = generateIv(nonce, keyStr);
|
||||
|
||||
// Encrypt the data
|
||||
final encrypter = encrypt.Encrypter(
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: 'PKCS7'));
|
||||
final encrypted = encrypter.encrypt(plainText, iv: iv);
|
||||
|
||||
return {
|
||||
'data': encrypted.base64,
|
||||
'time': nonce,
|
||||
};
|
||||
}
|
||||
|
||||
// Decrypt function: Takes base64 ciphertext, key, and nonce, returns plaintext
|
||||
String decryptData(String cipherText, String keyStr, String nonce) {
|
||||
// Generate key and IV
|
||||
final key = generateKey(keyStr);
|
||||
final iv = generateIv(nonce, keyStr);
|
||||
|
||||
// Decrypt the data
|
||||
final encrypter = encrypt.Encrypter(
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: 'PKCS7'));
|
||||
final decrypted = encrypter.decrypt64(cipherText, iv: iv);
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
// Generate AES key from input key (32 bytes for AES-256)
|
||||
encrypt.Key generateKey(String keyStr) {
|
||||
final keyBytes = sha256.convert(utf8.encode(keyStr)).bytes;
|
||||
return encrypt.Key(Uint8List.fromList(keyBytes));
|
||||
}
|
||||
|
||||
// Generate IV (Initialization Vector) from nonce and key
|
||||
encrypt.IV generateIv(String ivStr, String keyStr) {
|
||||
final md5Hash = md5.convert(utf8.encode(ivStr)).bytes;
|
||||
final combinedKey = hexEncode(md5Hash) + keyStr;
|
||||
final finalHash = sha256.convert(utf8.encode(combinedKey)).bytes;
|
||||
|
||||
return encrypt.IV(Uint8List.fromList(
|
||||
finalHash.sublist(0, 16))); // AES-CBC IV must be 16 bytes
|
||||
}
|
||||
|
||||
// Helper function to encode bytes to hex
|
||||
String hexEncode(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||||
}
|
||||
}
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../widgets/kr_toast.dart';
|
||||
|
||||
class KRCommonUtil {
|
||||
/// 提示 meesage: 提示内容, toastPosition: 提示显示的位置, timeout: 显示时间(毫秒)
|
||||
static kr_showToast(String message,
|
||||
{KRToastPosition toastPosition = KRToastPosition.center,
|
||||
int timeout = 1500}) {
|
||||
KRToast.kr_showToast(
|
||||
message,
|
||||
position: toastPosition,
|
||||
duration: Duration(milliseconds: timeout),
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示加载动画
|
||||
static kr_showLoading({String? message}) {
|
||||
KRToast.kr_showLoading(message: message);
|
||||
}
|
||||
|
||||
/// 隐藏加载动画
|
||||
static kr_hideLoading() {
|
||||
KRToast.kr_hideLoading();
|
||||
}
|
||||
|
||||
/// 格式化字节数为可读字符串
|
||||
/// [bytes] 字节数
|
||||
static String kr_formatBytes(int bytes) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
|
||||
const List<String> suffixes = ["B", "KB", "MB", "GB", "TB"];
|
||||
int i = 0;
|
||||
double value = bytes.toDouble();
|
||||
|
||||
while (value >= 1024 && i < suffixes.length - 1) {
|
||||
value /= 1024;
|
||||
i++;
|
||||
}
|
||||
|
||||
return "${value.toStringAsFixed(2)} ${suffixes[i]}";
|
||||
}
|
||||
}
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
|
||||
import '../common/app_config.dart';
|
||||
|
||||
/// 支持的国家枚举
|
||||
enum KRCountry {
|
||||
cn('中国'),
|
||||
ir('伊朗'),
|
||||
af('阿富汗'),
|
||||
ru('俄罗斯'),
|
||||
id('印度尼西亚'),
|
||||
tr('土耳其'),
|
||||
br('巴西');
|
||||
|
||||
final String kr_name;
|
||||
const KRCountry(this.kr_name);
|
||||
|
||||
/// 获取国家代码
|
||||
String get kr_code => name.toLowerCase();
|
||||
|
||||
/// 获取国家名称
|
||||
String get kr_countryName => kr_name;
|
||||
|
||||
/// 从代码获取枚举值
|
||||
static KRCountry? kr_fromCode(String code) {
|
||||
try {
|
||||
return KRCountry.values.firstWhere(
|
||||
(country) => country.kr_code == code.toLowerCase(),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 国家工具类
|
||||
class KRCountryUtil {
|
||||
/// 存储键
|
||||
static const String _kr_countryKey = 'kr_current_country';
|
||||
|
||||
/// 当前选择的国家
|
||||
static final Rx<KRCountry> kr_currentCountry = KRCountry.cn.obs;
|
||||
|
||||
/// 存储实例
|
||||
static final KRSecureStorage _kr_storage = KRSecureStorage();
|
||||
|
||||
/// 初始化
|
||||
static Future<void> kr_init() async {
|
||||
try {
|
||||
final String? kr_savedCountry =
|
||||
await _kr_storage.kr_readData(key: _kr_countryKey);
|
||||
if (kr_savedCountry != null) {
|
||||
final KRCountry? kr_country = KRCountry.kr_fromCode(kr_savedCountry);
|
||||
if (kr_country != null) {
|
||||
kr_currentCountry.value = kr_country;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (AppConfig().kr_is_daytime == false) {
|
||||
kr_currentCountry.value = KRCountry.ru;
|
||||
} else {
|
||||
kr_currentCountry.value = KRCountry.cn;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
KRLogUtil.kr_e('初始化国家设置失败: $err', tag: 'CountryUtil');
|
||||
kr_currentCountry.value = KRCountry.cn;
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置当前国家
|
||||
static Future<void> kr_setCurrentCountry(KRCountry kr_country) async {
|
||||
try {
|
||||
await _kr_storage.kr_saveData(
|
||||
key: _kr_countryKey, value: kr_country.kr_code);
|
||||
kr_currentCountry.value = kr_country;
|
||||
} catch (err) {
|
||||
KRLogUtil.kr_e('设置国家失败: $err', tag: 'CountryUtil');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新国家并重启服务
|
||||
static Future<void> kr_updateCountry(KRCountry kr_country) async {
|
||||
try {
|
||||
// 更新国家设置
|
||||
await kr_setCurrentCountry(kr_country);
|
||||
|
||||
// 如果服务正在运行,重启服务
|
||||
if (KRSingBoxImp.instance.kr_status.value == SingboxStarted()) {
|
||||
await KRSingBoxImp.instance.kr_restart();
|
||||
}
|
||||
} catch (err) {
|
||||
KRLogUtil.kr_e('更新国家失败: $err', tag: 'CountryUtil');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前国家代码
|
||||
static String kr_getCurrentCountryCode() {
|
||||
return kr_currentCountry.value.kr_code;
|
||||
}
|
||||
|
||||
static String kr_getCurrentCountryName() {
|
||||
return kr_getCountryName(kr_currentCountry.value);
|
||||
|
||||
}
|
||||
|
||||
/// 获取国家名称
|
||||
static String kr_getCountryName(KRCountry country) {
|
||||
switch (country) {
|
||||
case KRCountry.ir:
|
||||
return AppTranslations.kr_country.ir;
|
||||
case KRCountry.cn:
|
||||
return AppTranslations.kr_country.cn;
|
||||
|
||||
case KRCountry.af:
|
||||
return AppTranslations.kr_country.af;
|
||||
|
||||
case KRCountry.ru:
|
||||
return AppTranslations.kr_country.ru;
|
||||
|
||||
case KRCountry.id:
|
||||
return AppTranslations.kr_country.id;
|
||||
case KRCountry.tr:
|
||||
return AppTranslations.kr_country.tr;
|
||||
case KRCountry.br:
|
||||
return AppTranslations.kr_country.br;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有支持的国家列表
|
||||
static List<KRCountry> kr_getSupportedCountries() {
|
||||
if (AppConfig().kr_is_daytime == false) {
|
||||
return KRCountry.values.where((element) => element != KRCountry.cn).toList();
|
||||
}
|
||||
return KRCountry.values;
|
||||
}
|
||||
|
||||
/// 获取国家信息列表
|
||||
static List<Map<String, String>> kr_getCountryInfoList() {
|
||||
return KRCountry.values
|
||||
.map((country) => {
|
||||
'code': country.kr_code,
|
||||
'name': country.kr_countryName,
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter_udid/flutter_udid.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
/// 设备工具类
|
||||
class KRDeviceUtil {
|
||||
static final KRDeviceUtil _instance = KRDeviceUtil._internal();
|
||||
|
||||
/// 设备ID缓存
|
||||
String? _kr_cachedDeviceId;
|
||||
|
||||
/// 存储键
|
||||
static const String _kr_deviceIdKey = 'kr_device_id';
|
||||
|
||||
/// 存储实例
|
||||
final KRSecureStorage _kr_storage = KRSecureStorage();
|
||||
|
||||
KRDeviceUtil._internal();
|
||||
|
||||
factory KRDeviceUtil() => _instance;
|
||||
|
||||
/// 获取设备ID
|
||||
/// 如果获取失败,返回空字符串
|
||||
Future<String> kr_getDeviceId() async {
|
||||
|
||||
try {
|
||||
if (_kr_cachedDeviceId != null) {
|
||||
return _kr_cachedDeviceId!;
|
||||
}
|
||||
|
||||
// 先从存储中获取
|
||||
final String? kr_savedDeviceId = await _kr_storage.kr_readData(key: _kr_deviceIdKey);
|
||||
if (kr_savedDeviceId != null) {
|
||||
_kr_cachedDeviceId = kr_savedDeviceId;
|
||||
return _kr_cachedDeviceId!;
|
||||
}
|
||||
|
||||
// 根据不同平台获取设备ID
|
||||
if (Platform.isMacOS || Platform.isWindows ) {
|
||||
// 获取系统信息
|
||||
final PackageInfo kr_packageInfo = await PackageInfo.fromPlatform();
|
||||
final String kr_platform = Platform.operatingSystem;
|
||||
final String kr_version = Platform.operatingSystemVersion;
|
||||
final String kr_localHostname = Platform.localHostname;
|
||||
|
||||
// 组合信息生成唯一ID
|
||||
final String kr_deviceInfo = '$kr_platform-$kr_version-$kr_localHostname-${kr_packageInfo.packageName}-${kr_packageInfo.buildNumber}';
|
||||
_kr_cachedDeviceId = md5.convert(utf8.encode(kr_deviceInfo)).toString();
|
||||
} else if (Platform.isIOS || Platform.isAndroid ) {
|
||||
|
||||
_kr_cachedDeviceId = await FlutterUdid.udid;
|
||||
} else {
|
||||
KRLogUtil.kr_e('不支持的平台: ${Platform.operatingSystem}', tag: 'DeviceUtil');
|
||||
return '';
|
||||
}
|
||||
|
||||
// 保存到存储中
|
||||
if (_kr_cachedDeviceId != null) {
|
||||
await _kr_storage.kr_saveData(key: _kr_deviceIdKey, value: _kr_cachedDeviceId!);
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('获取设备ID: $_kr_cachedDeviceId', tag: 'DeviceUtil');
|
||||
return _kr_cachedDeviceId ?? '';
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('获取设备ID失败: $e', tag: 'DeviceUtil');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除缓存的设备ID
|
||||
void kr_clearDeviceId() {
|
||||
_kr_cachedDeviceId = null;
|
||||
_kr_storage.kr_deleteData(key: _kr_deviceIdKey);
|
||||
}
|
||||
}
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
|
||||
/// 消息类型枚举
|
||||
enum KRMessageType {
|
||||
kr_payment,
|
||||
kr_subscribe_update,
|
||||
|
||||
}
|
||||
|
||||
/// 消息数据类
|
||||
class KRMessageData {
|
||||
final KRMessageType kr_type; // 消息类型
|
||||
final Map<String, dynamic> kr_data; // 消息数据
|
||||
|
||||
KRMessageData({
|
||||
required this.kr_type,
|
||||
required this.kr_data,
|
||||
});
|
||||
}
|
||||
|
||||
/// 消息总线工具类
|
||||
class KREventBus {
|
||||
// 单例模式
|
||||
static final KREventBus _instance = KREventBus._internal();
|
||||
factory KREventBus() => _instance;
|
||||
KREventBus._internal();
|
||||
|
||||
// 消息流控制器
|
||||
final _kr_messageController = Rx<KRMessageData?>(null);
|
||||
|
||||
/// 发送消息
|
||||
void kr_sendMessage(
|
||||
KRMessageType type, {
|
||||
Map<String, dynamic> data = const {},
|
||||
}) {
|
||||
_kr_messageController.value = KRMessageData(
|
||||
kr_type: type,
|
||||
kr_data: data,
|
||||
);
|
||||
}
|
||||
|
||||
/// 监听特定类型的消息
|
||||
/// 返回 Worker 对象,可用于手动取消订阅
|
||||
/// 在控制器的 onClose 方法中调用 worker.dispose() 来取消订阅
|
||||
/// 示例:
|
||||
/// ```dart
|
||||
/// class MyController extends GetxController {
|
||||
/// Worker? _worker;
|
||||
///
|
||||
/// @override
|
||||
/// void onInit() {
|
||||
/// super.onInit();
|
||||
/// _worker = KREventBus().kr_listenMessage(
|
||||
/// KRMessageType.kr_paymentSuccess,
|
||||
/// (message) => KRLogUtil.kr_d('收到消息: $message', tag: 'EventBus'),
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
/// @override
|
||||
/// void onClose() {
|
||||
/// _worker?.dispose();
|
||||
/// super.onClose();
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
Worker kr_listenMessage(
|
||||
KRMessageType type,
|
||||
Function(KRMessageData) callback,
|
||||
) {
|
||||
return ever(_kr_messageController, (KRMessageData? message) {
|
||||
if (message != null && message.kr_type == type) {
|
||||
callback(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 监听多个类型的消息
|
||||
/// 返回 Worker 对象,可用于手动取消订阅
|
||||
/// 在控制器的 onClose 方法中调用 worker.dispose() 来取消订阅
|
||||
Worker kr_listenMessages(
|
||||
List<KRMessageType> types,
|
||||
Function(KRMessageData) callback,
|
||||
) {
|
||||
return ever(_kr_messageController, (KRMessageData? message) {
|
||||
if (message != null && types.contains(message.kr_type)) {
|
||||
callback(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 监听所有消息
|
||||
/// 返回 Worker 对象,可用于手动取消订阅
|
||||
/// 在控制器的 onClose 方法中调用 worker.dispose() 来取消订阅
|
||||
Worker kr_listenAllMessages(Function(KRMessageData) callback) {
|
||||
return ever(_kr_messageController, (KRMessageData? message) {
|
||||
if (message != null) {
|
||||
callback(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
// import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
/// 地图瓦片缓存工具类
|
||||
class KRFMTC {
|
||||
static const String kr_storeName = 'kaer_map_store';
|
||||
|
||||
/// HTTP 客户端,用于复用连接
|
||||
static late final http.Client _httpClient = http.Client();
|
||||
|
||||
/// 瓦片提供者实例
|
||||
static late final TileProvider _tileProvider = NetworkTileProvider();
|
||||
|
||||
/// 判断是否在中国大陆
|
||||
static bool _kr_isInMainlandChina() {
|
||||
// 获取系统语言
|
||||
final List<Locale> systemLocales = ui.window.locales;
|
||||
final String? languageCode = systemLocales.isNotEmpty ? systemLocales.first.languageCode : null;
|
||||
final String? countryCode = systemLocales.isNotEmpty ? systemLocales.first.countryCode : null;
|
||||
|
||||
// 获取系统时区
|
||||
final String timeZoneName = DateTime.now().timeZoneName;
|
||||
|
||||
// 检查是否为中文语言环境
|
||||
bool isChineseLanguage = languageCode == 'zh';
|
||||
// 检查是否为中国地区代码
|
||||
bool isChineseRegion = countryCode == 'CN';
|
||||
// 检查是否为中国时区
|
||||
bool isChineseTimezone = timeZoneName.contains('China') || timeZoneName == 'Asia/Shanghai';
|
||||
|
||||
// 如果同时满足语言和地区或时区条件,则认为在中国大陆
|
||||
return (isChineseLanguage && (isChineseRegion || isChineseTimezone));
|
||||
}
|
||||
|
||||
/// 获取地图瓦片URL
|
||||
static String kr_getTileUrl() {
|
||||
if (_kr_isInMainlandChina()) {
|
||||
// 使用高德地图
|
||||
return 'https://webst0{s}.is.autonavi.com/appmaptile?style=7&x={x}&y={y}&z={z}';
|
||||
} else {
|
||||
// 使用 OpenStreetMap
|
||||
return 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取地图瓦片子域名
|
||||
static List<String> kr_getTileSubdomains() {
|
||||
if (_kr_isInMainlandChina()) {
|
||||
return ['1', '2', '3', '4'];
|
||||
} else {
|
||||
return ['a', 'b', 'c'];
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取地图瓦片提供者
|
||||
static TileProvider kr_getTileProvider() {
|
||||
return _tileProvider;
|
||||
}
|
||||
|
||||
/// 初始化地图缓存
|
||||
static Future<void> kr_initMapCache() async {
|
||||
// 不使用缓存,无需实现
|
||||
}
|
||||
|
||||
/// 清理地图缓存
|
||||
static Future<void> kr_clearMapCache() async {
|
||||
// 不使用缓存,无需实现
|
||||
}
|
||||
|
||||
/// 检查瓦片是否已缓存
|
||||
static Future<bool> kr_isTileCached({
|
||||
required TileCoordinates coords,
|
||||
required TileLayer options,
|
||||
}) async {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 获取缓存状态信息
|
||||
static Future<({bool isAvailable, int cacheCount, String cacheSize})> kr_checkCacheStatus() async {
|
||||
return (isAvailable: false, cacheCount: 0, cacheSize: '0 MB');
|
||||
}
|
||||
|
||||
/// 释放资源
|
||||
static void kr_dispose() {
|
||||
_httpClient.close();
|
||||
}
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
import 'package:loggy/loggy.dart';
|
||||
|
||||
/// 日志工具类
|
||||
class KRLogUtil {
|
||||
static final KRLogUtil _instance = KRLogUtil._internal();
|
||||
factory KRLogUtil() => _instance;
|
||||
KRLogUtil._internal();
|
||||
|
||||
/// 初始化日志
|
||||
static void kr_init() {
|
||||
Loggy.initLoggy(
|
||||
logPrinter: PrettyPrinter(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 调试日志
|
||||
static void kr_d(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').debug(message);
|
||||
}
|
||||
|
||||
/// 信息日志
|
||||
static void kr_i(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').info(message);
|
||||
}
|
||||
|
||||
/// 警告日志
|
||||
static void kr_w(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').warning(message);
|
||||
}
|
||||
|
||||
/// 错误日志
|
||||
static void kr_e(String message, {String? tag, Object? error, StackTrace? stackTrace}) {
|
||||
Loggy('${tag ?? 'KRLogUtil'}').error(message, error, stackTrace);
|
||||
}
|
||||
|
||||
/// 网络日志
|
||||
static void kr_network(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'Network'}').info(message);
|
||||
}
|
||||
|
||||
/// 性能日志
|
||||
static void kr_performance(String message, {String? tag}) {
|
||||
Loggy('${tag ?? 'Performance'}').info(message);
|
||||
}
|
||||
}
|
||||
Executable
+172
@@ -0,0 +1,172 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import '../widgets/dialogs/kr_dialog.dart';
|
||||
import '../localization/app_translations.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:io' show InternetAddress;
|
||||
|
||||
/// 网络检查工具类
|
||||
class KRNetworkCheck {
|
||||
/// 初始化网络检查
|
||||
static Future<bool> kr_initialize(BuildContext context, {VoidCallback? onPermissionGranted}) async {
|
||||
try {
|
||||
if (Platform.isIOS) {
|
||||
// iOS 平台特殊处理
|
||||
final hasPermission = await kr_checkNetworkPermission();
|
||||
if (!hasPermission) {
|
||||
// 显示网络设置提示对话框
|
||||
final bool? result = await kr_showNetworkDialog();
|
||||
if (result == true) {
|
||||
// 打开系统设置
|
||||
await kr_openAppSettings();
|
||||
|
||||
// 等待用户返回并检查权限
|
||||
bool hasPermissionAfterSettings = false;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
hasPermissionAfterSettings = await kr_checkNetworkPermission();
|
||||
if (hasPermissionAfterSettings) {
|
||||
onPermissionGranted?.call();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermissionAfterSettings) {
|
||||
await kr_showNetworkDialog();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Android 平台处理
|
||||
final hasPermission = await kr_checkNetworkPermission();
|
||||
if (!hasPermission) {
|
||||
final bool? result = await kr_showNetworkDialog();
|
||||
if (result == true) {
|
||||
await kr_openAppSettings();
|
||||
|
||||
bool hasPermissionAfterSettings = false;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
hasPermissionAfterSettings = await kr_checkNetworkPermission();
|
||||
if (hasPermissionAfterSettings) {
|
||||
onPermissionGranted?.call();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermissionAfterSettings) {
|
||||
await kr_showNetworkDialog();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onPermissionGranted?.call();
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('网络检查初始化错误: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查网络权限
|
||||
static Future<bool> kr_checkNetworkPermission() async {
|
||||
try {
|
||||
if (Platform.isIOS) {
|
||||
// iOS 平台使用更可靠的方式检查网络状态
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试进行实际的网络请求测试
|
||||
try {
|
||||
final result = await InternetAddress.lookup('www.apple.com');
|
||||
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Android 平台保持原有逻辑
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('网络权限检查错误: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查网络连接状态
|
||||
static Future<bool> kr_checkNetworkConnection() async {
|
||||
try {
|
||||
if (Platform.isIOS) {
|
||||
// iOS 平台使用更可靠的方式检查网络状态
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final result = await InternetAddress.lookup('www.apple.com');
|
||||
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('网络连接检查错误: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 监听网络状态变化
|
||||
static Stream<ConnectivityResult> kr_networkStream() {
|
||||
return Connectivity().onConnectivityChanged;
|
||||
}
|
||||
|
||||
/// 打开应用设置页面
|
||||
static Future<void> kr_openAppSettings() async {
|
||||
if (Platform.isIOS) {
|
||||
// iOS 平台打开网络设置
|
||||
try {
|
||||
await openAppSettings();
|
||||
} catch (e) {
|
||||
debugPrint('打开设置页面错误: $e');
|
||||
}
|
||||
} else {
|
||||
await openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示网络权限对话框
|
||||
static Future<bool> kr_showNetworkDialog() async {
|
||||
bool? result;
|
||||
await KRDialog.show(
|
||||
title: Platform.isIOS
|
||||
? AppTranslations.kr_networkPermission.title
|
||||
: AppTranslations.kr_networkPermission.title,
|
||||
message: Platform.isIOS
|
||||
? AppTranslations.kr_networkPermission.description
|
||||
: AppTranslations.kr_networkPermission.description,
|
||||
confirmText: AppTranslations.kr_networkPermission.goToSettings,
|
||||
cancelText: AppTranslations.kr_networkPermission.cancel,
|
||||
onConfirm: () async {
|
||||
result = await openAppSettings();
|
||||
},
|
||||
onCancel: () {
|
||||
result = false;
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
}
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
/// 网络权限工具类
|
||||
class KRNetworkPermission {
|
||||
/// 检查网络权限
|
||||
static Future<bool> kr_checkNetworkPermission() async {
|
||||
if (GetPlatform.isIOS) {
|
||||
// iOS 检查网络权限
|
||||
final status = await Permission.location.status;
|
||||
if (!status.isGranted) {
|
||||
return false;
|
||||
}
|
||||
} else if (GetPlatform.isAndroid) {
|
||||
// Android 检查网络权限
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 请求网络权限
|
||||
static Future<bool> kr_requestNetworkPermission() async {
|
||||
if (GetPlatform.isIOS) {
|
||||
// iOS 请求网络权限
|
||||
final status = await Permission.location.request();
|
||||
if (!status.isGranted) {
|
||||
// 直接打开设置页面
|
||||
await openAppSettings();
|
||||
}
|
||||
return status.isGranted;
|
||||
} else if (GetPlatform.isAndroid) {
|
||||
// Android 请求网络权限
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
// 直接打开设置页面
|
||||
await openAppSettings();
|
||||
}
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 检查网络连接状态
|
||||
static Future<bool> kr_checkNetworkConnection() async {
|
||||
try {
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 监听网络状态变化
|
||||
static Stream<ConnectivityResult> kr_networkStream() {
|
||||
return Connectivity().onConnectivityChanged;
|
||||
}
|
||||
}
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import '../widgets/dialogs/kr_dialog.dart';
|
||||
import '../localization/app_translations.dart';
|
||||
|
||||
class KRNetworkStatus {
|
||||
static Future<bool> checkNetworkStatus() async {
|
||||
final connectivityResult = await Connectivity().checkConnectivity();
|
||||
if (connectivityResult != ConnectivityResult.none) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool? result;
|
||||
await KRDialog.show(
|
||||
title: AppTranslations.kr_networkStatus.title,
|
||||
message: AppTranslations.kr_networkStatus.checkNetwork,
|
||||
confirmText: AppTranslations.kr_networkStatus.retry,
|
||||
cancelText: AppTranslations.kr_networkStatus.cancel,
|
||||
onConfirm: () => result = true,
|
||||
onCancel: () => result = false,
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
return checkNetworkStatus();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Executable
+134
@@ -0,0 +1,134 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class KRSecureStorage {
|
||||
// 创建一个单例实例
|
||||
static final KRSecureStorage _instance = KRSecureStorage._internal();
|
||||
factory KRSecureStorage() => _instance;
|
||||
|
||||
// 私有构造函数
|
||||
KRSecureStorage._internal();
|
||||
|
||||
// 存储箱名称
|
||||
static const String _boxName = 'kaer_secure_storage';
|
||||
|
||||
// 加密密钥
|
||||
static const String _encryptionKey = 'kaer_secure_storage_key';
|
||||
|
||||
// 初始化 Hive
|
||||
Future<void> kr_initHive() async {
|
||||
try {
|
||||
if (Platform.isMacOS) {
|
||||
final baseDir = await getApplicationSupportDirectory();
|
||||
await Hive.initFlutter(baseDir.path);
|
||||
} else {
|
||||
await Hive.initFlutter();
|
||||
}
|
||||
// 使用加密适配器
|
||||
final key = HiveAesCipher(_generateKey());
|
||||
await Hive.openBox(_boxName, encryptionCipher: key);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('初始化 Hive 失败: $e', tag: 'SecureStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 生成加密密钥
|
||||
List<int> _generateKey() {
|
||||
final key = utf8.encode(_encryptionKey);
|
||||
final hash = sha256.convert(key);
|
||||
return hash.bytes;
|
||||
}
|
||||
|
||||
// 获取存储箱
|
||||
Box<dynamic> get _box => Hive.box(_boxName);
|
||||
|
||||
// 存储数据
|
||||
Future<void> kr_saveData({required String key, required String value}) async {
|
||||
try {
|
||||
await _box.put(key, value);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('存储数据失败: $e', tag: 'SecureStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 读取数据
|
||||
Future<String?> kr_readData({required String key}) async {
|
||||
try {
|
||||
return _box.get(key) as String?;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('读取数据失败: $e', tag: 'SecureStorage');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据
|
||||
Future<void> kr_deleteData({required String key}) async {
|
||||
try {
|
||||
await _box.delete(key);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('删除数据失败: $e', tag: 'SecureStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有数据
|
||||
Future<void> kr_clearAllData() async {
|
||||
try {
|
||||
await _box.clear();
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('清除数据失败: $e', tag: 'SecureStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查键是否存在
|
||||
Future<bool> kr_hasKey({required String key}) async {
|
||||
try {
|
||||
return _box.containsKey(key);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('检查键失败: $e', tag: 'SecureStorage');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存布尔值
|
||||
Future<void> kr_saveBool({required String key, required bool value}) async {
|
||||
try {
|
||||
await _box.put(key, value);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('存储布尔值失败: $e', tag: 'SecureStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取布尔值
|
||||
Future<bool?> kr_getBool({required String key}) async {
|
||||
try {
|
||||
return _box.get(key) as bool?;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('读取布尔值失败: $e', tag: 'SecureStorage');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存整数
|
||||
Future<void> kr_saveInt({required String key, required int value}) async {
|
||||
try {
|
||||
await _box.put(key, value);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('存储整数失败: $e', tag: 'SecureStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取整数
|
||||
Future<int?> kr_getInt({required String key}) async {
|
||||
try {
|
||||
return _box.get(key) as int?;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('读取整数失败: $e', tag: 'SecureStorage');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/dialogs/kr_update_dialog.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import '../localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
|
||||
import '../model/response/kr_config_data.dart';
|
||||
|
||||
/// 更新工具类
|
||||
class KRUpdateUtil {
|
||||
/// 单例模式
|
||||
static final KRUpdateUtil _instance = KRUpdateUtil._internal();
|
||||
factory KRUpdateUtil() => _instance;
|
||||
KRUpdateUtil._internal();
|
||||
|
||||
/// 当前更新信息
|
||||
KRUpdateApplication? _kr_currentUpdateInfo;
|
||||
|
||||
/// 初始化更新信息
|
||||
/// [updateInfo] 更新信息对象
|
||||
void kr_initUpdateInfo(KRUpdateApplication updateInfo) {
|
||||
_kr_currentUpdateInfo = updateInfo;
|
||||
}
|
||||
|
||||
/// 检查更新
|
||||
Future<void> kr_checkUpdate() async {
|
||||
if (_kr_currentUpdateInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前应用版本号
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
final String currentVersion = packageInfo.version;
|
||||
|
||||
// 比较版本号
|
||||
if (_kr_compareVersions(currentVersion, _kr_currentUpdateInfo!.kr_version) <
|
||||
0) {
|
||||
_kr_showUpdateDialog(_kr_currentUpdateInfo!);
|
||||
}
|
||||
}
|
||||
|
||||
/// 比较版本号
|
||||
/// 返回值:
|
||||
/// -1: 当前版本小于目标版本
|
||||
/// 0: 当前版本等于目标版本
|
||||
/// 1: 当前版本大于目标版本
|
||||
int _kr_compareVersions(String currentVersion, String targetVersion) {
|
||||
return currentVersion.compareTo(targetVersion);
|
||||
}
|
||||
|
||||
/// 显示更新对话框
|
||||
void _kr_showUpdateDialog(KRUpdateApplication updateInfo) {
|
||||
// 获取更新内容
|
||||
String? updateContent = updateInfo.kr_version_description;
|
||||
|
||||
// 如果更新内容为空,使用默认内容
|
||||
if (updateContent?.isEmpty ?? true) {
|
||||
updateContent = AppTranslations.kr_update.content;
|
||||
}
|
||||
|
||||
KRUpdateDialog.show(
|
||||
|
||||
version: updateInfo.kr_version,
|
||||
updateContent: updateContent,
|
||||
onConfirm: () {
|
||||
_kr_handleUpdate(updateInfo);
|
||||
},
|
||||
onCancel: () => Get.back(),
|
||||
showCancelButton: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 处理更新
|
||||
Future<void> _kr_handleUpdate(KRUpdateApplication updateInfo) async {
|
||||
if (updateInfo.kr_url.isEmpty) {
|
||||
Get.back();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Uri uri = Uri.parse(updateInfo.kr_url);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// Android 平台处理
|
||||
await _kr_handleAndroidUpdate(uri);
|
||||
} else if (Platform.isIOS) {
|
||||
// iOS 平台处理
|
||||
await _kr_handleIOSUpdate(uri);
|
||||
} else if (Platform.isMacOS) {
|
||||
// macOS 平台处理
|
||||
await _kr_handleMacOSUpdate(uri);
|
||||
} else if (Platform.isWindows) {
|
||||
// Windows 平台处理
|
||||
await _kr_handleWindowsUpdate(uri);
|
||||
} else if (Platform.isLinux) {
|
||||
// Linux 平台处理
|
||||
await _kr_handleLinuxUpdate(uri);
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('更新处理错误: $e', tag: 'UpdateUtil');
|
||||
} finally {
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 Android 平台更新
|
||||
Future<void> _kr_handleAndroidUpdate(Uri uri) async {
|
||||
// Android 平台通常是下载 APK 文件
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 iOS 平台更新
|
||||
Future<void> _kr_handleIOSUpdate(Uri uri) async {
|
||||
// iOS 平台通常是跳转到 App Store
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 macOS 平台更新
|
||||
Future<void> _kr_handleMacOSUpdate(Uri uri) async {
|
||||
// macOS 平台通常是下载 DMG 文件或跳转到 Mac App Store
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 Windows 平台更新
|
||||
Future<void> _kr_handleWindowsUpdate(Uri uri) async {
|
||||
// Windows 平台通常是下载 exe 安装文件
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理 Linux 平台更新
|
||||
Future<void> _kr_handleLinuxUpdate(Uri uri) async {
|
||||
// Linux 平台通常是下载安装包文件
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+224
@@ -0,0 +1,224 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/localization/kr_language_utils.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import '../services/singbox_imp/kr_sing_box_imp.dart';
|
||||
|
||||
class KRWindowManager with WindowListener, TrayListener {
|
||||
static final KRWindowManager _instance = KRWindowManager._internal();
|
||||
factory KRWindowManager() => _instance;
|
||||
KRWindowManager._internal();
|
||||
|
||||
/// 初始化窗口管理器
|
||||
Future<void> kr_initWindowManager() async {
|
||||
KRLogUtil.kr_i('kr_initWindowManager: 开始初始化窗口管理器');
|
||||
|
||||
await windowManager.ensureInitialized();
|
||||
KRLogUtil.kr_i('kr_initWindowManager: 窗口管理器已初始化');
|
||||
|
||||
const WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(800, 668),
|
||||
minimumSize: Size(800, 668),
|
||||
center: true,
|
||||
backgroundColor: Colors.white,
|
||||
skipTaskbar: false,
|
||||
title: 'Kaer VPN',
|
||||
titleBarStyle: TitleBarStyle.normal,
|
||||
windowButtonVisibility: true,
|
||||
);
|
||||
|
||||
await windowManager.waitUntilReadyToShow(windowOptions);
|
||||
KRLogUtil.kr_i('kr_initWindowManager: 窗口准备就绪');
|
||||
|
||||
// 先添加监听器
|
||||
windowManager.addListener(this);
|
||||
KRLogUtil.kr_i('kr_initWindowManager: 已添加窗口监听器');
|
||||
|
||||
// 确保在 Windows 下正确设置窗口属性
|
||||
if (Platform.isWindows) {
|
||||
await windowManager.setTitleBarStyle(TitleBarStyle.normal);
|
||||
await windowManager.setTitle('BearVPN');
|
||||
await windowManager.setSize(const Size(800, 668));
|
||||
await windowManager.setMinimumSize(const Size(800, 668));
|
||||
await windowManager.center();
|
||||
await windowManager.show();
|
||||
// 阻止窗口关闭
|
||||
await windowManager.setPreventClose(true);
|
||||
} else {
|
||||
await windowManager.setTitle('Kaer VPN');
|
||||
await windowManager.setSize(const Size(800, 668));
|
||||
await windowManager.setMinimumSize(const Size(800, 668));
|
||||
await windowManager.center();
|
||||
}
|
||||
|
||||
// 初始化托盘
|
||||
await _initTray();
|
||||
|
||||
// 初始化平台通道
|
||||
_initPlatformChannel();
|
||||
|
||||
KRLogUtil.kr_i('kr_initWindowManager: 初始化完成');
|
||||
|
||||
ever(KRLanguageUtils.kr_language, (_) {
|
||||
final Menu menu = Menu(
|
||||
items: [
|
||||
MenuItem(
|
||||
label: AppTranslations.kr_tray.openDashboard,
|
||||
onClick: (_) => _showWindow(),
|
||||
),
|
||||
if (Platform.isMacOS) MenuItem.separator(),
|
||||
if (Platform.isMacOS)
|
||||
MenuItem(
|
||||
label: AppTranslations.kr_tray.copyToTerminal,
|
||||
onClick: (_) => _copyToTerminal(),
|
||||
),
|
||||
MenuItem.separator(),
|
||||
MenuItem(
|
||||
label: AppTranslations.kr_tray.exitApp,
|
||||
onClick: (_) => _exitApp(),
|
||||
),
|
||||
],
|
||||
);
|
||||
trayManager.setContextMenu(menu);
|
||||
});
|
||||
}
|
||||
|
||||
/// 初始化平台通道
|
||||
void _initPlatformChannel() {
|
||||
if (Platform.isMacOS) {
|
||||
const platform = MethodChannel('kaer_vpn/terminate');
|
||||
platform.setMethodCallHandler((call) async {
|
||||
if (call.method == 'onTerminate') {
|
||||
KRLogUtil.kr_i('收到应用终止通知');
|
||||
await _handleTerminate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化托盘
|
||||
Future<void> _initTray() async {
|
||||
KRLogUtil.kr_i('_initTray: 开始初始化托盘');
|
||||
trayManager.addListener(this);
|
||||
|
||||
final String iconPath = Platform.isMacOS
|
||||
? 'assets/images/tray_icon.png'
|
||||
: 'assets/images/tray_icon.ico';
|
||||
await trayManager.setIcon(iconPath);
|
||||
|
||||
// 初始化托盘
|
||||
Future.delayed(const Duration(seconds: 1), () async {
|
||||
final Menu menu = Menu(
|
||||
items: [
|
||||
MenuItem(
|
||||
label: AppTranslations.kr_tray.openDashboard,
|
||||
onClick: (_) => _showWindow(),
|
||||
),
|
||||
if (Platform.isMacOS) MenuItem.separator(),
|
||||
if (Platform.isMacOS)
|
||||
MenuItem(
|
||||
label: AppTranslations.kr_tray.copyToTerminal,
|
||||
onClick: (_) => _copyToTerminal(),
|
||||
),
|
||||
MenuItem.separator(),
|
||||
MenuItem(
|
||||
label: AppTranslations.kr_tray.exitApp,
|
||||
onClick: (_) => _exitApp(),
|
||||
),
|
||||
],
|
||||
);
|
||||
await trayManager.setContextMenu(menu);
|
||||
});
|
||||
}
|
||||
|
||||
/// 复制到终端
|
||||
Future<void> _copyToTerminal() async {
|
||||
final String kr_port = KRSingBoxImp.instance.kr_port.toString();
|
||||
final String proxyText =
|
||||
'export https_proxy=http://127.0.0.1:$kr_port http_proxy=http://127.0.0.1:$kr_port all_proxy=socks5://127.0.0.1:$kr_port';
|
||||
|
||||
await Clipboard.setData(ClipboardData(text: proxyText));
|
||||
}
|
||||
|
||||
/// 退出应用
|
||||
Future<void> _exitApp() async {
|
||||
KRLogUtil.kr_i('_exitApp: 退出应用');
|
||||
await _handleTerminate();
|
||||
await windowManager.destroy();
|
||||
}
|
||||
|
||||
/// 显示窗口
|
||||
Future<void> _showWindow() async {
|
||||
KRLogUtil.kr_i('_showWindow: 开始显示窗口');
|
||||
try {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
await windowManager.setSkipTaskbar(false);
|
||||
await windowManager.setAlwaysOnTop(true);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await windowManager.setAlwaysOnTop(false);
|
||||
KRLogUtil.kr_i('_showWindow: 窗口显示成功');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('_showWindow: 显示窗口失败 - $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowEvent(String eventName) {
|
||||
KRLogUtil.kr_i('onWindowEvent: 收到窗口事件 - $eventName');
|
||||
// 移除 Windows 下自动显示窗口的逻辑
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowClose() async {
|
||||
if (Platform.isWindows) {
|
||||
await windowManager.hide();
|
||||
} else if (Platform.isMacOS) {
|
||||
await windowManager.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconMouseDown() {
|
||||
// 左键点击只显示菜单
|
||||
trayManager.popUpContextMenu();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayIconRightMouseDown() {
|
||||
// 右键点击只显示菜单
|
||||
trayManager.popUpContextMenu();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowFocus() {
|
||||
// 移除自动显示窗口的逻辑
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowBlur() {
|
||||
// 当窗口失去焦点时,保持窗口可见
|
||||
}
|
||||
|
||||
/// 设置窗口背景颜色
|
||||
Future<void> kr_setBackgroundColor(Color color) async {
|
||||
await windowManager.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
/// 处理应用终止
|
||||
Future<void> _handleTerminate() async {
|
||||
KRLogUtil.kr_i('_handleTerminate: 处理应用终止');
|
||||
if (KRSingBoxImp.instance.kr_status == SingboxStatus.started()) {
|
||||
await KRSingBoxImp.instance.kr_stop();
|
||||
}
|
||||
await trayManager.destroy();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user