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
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:
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user