feat: 更新代码仓库全部修改
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / build (push) Has been cancelled

This commit is contained in:
2025-10-30 04:47:53 -07:00
parent 145832093e
commit f42a481452
134 changed files with 8032 additions and 4270 deletions
+9 -4
View File
@@ -4,16 +4,17 @@ abstract class Api {
static const String kr_isRegister = "/v1/app/auth/check";
/// 注册
static const String kr_register = "/v1/auth/register";
// static const String kr_register = "/v1/auth/register";
static const String kr_register = "/v1/public/user/bind_email_with_verification";
/// 验证验证码
static const String kr_checkVerificationCode = "/v1/auth/check-code";
/// 发送验证码(统一接口,支持邮箱和手机)
static const String kr_sendCode = "/v1/auth/send-code";
static const String kr_sendCode = "/v1/common/send_code";
/// 登录接口
static const String kr_login = "/v1/auth/login";
static const String kr_login = "/v1/public/user/bind_email_with_password";
/// 设备登录(游客登录)
/// 参考 OmnOem 项目 ppanel.json 配置
@@ -55,7 +56,7 @@ abstract class Api {
"/v1/public/user/subscribe";
/// 续费
static const String kr_renewal = "/v1/app/order/renewal";
static const String kr_renewal = "/v1/public/order/renewal";
/// 获取用户订阅流量日志
/// 通过该接口判断订单状态
@@ -104,4 +105,8 @@ abstract class Api {
/// 获取用户信息(用于获取邀请码等)
static const String kr_getUserInfo = "/v1/public/user/info";
/// 保存邀请码
static const String hi_invite_code = "/v1/public/user/bind_invite_code";
}
+28 -1
View File
@@ -87,7 +87,7 @@ class KRUserApi {
static Future<Either<HttpError, KRUserInfo>> kr_getUserInfo() async {
final Map<String, dynamic> data = <String, dynamic>{};
BaseResponse<KRUserInfo> baseResponse =
await HttpUtil.getInstance().request<KRUserInfo>(
await HttpUtil.getInstance().request<KRUserInfo>(
Api.kr_getUserInfo,
data,
method: HttpMethod.GET,
@@ -193,4 +193,31 @@ class KRUserApi {
return right(null);
}
/// 绑定样式
Future<Either<HttpError, void>> hi_inviteCode(String inviteCode) async {
final Map<String, dynamic> data = <String, dynamic>{};
// 将字符串 ID 转换为整数
try {
data['invite_code'] = inviteCode;
} catch (e) {
return left(HttpError(msg: '邀请码错误', code: -1));
}
BaseResponse<dynamic> baseResponse =
await HttpUtil.getInstance().request<dynamic>(
Api.hi_invite_code,
data,
method: HttpMethod.POST,
isShowLoading: true,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(null);
}
}
+18 -40
View File
@@ -1,7 +1,6 @@
import 'dart:io' as io;
import 'dart:io';
import 'dart:math';
import 'dart:convert';
import 'package:dio/io.dart' as dio_io;
import 'package:fpdart/fpdart.dart';
import 'package:get/get.dart';
@@ -21,7 +20,6 @@ import '../kr_device_info_service.dart';
import '../kr_site_config_service.dart';
import '../../common/app_config.dart';
import 'package:dio/dio.dart' as dio;
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
class KRAuthApi {
/// 检查账号是否已注册(仅支持邮箱)
@@ -53,18 +51,19 @@ class KRAuthApi {
String? inviteCode}) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['password'] = password;
data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
data["code"] = code;
// data['password'] = password;
// data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
// 验证码是可选的,只有在提供时才发送
if (code != null && code.isNotEmpty) {
data["code"] = code;
}
// if (code != null && code.isNotEmpty) {
//
// }
// 邀请码是可选的
if (inviteCode != null && inviteCode.isNotEmpty) {
data["invite"] = inviteCode;
}
// if (inviteCode != null && inviteCode.isNotEmpty) {
// data["invite"] = inviteCode;
// }
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_register, data,
@@ -108,7 +107,7 @@ class KRAuthApi {
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
// data["identifier"] = deviceId;
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_login, data,
@@ -178,10 +177,7 @@ class KRAuthApi {
/// 设备登录(游客登录)
Future<Either<HttpError, String>> kr_deviceLogin() async {
try {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('🔐 开始设备登录(游客模式)');
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
KRLogUtil.kr_i('🔐 开始设备登录(游客模式)', tag: 'KRAuthApi');
KRLogUtil.kr_i('🔐 开始设备登录', tag: 'KRAuthApi');
// 获取设备信息
final deviceInfoService = KRDeviceInfoService();
@@ -229,24 +225,6 @@ class KRAuthApi {
// 使用 Dio 直接发送请求(因为需要特殊的加密处理)
final dioInstance = dio.Dio();
// 🔧 配置HttpClientAdapter优先使用本地 sing-box mixed 端口,
// 失败时退回直连
dioInstance.httpClientAdapter = dio_io.IOHttpClientAdapter(
createHttpClient: () {
final client = io.HttpClient();
client.findProxy = (url) {
final proxyConfig = KRSingBoxImp.instance.kr_buildProxyRule();
KRLogUtil.kr_i(
'🔍 KRAuthApi 请求使用代理: $proxyConfig, url: $url',
tag: 'KRAuthApi',
);
return proxyConfig;
};
return client;
},
);
final baseUrl = AppConfig.getInstance().baseUrl;
final url = '$baseUrl${Api.kr_deviceLogin}';
@@ -333,17 +311,17 @@ class KRAuthApi {
}
String _kr_getUserAgent() {
if (io.Platform.isAndroid) {
if (Platform.isAndroid) {
return 'android';
} else if (io.Platform.isIOS) {
} else if (Platform.isIOS) {
return 'ios';
} else if (io.Platform.isMacOS) {
} else if (Platform.isMacOS) {
return 'mac';
} else if (io.Platform.isWindows) {
} else if (Platform.isWindows) {
return 'windows';
} else if (io.Platform.isLinux) {
} else if (Platform.isLinux) {
return 'linux';
} else if (io.Platform.isFuchsia) {
} else if (Platform.isFuchsia) {
return 'harmony';
} else {
return 'unknown';
@@ -145,7 +145,7 @@ class KRSubscribeApi {
data['order_no'] = orderNo;
BaseResponse<KROrderStatus> baseResponse =
await HttpUtil.getInstance().request<KROrderStatus>(
await HttpUtil.getInstance().request<KROrderStatus>(
Api.kr_queryOrderStatus,
data,
method: HttpMethod.GET,
@@ -0,0 +1,174 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../modules/kr_home/views/hi_subscription_corner_button.dart';
import 'package:kaer_with_panels/main.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
class GlobalOverlayService extends GetxService {
Color? _currentColor;
static GlobalOverlayService get instance => Get.find<GlobalOverlayService>();
VoidCallback? _subscriptionAnimationTrigger;
void registerAnimationTrigger(VoidCallback trigger) {
_subscriptionAnimationTrigger = trigger;
}
/// 触发按钮动画跳转购买页
void triggerSubscriptionAnimation() {
if (_subscriptionAnimationTrigger != null) {
_subscriptionAnimationTrigger!.call();
} else {
print('🔥 Subscription animation trigger not registered yet');
}
}
OverlayEntry? _overlayEntry;
final RxBool _isVisible = false.obs;
bool get isVisible => _isVisible.value;
@override
void onInit() {
super.onInit();
print('🔥 GlobalOverlayService: Service initialized');
}
void showSubscriptionButton([OverlayState? overlay]) {
if (_isVisible.value) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
_doShowSubscriptionButton(overlay);
});
}
void _doShowSubscriptionButton([OverlayState? overlay, Color? color]) {
print('🔥 _doShowSubscriptionButton called, overlay=$overlay');
if (_isVisible.value) return;
try {
overlay ??= navigatorKey.currentState?.overlay;
if (overlay == null) {
print('🔥 GlobalOverlayService: No overlay found, cannot show subscription button');
return;
}
_overlayEntry = OverlayEntry(
builder: (context) {
final statusBarHeight = MediaQuery.of(context).padding.top < 62 ? 62 : MediaQuery.of(context).padding.top;
final double radius = 40.0 + statusBarHeight;
final double diameter = radius * 2;
return Stack(
children: [
// 1️⃣ 圆背景,保持原来位置
Positioned(
top: -(diameter - (radius + statusBarHeight)),
right: -(radius - (statusBarHeight / 2)),
width: diameter,
height: diameter,
child: HISubscriptionCornerButton(
size: diameter,
color: _currentColor ?? Theme.of(context).primaryColor,
topOffset: -radius,
rightOffset: -radius,
),
),
// 2️⃣ money-icon,独立定位,固定在屏幕右上角
Positioned(
top: MediaQuery.of(context).padding.top + 8,
right: (radius - (statusBarHeight / 2)) / 2,
child: GestureDetector(
behavior: HitTestBehavior.translucent, // 让区域可响应点击
onTap: () {
// ✅ 这里“代理”点击事件,转发到底层按钮动画逻辑
print('🔥 money-icon tapped → trigger HISubscriptionCornerButton animation');
GlobalOverlayService.instance.triggerSubscriptionAnimation();
},
child: IgnorePointer(
ignoring: false, // 不阻断事件
child: SizedBox(
width: 44,
height: 44,
child: KrLocalImage(
imageName: 'money-icon',
width: 44,
height: 44,
imageType: ImageType.svg,
),
),
),
),
),
],
);
},
);
overlay.insert(_overlayEntry!);
_isVisible.value = true;
print('🔥 GlobalOverlayService: Subscription button shown successfully');
} catch (e) {
print('🔥 GlobalOverlayService: Error showing subscription button: $e');
}
}
void hideSubscriptionButton() {
if (!_isVisible.value) return;
_overlayEntry?.remove();
_overlayEntry = null;
_isVisible.value = false;
}
void toggleSubscriptionButton() {
if (_isVisible.value) {
hideSubscriptionButton();
} else {
showSubscriptionButton();
}
}
void safeShowSubscriptionButton() {
void _attemptShow() {
final overlay = navigatorKey.currentState?.overlay;
if (overlay == null) {
print('🔥 Overlay not ready, retrying...');
Future.delayed(const Duration(milliseconds: 200), _attemptShow);
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
print('🔥 Overlay ready, inserting subscription button');
showSubscriptionButton(overlay);
});
}
_attemptShow();
}
void updateSubscriptionButtonColor(Color? color) {
if (_overlayEntry == null) {
print('🔥 updateSubscriptionButtonColor: OverlayEntry is null');
return;
}
_currentColor = color;
_overlayEntry!.markNeedsBuild(); // 🔥 强制刷新 UI
}
void forceShowSubscriptionButton() {
print('🔥 GlobalOverlayService: Force showing subscription button');
hideSubscriptionButton();
Future.delayed(const Duration(milliseconds: 100), () {
safeShowSubscriptionButton();
});
}
@override
void onClose() {
hideSubscriptionButton();
super.onClose();
}
}
+1 -1
View File
@@ -255,6 +255,6 @@ class KRDeviceInfoService {
final model = getDeviceModel();
final osVersion = getOSVersion();
return 'BearVPN/1.0.0 ($platform; $model; $osVersion) Flutter';
return 'HiVPN/1.0.0 ($platform; $model; $osVersion) Flutter';
}
}
@@ -464,6 +464,7 @@ class KRSubscribeService {
await kr_subscribeApi.kr_getAlreadySubscribe();
alreadySubscribeResult.fold(
(error) {
KRLogUtil.kr_e('获取已订阅列表失败: ${error.msg}', tag: 'SubscribeService');
throw Exception('获取已订阅列表失败: ${error.msg}');
},
(subscribes) {
@@ -615,6 +616,7 @@ class KRSubscribeService {
} catch (err, stackTrace) {
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
KRLogUtil.kr_e('刷新数据异常: $err\n$stackTrace', tag: 'SubscribeService');
rethrow;
}
}
@@ -43,10 +43,10 @@ class KRSingBoxImp {
late Directories kr_configDics;
/// 配置文件名称
String kr_configName = "BearVPN";
String kr_configName = "hiFastVPN";
/// 通道方法
final _kr_methodChannel = const MethodChannel("com.baer.app/platform");
final _kr_methodChannel = const MethodChannel("com.hi.app/platform");
final _kr_container = ProviderContainer();
@@ -70,6 +70,8 @@ class KRSingBoxImp {
/// 是否自动自动选择线路
final kr_isAutoOutbound = true.obs;
bool _initialized = false;
/// 连接类型
final kr_connectionType = KRConnectionType.rule.obs;
@@ -130,6 +132,12 @@ class KRSingBoxImp {
/// 初始化
Future<void> init() async {
try {
if (_initialized) {
KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化');
return;
}
_initialized = true;
KRLogUtil.kr_i('开始初始化 SingBox');
// 在应用启动时初始化
await KRCountryUtil.kr_init();
@@ -735,4 +743,4 @@ class KRSingBoxImp {
KRLogUtil.kr_e('📚 错误详情: ${e.toString()}', tag: 'SingBox');
}
}
}
}