feat: bugfix
This commit is contained in:
parent
dbcdf8b8a4
commit
ce4abb7e01
23
Makefile
23
Makefile
@ -12,6 +12,10 @@ ifeq ($(OS),Windows_NT)
|
||||
endif
|
||||
endif
|
||||
|
||||
APP_NAME=HiFastVPN
|
||||
VERSION_NAME=$(shell grep '^version:' pubspec.yaml | sed 's/version: //;s/+.*//;s/[^0-9.]//g')
|
||||
VERSION_BUILD=$(shell grep '^version:' pubspec.yaml | sed 's/.*+//;s/[^0-9]//g')
|
||||
|
||||
|
||||
BINDIR=libcore$(SEP)bin
|
||||
ANDROID_OUT=android$(SEP)app$(SEP)libs
|
||||
@ -54,7 +58,7 @@ gen:
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
translate:
|
||||
dart run slang
|
||||
dart run slang --overwrite
|
||||
|
||||
|
||||
|
||||
@ -152,9 +156,24 @@ gen_translations: #generating missing translations using google translate
|
||||
|
||||
android-release: android-apk-release
|
||||
|
||||
android-rename:
|
||||
@FULL_VER=$(VERSION_NAME).$(VERSION_BUILD); \
|
||||
OUT_DIR=build/app/outputs/flutter-apk; \
|
||||
echo "Renaming APKs in $$OUT_DIR to $(APP_NAME)-$$FULL_VER-<ABI>.apk"; \
|
||||
for SRC in "$$OUT_DIR"/app-*-release.apk; do \
|
||||
if [ -f "$$SRC" ]; then \
|
||||
FILENAME=$$(basename "$$SRC"); \
|
||||
TGT="$$OUT_DIR/$(APP_NAME)-$$FULL_VER-$${FILENAME#app-}"; \
|
||||
echo "Renaming $$SRC -> $$TGT"; \
|
||||
mv -v "$$SRC" "$$TGT"; \
|
||||
else \
|
||||
echo "Skip $$SRC, file not found"; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
android-apk-release:
|
||||
echo flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --split-per-abi --verbose
|
||||
flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --verbose
|
||||
flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --verbose
|
||||
ls -R build/app/outputs
|
||||
|
||||
android-aab-release:
|
||||
|
||||
@ -58,7 +58,7 @@ class HIMenuView extends GetView<HIMenuController> implements HasSwipeConfig {
|
||||
);
|
||||
}),
|
||||
|
||||
SizedBox(height: 10.h), // 卡片和菜单列表的间距
|
||||
SizedBox(height: 10), // 卡片和菜单列表的间距
|
||||
|
||||
// ListView.separated 现在也会继承 Padding 的约束
|
||||
ListView.separated(
|
||||
|
||||
@ -31,10 +31,10 @@ class UserInfoCard extends StatelessWidget {
|
||||
// 让整个卡片的透明区域也能响应点击
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.fromLTRB(7.w, 4, 18.w, 4),
|
||||
padding: EdgeInsets.fromLTRB(5, 4, 18, 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(35.5.r),
|
||||
borderRadius: BorderRadius.circular(35.5),
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 2.0, // 👇 核心改动: 将边框宽度设置为 2.0
|
||||
@ -43,16 +43,16 @@ class UserInfoCard extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 18.r,
|
||||
radius: 18,
|
||||
backgroundColor: Colors.white,
|
||||
child: KrLocalImage(
|
||||
imageName: 'hi-home-logo',
|
||||
imageType: ImageType.svg,
|
||||
width: 16.w,
|
||||
height: 16.h,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@ -109,8 +109,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 连接计时器
|
||||
Timer? _kr_connectionTimer;
|
||||
|
||||
// 🔧 新增:EventBus 监听器 Worker(用于清理)
|
||||
Worker? _eventBusWorker;
|
||||
int _kr_connectionSeconds = 0;
|
||||
|
||||
// 🔧 保守修复:添加状态变化时间追踪,用于检测状态卡住
|
||||
DateTime? _lastStatusChangeTime;
|
||||
Timer? _statusWatchdogTimer;
|
||||
|
||||
// 当前选中的组
|
||||
final Rx<KRGroupOutboundList?> kr_currentGroup =
|
||||
Rx<KRGroupOutboundList?>(null);
|
||||
@ -315,13 +322,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
});
|
||||
|
||||
// 🔧 Android 15 新增:5秒后再次强制更新高度,兜底保护
|
||||
Future.delayed(const Duration(seconds: 5), () {
|
||||
if (kr_bottomPanelHeight.value < 100) {
|
||||
KRLogUtil.kr_w('检测到底部面板高度异常(${kr_bottomPanelHeight.value}),强制修正',
|
||||
tag: 'HomeController');
|
||||
kr_updateBottomPanelHeight();
|
||||
}
|
||||
});
|
||||
// 🔧 保守修复: 启动状态监控定时器,每 30 秒检查一次状态
|
||||
_startStatusWatchdog();
|
||||
}
|
||||
|
||||
/// 🔧 新增:恢复上次选择的节点显示
|
||||
@ -694,11 +696,14 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
});
|
||||
|
||||
// 监听所有支付相关消息
|
||||
KREventBus().kr_listenMessages(
|
||||
// 🔧 修复:保存 EventBus 监听器 Worker,以便在控制器销毁时清理
|
||||
_eventBusWorker = KREventBus().kr_listenMessages(
|
||||
[KRMessageType.kr_payment, KRMessageType.kr_subscribe_update],
|
||||
_kr_handleMessage,
|
||||
);
|
||||
if (kDebugMode) {
|
||||
print('✅ [HomeController] EventBus 监听器已注册');
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理消息
|
||||
@ -735,6 +740,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_i('🔄 连接状态变化: $status', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 当前状态类型: ${status.runtimeType}', tag: 'HomeController');
|
||||
|
||||
// 🔧 保守修复: 记录状态变化时间
|
||||
_lastStatusChangeTime = DateTime.now();
|
||||
|
||||
switch (status) {
|
||||
case SingboxStopped():
|
||||
KRLogUtil.kr_i('🔴 状态: 已停止', tag: 'HomeController');
|
||||
@ -880,15 +888,28 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
tag: 'HomeController');
|
||||
if (kDebugMode) {}
|
||||
|
||||
// 🔧 关键: 如果正在切换中,直接忽略(参考 hiddify-app 的 "switching status, debounce")
|
||||
// 🔧 保守修复: 检测状态是否卡住(超过10秒)
|
||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)',
|
||||
tag: 'HomeController');
|
||||
if (kDebugMode) {}
|
||||
return;
|
||||
final now = DateTime.now();
|
||||
if (_lastStatusChangeTime != null &&
|
||||
now.difference(_lastStatusChangeTime!) > const Duration(seconds: 10)) {
|
||||
// 状态卡住,强制重置
|
||||
KRLogUtil.kr_w('⚠️ 检测到状态卡住超过10秒 (当前: $currentStatus),执行强制重置', tag: 'HomeController');
|
||||
await _forceResetState();
|
||||
// 重置后重新尝试操作
|
||||
KRLogUtil.kr_i('🔄 状态已重置,继续执行切换操作', tag: 'HomeController');
|
||||
} else {
|
||||
KRLogUtil.kr_i('🔄 正在切换中,忽略本次操作 (当前状态: $currentStatus)', tag: 'HomeController');
|
||||
if (kDebugMode) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 🔧 保守修复: 记录状态变化时间
|
||||
_lastStatusChangeTime = DateTime.now();
|
||||
|
||||
if (value) {
|
||||
// 开启连接
|
||||
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
|
||||
@ -939,26 +960,40 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
/// 🔧 等待状态达到预期值
|
||||
Future<void> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
||||
if (kDebugMode) {}
|
||||
/// 🔧 保守修复: 返回 bool 表示是否成功达到预期状态
|
||||
Future<bool> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
|
||||
if (kDebugMode) {
|
||||
}
|
||||
final startTime = DateTime.now();
|
||||
|
||||
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
|
||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
if (kDebugMode) {}
|
||||
if (kDebugMode) {
|
||||
}
|
||||
|
||||
if (currentStatus.runtimeType == expectedType) {
|
||||
if (kDebugMode) {}
|
||||
if (kDebugMode) {
|
||||
}
|
||||
// 强制同步确保 kr_isConnected 正确
|
||||
kr_forceSyncConnectionStatus();
|
||||
return;
|
||||
// 🔧 更新状态变化时间
|
||||
_lastStatusChangeTime = DateTime.now();
|
||||
return true; // 成功
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
if (kDebugMode) {}
|
||||
// 🔧 保守修复: 超时后记录详细日志
|
||||
final finalStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
KRLogUtil.kr_w(
|
||||
'⏱️ 等待状态超时: 期望=${expectedType.toString()}, 实际=${finalStatus.runtimeType}, 耗时=${maxSeconds}秒',
|
||||
tag: 'HomeController',
|
||||
);
|
||||
if (kDebugMode) {
|
||||
}
|
||||
kr_forceSyncConnectionStatus();
|
||||
return false; // 超时失败
|
||||
}
|
||||
|
||||
Future<void> _kr_prepareCountrySelectionBeforeStart() async {
|
||||
@ -1813,10 +1848,36 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// 注销应用生命周期监听
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
if (kDebugMode) {
|
||||
print('🧹 [HomeController] 开始清理资源...');
|
||||
}
|
||||
|
||||
// 清理 EventBus 监听器
|
||||
_eventBusWorker?.dispose();
|
||||
if (kDebugMode) {
|
||||
print('✅ [HomeController] EventBus 监听器已清理');
|
||||
}
|
||||
|
||||
// 清理连接计时器
|
||||
_kr_connectionTimer?.cancel();
|
||||
if (kDebugMode) {
|
||||
print('✅ [HomeController] 连接计时器已清理');
|
||||
}
|
||||
|
||||
// 🔧 保守修复: 清理状态监控定时器
|
||||
_statusWatchdogTimer?.cancel();
|
||||
_connectionTimeoutTimer?.cancel();
|
||||
if (kDebugMode) {
|
||||
print('✅ [HomeController] 状态监控定时器已清理');
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ [HomeController] 资源清理完成');
|
||||
}
|
||||
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@ -2283,7 +2344,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
_kr_connectionTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_kr_connectionSeconds++;
|
||||
kr_connectionTime.value = kr_formatDuration(_kr_connectionSeconds);
|
||||
KRLogUtil.kr_i(kr_connectText.value);
|
||||
KRLogUtil.kr_i(kr_connectText.value, tag: 'kr_startConnectionTimer');
|
||||
});
|
||||
}
|
||||
|
||||
@ -2414,6 +2475,85 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔧 保守修复: 强制重置状态(用于状态卡住时)
|
||||
Future<void> _forceResetState() async {
|
||||
try {
|
||||
KRLogUtil.kr_w('🔄 开始强制重置状态...', tag: 'HomeController');
|
||||
|
||||
// 1. 尝试停止 SingBox
|
||||
try {
|
||||
await KRSingBoxImp.instance.kr_stop().timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_w('⚠️ 强制停止超时,继续重置', tag: 'HomeController');
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ 强制停止失败: $e,继续重置', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 2. 等待状态稳定
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// 3. 强制重置所有 UI 状态为断开
|
||||
kr_connectText.value = AppTranslations.kr_home.disconnected;
|
||||
KRSingBoxImp.instance.kr_status.value = SingboxStatus.stopped();
|
||||
kr_isConnected.value = false;
|
||||
kr_currentSpeed.value = "--";
|
||||
kr_currentNodeLatency.value = -2;
|
||||
kr_currentIp.value = "--";
|
||||
kr_currentProtocol.value = "--";
|
||||
kr_stopConnectionTimer();
|
||||
kr_resetConnectionInfo();
|
||||
|
||||
// 4. 重置状态变化时间
|
||||
_lastStatusChangeTime = null;
|
||||
|
||||
// 5. 强制更新 UI
|
||||
update();
|
||||
|
||||
KRLogUtil.kr_i('✅ 强制重置状态完成', tag: 'HomeController');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 强制重置状态失败: $e', tag: 'HomeController');
|
||||
}
|
||||
}
|
||||
|
||||
/// 🔧 保守修复: 启动状态监控定时器,定期检测状态是否卡住
|
||||
void _startStatusWatchdog() {
|
||||
_statusWatchdogTimer?.cancel();
|
||||
_statusWatchdogTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
|
||||
final currentStatus = KRSingBoxImp.instance.kr_status.value;
|
||||
|
||||
// 只检测中间状态(Starting/Stopping)
|
||||
if (currentStatus is SingboxStarting || currentStatus is SingboxStopping) {
|
||||
final now = DateTime.now();
|
||||
if (_lastStatusChangeTime != null) {
|
||||
final duration = now.difference(_lastStatusChangeTime!);
|
||||
|
||||
if (duration > const Duration(seconds: 15)) {
|
||||
// 状态卡住超过 15 秒
|
||||
KRLogUtil.kr_w(
|
||||
'⚠️ [Watchdog] 检测到状态卡住: ${currentStatus.runtimeType}, 持续时间: ${duration.inSeconds}秒',
|
||||
tag: 'HomeController',
|
||||
);
|
||||
|
||||
// 自动触发强制重置
|
||||
_forceResetState().then((_) {
|
||||
Get.snackbar(
|
||||
'系统提示',
|
||||
'VPN 状态异常已自动修复',
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KRLogUtil.kr_i('✅ 状态监控定时器已启动', tag: 'HomeController');
|
||||
}
|
||||
|
||||
/// 连接超时处理
|
||||
Timer? _connectionTimeoutTimer;
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
|
||||
@ -25,14 +26,16 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
final delay = controller.kr_currentNodeLatency.value;
|
||||
|
||||
// 🔧 关键: 强制读取两个 observable 确保追踪
|
||||
final _ = KRSingBoxImp.instance.kr_status.value; // 强制追踪
|
||||
final isConnected = controller.kr_isConnected.value; // 使用 controller 的状态
|
||||
final _ = KRSingBoxImp.instance.kr_status.value; // 强制追踪
|
||||
final isConnected = controller.kr_isConnected.value; // 使用 controller 的状态
|
||||
|
||||
// 再次读取状态用于判断
|
||||
final status = KRSingBoxImp.instance.kr_status.value;
|
||||
final isSwitching = status is SingboxStarting || status is SingboxStopping;
|
||||
final isSwitching =
|
||||
status is SingboxStarting || status is SingboxStopping;
|
||||
|
||||
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
print(
|
||||
'🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
|
||||
final isShow = delay == -1 || isConnected;
|
||||
|
||||
@ -61,32 +64,37 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
maxWidth: buttonSize * 3,
|
||||
maxHeight: buttonSize * 3,
|
||||
child: ContinuousRippleEffect(
|
||||
color: Theme.of(context).primaryColor
|
||||
),
|
||||
color: Theme.of(context).primaryColor),
|
||||
),
|
||||
),
|
||||
|
||||
if (!isShow)
|
||||
Positioned(
|
||||
top: -20, // 距离顶部 10.w
|
||||
top: -20, // 距离顶部 10.w
|
||||
left: 115, // 距离右侧 10.w
|
||||
child: KrLocalImage(
|
||||
imageName: "hi-home-slogan",
|
||||
imageType: ImageType.svg,
|
||||
),
|
||||
),
|
||||
|
||||
/// ✅ 按钮主体
|
||||
Material(
|
||||
shape: const CircleBorder(),
|
||||
color: isShow ? Colors.transparent : Theme.of(context).primaryColor,
|
||||
color: isShow
|
||||
? Colors.transparent
|
||||
: Theme.of(context).primaryColor,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if(isSwitching) {
|
||||
print('🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
HapticFeedback.lightImpact();
|
||||
if (isSwitching) {
|
||||
print(
|
||||
'🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
||||
return;
|
||||
}
|
||||
final current = controller.kr_subscribeService.kr_currentSubscribe.value;
|
||||
final current = controller
|
||||
.kr_subscribeService.kr_currentSubscribe.value;
|
||||
bool hasValidSubscription = false;
|
||||
if (current != null) {
|
||||
DateTime? expire;
|
||||
@ -95,10 +103,12 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
} catch (_) {
|
||||
expire = null;
|
||||
}
|
||||
hasValidSubscription = expire != null && expire.isAfter(DateTime.now());
|
||||
hasValidSubscription =
|
||||
expire != null && expire.isAfter(DateTime.now());
|
||||
}
|
||||
if (hasValidSubscription) {
|
||||
controller.kr_toggleSwitch(!controller.kr_isConnected.value);
|
||||
controller
|
||||
.kr_toggleSwitch(!controller.kr_isConnected.value);
|
||||
} else {
|
||||
HIDialog.show(
|
||||
customMessageWidget: Padding(
|
||||
@ -107,7 +117,10 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
'尚未购买套餐,请前往购买页面下单后即可开始极速网络体验',
|
||||
textAlign: TextAlign.left,
|
||||
style: KrAppTextStyle(
|
||||
color: Theme.of(context).textTheme.bodyMedium?.color,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.color,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@ -116,7 +129,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
cancelText: '取消',
|
||||
confirmText: '前往',
|
||||
onConfirm: () {
|
||||
GlobalOverlayService.instance.triggerSubscriptionAnimation();
|
||||
GlobalOverlayService.instance
|
||||
.triggerSubscriptionAnimation();
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -128,7 +142,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (isShow)
|
||||
/// ✅ isShow = true,图片居中,文字贴近底部
|
||||
|
||||
/// ✅ isShow = true,图片居中,文字贴近底部
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
@ -148,20 +163,21 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
controller.kr_connectText.value == '已连接'
|
||||
? '已连接\n点击断开'
|
||||
: controller.kr_connectText.value,
|
||||
key: ValueKey(controller.kr_connectText.value),
|
||||
key: ValueKey(
|
||||
controller.kr_connectText.value),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1
|
||||
),
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
/// ✅ isShow = false,恢复原本布局
|
||||
|
||||
/// ✅ isShow = false,恢复原本布局
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@ -176,7 +192,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
controller.kr_connectText.value == '已连接'
|
||||
? '已连接\n点击断开'
|
||||
: controller.kr_connectText.value,
|
||||
key: ValueKey(controller.kr_connectText.value),
|
||||
key:
|
||||
ValueKey(controller.kr_connectText.value),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
@ -189,11 +206,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -202,6 +216,7 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// ✅ 呼吸式同心圆动画(最小圆固定270,其他圆在270~479间波动,初始直径差10%)
|
||||
/// ✅ 呼吸式同心圆动画(所有圆在270~470之间运动,起点相同但周期不同)
|
||||
class ContinuousRippleEffect extends StatefulWidget {
|
||||
@ -232,10 +247,11 @@ class _ContinuousRippleEffectState extends State<ContinuousRippleEffect>
|
||||
// 动画周期差异:外层慢,内层快
|
||||
final duration = Duration(milliseconds: 3000 + i * 1200);
|
||||
|
||||
final controller =
|
||||
AnimationController(vsync: this, duration: duration)..repeat(reverse: true);
|
||||
final controller = AnimationController(vsync: this, duration: duration)
|
||||
..repeat(reverse: true);
|
||||
|
||||
final curved = CurvedAnimation(parent: controller, curve: Curves.easeInOutSine);
|
||||
final curved =
|
||||
CurvedAnimation(parent: controller, curve: Curves.easeInOutSine);
|
||||
_controllers.add(controller);
|
||||
_animations.add(curved);
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ class KRLoginView extends GetView<KRLoginController> {
|
||||
// hintText: '确认密码',
|
||||
// isPassword: true,
|
||||
// ),
|
||||
SizedBox(height: 10.w),
|
||||
SizedBox(height: 10),
|
||||
// 👇 核心改动:使用新的验证码输入框
|
||||
_buildVerificationCodeField(),
|
||||
],
|
||||
|
||||
@ -621,7 +621,7 @@ class KRPurchaseMembershipController extends GetxController {
|
||||
return '';
|
||||
}
|
||||
// 计算折扣百分比(例如:97% 显示为 -3%)
|
||||
final discountPercent = 100 - discount.kr_discount;
|
||||
final discountPercent = (100 - discount.kr_discount).ceil();
|
||||
return '${discountPercent}%';
|
||||
}
|
||||
return '';
|
||||
|
||||
@ -336,7 +336,7 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController>
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${controller.kr_getDiscountText(plan, discountIndex!)} OFF',
|
||||
'${controller.kr_getDiscountText(plan, discountIndex!)} off',
|
||||
style: TextStyle(
|
||||
color: Colors.black, // 白色文字更清晰
|
||||
fontSize: 14,
|
||||
|
||||
@ -78,7 +78,7 @@ class GlobalOverlayService extends GetxService {
|
||||
// 2️⃣ money-icon,独立定位,固定在屏幕右上角
|
||||
Positioned(
|
||||
top: MediaQuery.of(context).padding.top + 8,
|
||||
right: (radius - (statusBarHeight / 2)) / 2,
|
||||
right: ((radius - (statusBarHeight / 2)) / 2) + 3,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent, // 让区域可响应点击
|
||||
onTap: () {
|
||||
|
||||
5
local-build-macos.sh
Normal file
5
local-build-macos.sh
Normal file
@ -0,0 +1,5 @@
|
||||
create-dmg "HiFastVPN.app" \
|
||||
--overwrite \
|
||||
--dmg-title="HiFastVPN" \
|
||||
--app-drop-link \
|
||||
--dmg "HiFastVPN.dmg"
|
||||
@ -21,6 +21,8 @@ PODS:
|
||||
- SAMKeychain (1.5.3)
|
||||
- screen_retriever_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- tray_manager (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
@ -40,6 +42,7 @@ DEPENDENCIES:
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)
|
||||
@ -68,6 +71,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
screen_retriever_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
tray_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||
url_launcher_macos:
|
||||
@ -89,6 +94,7 @@ SPEC CHECKSUMS:
|
||||
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
|
||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188
|
||||
|
||||
@ -571,11 +571,12 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -704,11 +705,12 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -731,11 +733,12 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
@ -2,8 +2,14 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(TeamIdentifierPrefix)</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
@ -2,8 +2,14 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(TeamIdentifierPrefix)</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.0.4+1
|
||||
version: 0.0.4+100
|
||||
|
||||
environment:
|
||||
sdk: '>=3.5.0 <4.0.0'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user