Compare commits
3 Commits
325a63d35f
...
b442289b8a
| Author | SHA1 | Date | |
|---|---|---|---|
| b442289b8a | |||
| 89d13e6237 | |||
| 8fd742a688 |
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Executable file → Normal file
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Executable file → Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 5.4 KiB |
@ -1447,7 +1447,7 @@ class AppConfig {
|
|||||||
kr_official_telegram = config.kr_official_telegram;
|
kr_official_telegram = config.kr_official_telegram;
|
||||||
kr_official_telephone = config.kr_official_telephone;
|
kr_official_telephone = config.kr_official_telephone;
|
||||||
kr_invitation_link = config.kr_invitation_link;
|
kr_invitation_link = config.kr_invitation_link;
|
||||||
kr_website_id = config.kr_website_id;
|
// kr_website_id = config.kr_website_id;
|
||||||
if (config.kr_domains.isNotEmpty) {
|
if (config.kr_domains.isNotEmpty) {
|
||||||
KRDomain.kr_handleDomains(config.kr_domains);
|
KRDomain.kr_handleDomains(config.kr_domains);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,22 +19,12 @@ class HINodePageView extends GetView<HINodeListController> {
|
|||||||
return HIBaseScaffold(
|
return HIBaseScaffold(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
// 主要内容区域
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0, // ⭐ 所有内容整体贴到左边
|
left: 0,
|
||||||
child: Obx(() => controller.isDebugMode.value
|
child: Obx(() => controller.isDebugMode.value
|
||||||
? GestureDetector(
|
? GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
controller.kr_resetDebugMode();
|
controller.kr_resetDebugMode();
|
||||||
Get.snackbar(
|
|
||||||
'调试模式',
|
|
||||||
'已关闭调试模式',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
|
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
|
||||||
@ -65,6 +55,11 @@ class HINodePageView extends GetView<HINodeListController> {
|
|||||||
)
|
)
|
||||||
: const SizedBox.shrink()),
|
: const SizedBox.shrink()),
|
||||||
),
|
),
|
||||||
|
// 主要内容区域
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
|
||||||
// 模式切换器
|
// 模式切换器
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 100.w, right: 60.w),
|
padding: EdgeInsets.only(left: 100.w, right: 60.w),
|
||||||
|
|||||||
@ -429,7 +429,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'设备:${_extractDeviceModel(userAgent)}',
|
'设备:${_extractDeviceModel(userAgent, deviceType)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 10.sp, //
|
fontSize: 10.sp, //
|
||||||
@ -589,7 +589,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _extractDeviceModel(String deviceName) {
|
String _extractDeviceModel(String deviceName, String deviceType) {
|
||||||
// 匹配括号内内容
|
// 匹配括号内内容
|
||||||
final RegExp regExp = RegExp(r'\((.*?)\)');
|
final RegExp regExp = RegExp(r'\((.*?)\)');
|
||||||
final Match? match = regExp.firstMatch(deviceName);
|
final Match? match = regExp.firstMatch(deviceName);
|
||||||
|
|||||||
@ -305,19 +305,24 @@ class KRLoginController extends GetxController
|
|||||||
|
|
||||||
/// 发送验证码(仅支持邮箱)
|
/// 发送验证码(仅支持邮箱)
|
||||||
void kr_sendCode() async {
|
void kr_sendCode() async {
|
||||||
final either = await KRAuthApi().kr_sendCode(
|
if (accountController.text.isEmpty) {
|
||||||
accountController.text,
|
KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterAccount);
|
||||||
2
|
return;
|
||||||
); // 重置密码验证码类型为3
|
}
|
||||||
/*
|
|
||||||
*
|
int type;
|
||||||
* kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode
|
final check = await KRAuthApi().kr_isRegister(accountController.text);
|
||||||
? 2 // 注册验证码类型为2
|
final result = check.fold((l) {
|
||||||
: 3*/
|
KRCommonUtil.kr_showToast(l.msg);
|
||||||
|
return null;
|
||||||
|
}, (isRegistered) => isRegistered ? 2 : 1);
|
||||||
|
if (result == null) return;
|
||||||
|
type = result;
|
||||||
|
|
||||||
|
final either = await KRAuthApi().kr_sendCode(accountController.text, type);
|
||||||
either.fold((l) {
|
either.fold((l) {
|
||||||
KRCommonUtil.kr_showToast(l.msg);
|
KRCommonUtil.kr_showToast(l.msg);
|
||||||
}, (r) async {
|
}, (r) async {
|
||||||
/// 开始倒计时
|
|
||||||
_startCountdown();
|
_startCountdown();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,10 +76,13 @@ class KRLoginView extends GetView<KRLoginController> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final _ = KRAppRunData.getInstance().kr_isLogin.value;
|
final account = KRAppRunData.getInstance().kr_account.value;
|
||||||
final email = KRAppRunData.getInstance().kr_account.value ?? '';
|
final isDeviceLogin = account != null && account.startsWith('9000');
|
||||||
|
|
||||||
|
if (isDeviceLogin) return const SizedBox();
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
email.isNotEmpty ? email : '',
|
account ?? '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 20.sp,
|
fontSize: 20.sp,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/// 接口名称
|
/// 接口名称
|
||||||
abstract class Api {
|
abstract class Api {
|
||||||
/// 游客登录查看是否已经注册
|
/// 游客登录查看是否已经注册
|
||||||
static const String kr_isRegister = "/v1/app/auth/check";
|
static const String kr_isRegister = "/v1/auth/check";
|
||||||
/// 判断邮箱和当前设备是否已存在订阅
|
/// 判断邮箱和当前设备是否已存在订阅
|
||||||
static const String kr_checkSubscription = "/v1/public/user/subscribe_status";
|
static const String kr_checkSubscription = "/v1/public/user/subscribe_status";
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class KRAuthApi {
|
|||||||
|
|
||||||
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
|
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
|
||||||
.request<KRIsRegister>(Api.kr_isRegister, data,
|
.request<KRIsRegister>(Api.kr_isRegister, data,
|
||||||
method: HttpMethod.POST, isShowLoading: true);
|
method: HttpMethod.GET, isShowLoading: true);
|
||||||
|
|
||||||
if (!baseResponse.isSuccess) {
|
if (!baseResponse.isSuccess) {
|
||||||
return left(
|
return left(
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
|
|||||||
import 'package:kaer_with_panels/singbox/service/singbox_service_provider.dart';
|
import 'package:kaer_with_panels/singbox/service/singbox_service_provider.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
|
||||||
import '../../../core/model/directories.dart';
|
import '../../../core/model/directories.dart';
|
||||||
import '../../../singbox/model/singbox_config_option.dart';
|
import '../../../singbox/model/singbox_config_option.dart';
|
||||||
@ -112,6 +113,62 @@ class KRSingBoxImp {
|
|||||||
/// 初始化进行中共享 Future(single-flight 防并发)
|
/// 初始化进行中共享 Future(single-flight 防并发)
|
||||||
Future<void>? _kr_initFuture;
|
Future<void>? _kr_initFuture;
|
||||||
|
|
||||||
|
/// 是否为Android模拟器
|
||||||
|
bool _kr_isAndroidEmulator = false;
|
||||||
|
|
||||||
|
/// 检测并缓存模拟器标识
|
||||||
|
///
|
||||||
|
/// - 仅在 Android 上进行模拟器特征识别
|
||||||
|
/// - 通过 brand/model/product/manufacturer 的常见模拟器特征判断
|
||||||
|
/// 返回:无(结果写入 `_kr_isAndroidEmulator`)
|
||||||
|
Future<void> _kr_detectEmulator() async {
|
||||||
|
try {
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
_kr_isAndroidEmulator = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final info = await DeviceInfoPlugin().androidInfo;
|
||||||
|
final brand = (info.brand ?? '').toLowerCase();
|
||||||
|
final model = (info.model ?? '').toLowerCase();
|
||||||
|
final product = (info.product ?? '').toLowerCase();
|
||||||
|
final manufacturer = (info.manufacturer ?? '').toLowerCase();
|
||||||
|
final device = (info.device ?? '').toLowerCase();
|
||||||
|
|
||||||
|
bool containsAny(String s, List<String> keys) => keys.any((k) => s.contains(k));
|
||||||
|
|
||||||
|
final indicators = <bool>[
|
||||||
|
containsAny(brand, [
|
||||||
|
'generic', 'unknown', 'google', 'vbox', 'virtualbox',
|
||||||
|
'bluestacks', 'nox', 'ldplayer', 'genymotion', 'mumu', 'netease', 'leidian'
|
||||||
|
]),
|
||||||
|
containsAny(model, [
|
||||||
|
'emulator', 'sdk', 'sdk_gphone', 'google_sdk', 'android sdk built for x86',
|
||||||
|
'bluestacks', 'genymotion', 'nox', 'ldplayer', 'mumu'
|
||||||
|
]),
|
||||||
|
containsAny(product, [
|
||||||
|
'sdk', 'google_sdk', 'sdk_gphone', 'emulator', 'vbox', 'virtualbox'
|
||||||
|
]),
|
||||||
|
containsAny(manufacturer, [
|
||||||
|
'genymotion', 'unknown', 'bluestacks', 'nox', 'ld', 'netease', 'mumu'
|
||||||
|
]),
|
||||||
|
containsAny(device, [
|
||||||
|
'emulator', 'generic', 'vbox', 'virtualbox', 'sdk'
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
_kr_isAndroidEmulator = indicators.any((v) => v);
|
||||||
|
KRLogUtil.kr_i(
|
||||||
|
'🔍 Android 模拟器检测: ${_kr_isAndroidEmulator ? '是模拟器' : '非模拟器'}'
|
||||||
|
' (brand=$brand, model=$model, product=$product, manufacturer=$manufacturer, device=$device)',
|
||||||
|
tag: 'SingBox',
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
_kr_isAndroidEmulator = false;
|
||||||
|
KRLogUtil.kr_w('⚠️ 模拟器检测失败,按真机处理: $e', tag: 'SingBox');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 当前混合代理端口是否就绪
|
/// 当前混合代理端口是否就绪
|
||||||
bool get kr_isProxyReady => kr_status.value is SingboxStarted;
|
bool get kr_isProxyReady => kr_status.value is SingboxStarted;
|
||||||
|
|
||||||
@ -167,6 +224,9 @@ class KRSingBoxImp {
|
|||||||
await KRCountryUtil.kr_init();
|
await KRCountryUtil.kr_init();
|
||||||
KRLogUtil.kr_i('国家工具初始化完成');
|
KRLogUtil.kr_i('国家工具初始化完成');
|
||||||
|
|
||||||
|
// 设备环境检测(Android模拟器)
|
||||||
|
await _kr_detectEmulator();
|
||||||
|
|
||||||
final oOption = SingboxConfigOption.fromJson(_getConfigOption());
|
final oOption = SingboxConfigOption.fromJson(_getConfigOption());
|
||||||
KRLogUtil.kr_i('配置选项初始化完成');
|
KRLogUtil.kr_i('配置选项初始化完成');
|
||||||
|
|
||||||
@ -555,9 +615,11 @@ class KRSingBoxImp {
|
|||||||
"log-level": "info", // 调试阶段使用 info,生产环境改为 warn
|
"log-level": "info", // 调试阶段使用 info,生产环境改为 warn
|
||||||
"resolve-destination": false,
|
"resolve-destination": false,
|
||||||
"ipv6-mode": "ipv4_only", // 参考 hiddify-app: 仅使用 IPv4 (有效值: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
|
"ipv6-mode": "ipv4_only", // 参考 hiddify-app: 仅使用 IPv4 (有效值: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
|
||||||
"remote-dns-address": "https://dns.google/dns-query", // 使用 Google DoH,避免中转节点 DNS 死锁
|
"remote-dns-address": _kr_isAndroidEmulator
|
||||||
|
? "local"
|
||||||
|
: "https://dns.google/dns-query",
|
||||||
"remote-dns-domain-strategy": "prefer_ipv4",
|
"remote-dns-domain-strategy": "prefer_ipv4",
|
||||||
"direct-dns-address": "local", // 使用系统 DNS,确保中转服务器域名能被解析
|
"direct-dns-address": "local",
|
||||||
"direct-dns-domain-strategy": "prefer_ipv4",
|
"direct-dns-domain-strategy": "prefer_ipv4",
|
||||||
"mixed-port": kr_port,
|
"mixed-port": kr_port,
|
||||||
"tproxy-port": kr_port,
|
"tproxy-port": kr_port,
|
||||||
@ -645,6 +707,47 @@ class KRSingBoxImp {
|
|||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 动态构建 DNS 配置
|
||||||
|
///
|
||||||
|
/// - 真机:使用远程 DoH + 本地系统 DNS,final 指向远程
|
||||||
|
/// - 模拟器:仅使用系统 DNS,final 指向直连,避免覆盖系统 DNS
|
||||||
|
/// 返回:Sing-box `dns` 段配置 Map
|
||||||
|
Map<String, dynamic> _kr_buildDnsConfig() {
|
||||||
|
if (_kr_isAndroidEmulator) {
|
||||||
|
KRLogUtil.kr_i('🛡️ 模拟器兼容模式:强制使用系统 DNS', tag: 'SingBox');
|
||||||
|
return {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"tag": "dns-direct",
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [],
|
||||||
|
"final": "dns-direct",
|
||||||
|
"strategy": "prefer_ipv4"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"tag": "dns-remote",
|
||||||
|
"address": "https://1.1.1.1/dns-query",
|
||||||
|
"address_resolver": "dns-direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "dns-direct",
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": _kr_buildDnsRules(),
|
||||||
|
"final": "dns-remote",
|
||||||
|
"strategy": "prefer_ipv4"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// 订阅状态变化流
|
/// 订阅状态变化流
|
||||||
/// 参考 hiddify-app: 监听 libcore 发送的状态事件来自动更新 UI
|
/// 参考 hiddify-app: 监听 libcore 发送的状态事件来自动更新 UI
|
||||||
void _kr_subscribeToStatus() {
|
void _kr_subscribeToStatus() {
|
||||||
@ -1150,23 +1253,7 @@ class KRSingBoxImp {
|
|||||||
"level": "debug",
|
"level": "debug",
|
||||||
"timestamp": true
|
"timestamp": true
|
||||||
},
|
},
|
||||||
"dns": {
|
"dns": _kr_buildDnsConfig(),
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"tag": "dns-remote",
|
|
||||||
"address": "https://1.1.1.1/dns-query",
|
|
||||||
"address_resolver": "dns-direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "dns-direct",
|
|
||||||
"address": "local",
|
|
||||||
"detour": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": _kr_buildDnsRules(), // ✅ 使用动态构建的 DNS 规则
|
|
||||||
"final": "dns-remote",
|
|
||||||
"strategy": "prefer_ipv4"
|
|
||||||
},
|
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type": "tun",
|
"type": "tun",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user