feat: 增加连续点击样式处理
This commit is contained in:
parent
5bd77511cc
commit
24cf03d6ce
@ -137,12 +137,12 @@ class KrNodeListItem {
|
|||||||
final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON
|
final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON
|
||||||
|
|
||||||
// 🔧 打印原始节点 JSON(用于调试)
|
// 🔧 打印原始节点 JSON(用于调试)
|
||||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
|
// KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
|
||||||
KRLogUtil.kr_i('📥 收到节点 API 原始数据:', tag: 'NodeList');
|
// KRLogUtil.kr_i('📥 收到节点 API 原始数据:', tag: 'NodeList');
|
||||||
KRLogUtil.kr_i('节点名称: ${json['name']}', tag: 'NodeList');
|
// KRLogUtil.kr_i('节点名称: ${json['name']}', tag: 'NodeList');
|
||||||
KRLogUtil.kr_i('协议类型: ${json['protocol']}', tag: 'NodeList');
|
// KRLogUtil.kr_i('协议类型: ${json['protocol']}', tag: 'NodeList');
|
||||||
KRLogUtil.kr_i('完整 JSON:', tag: 'NodeList');
|
// KRLogUtil.kr_i('完整 JSON:', tag: 'NodeList');
|
||||||
KRLogUtil.kr_i(jsonEncode(json), tag: 'NodeList');
|
// KRLogUtil.kr_i(jsonEncode(json), tag: 'NodeList');
|
||||||
|
|
||||||
// 🔧 如果有 protocols 字段,从中解析 cipher(但不覆盖顶层的 port)
|
// 🔧 如果有 protocols 字段,从中解析 cipher(但不覆盖顶层的 port)
|
||||||
if (protocols.isNotEmpty) {
|
if (protocols.isNotEmpty) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -11,6 +12,9 @@ import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
|
|||||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.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 'package:kaer_with_panels/singbox/model/singbox_status.dart';
|
||||||
|
|
||||||
|
DateTime? _hiConnectBtnNextAllowedAt;
|
||||||
|
const Duration _hiConnectBtnDebounce = Duration(milliseconds: 800);
|
||||||
|
|
||||||
/// ✅ 按钮组件(带多层呼吸同心圆动画)
|
/// ✅ 按钮组件(带多层呼吸同心圆动画)
|
||||||
class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
||||||
final VoidCallback? onTriggerSubscriptionAnimation;
|
final VoidCallback? onTriggerSubscriptionAnimation;
|
||||||
@ -88,11 +92,16 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
if (isSwitching) {
|
final _now = DateTime.now();
|
||||||
print(
|
if (_hiConnectBtnNextAllowedAt != null &&
|
||||||
'🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
|
_now.isBefore(_hiConnectBtnNextAllowedAt!)) {
|
||||||
|
_hiConnectBtnNextAllowedAt =
|
||||||
|
_now.add(_hiConnectBtnDebounce);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_hiConnectBtnNextAllowedAt =
|
||||||
|
_now.add(_hiConnectBtnDebounce);
|
||||||
|
|
||||||
final current = controller
|
final current = controller
|
||||||
.kr_subscribeService.kr_currentSubscribe.value;
|
.kr_subscribeService.kr_currentSubscribe.value;
|
||||||
bool hasValidSubscription = false;
|
bool hasValidSubscription = false;
|
||||||
|
|||||||
@ -40,13 +40,13 @@ class BaseResponse<T> {
|
|||||||
body = jsonDecode(decrypted);
|
body = jsonDecode(decrypted);
|
||||||
|
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print('✅ 解密成功');
|
// print('✅ 解密成功');
|
||||||
print('');
|
// print('');
|
||||||
print('📦 解密后的完整数据:');
|
// print('📦 解密后的完整数据:');
|
||||||
// 格式化打印 JSON,方便调试
|
// // 格式化打印 JSON,方便调试
|
||||||
final bodyStr = JsonEncoder.withIndent(' ').convert(body);
|
// final bodyStr = JsonEncoder.withIndent(' ').convert(body);
|
||||||
print(bodyStr);
|
// print(bodyStr);
|
||||||
print('═══════════════════════════════════════');
|
// print('═══════════════════════════════════════');
|
||||||
}
|
}
|
||||||
|
|
||||||
KRLogUtil.kr_i('🔓 解密成功', tag: 'BaseResponse');
|
KRLogUtil.kr_i('🔓 解密成功', tag: 'BaseResponse');
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class HttpUtil {
|
|||||||
void initDio() {
|
void initDio() {
|
||||||
KRLogUtil.kr_i('🚀 HttpUtil.initDio() 开始初始化', tag: 'HttpUtil');
|
KRLogUtil.kr_i('🚀 HttpUtil.initDio() 开始初始化', tag: 'HttpUtil');
|
||||||
// 不使用 Loggy,改用自定义简洁拦截器
|
// 不使用 Loggy,改用自定义简洁拦截器
|
||||||
_dio.interceptors.add(_KRSimpleHttpInterceptor());
|
_dio.interceptors.add(_KRSimpleHttpInterceptor(_dio));
|
||||||
_dio.options.baseUrl = AppConfig.getInstance().baseUrl;
|
_dio.options.baseUrl = AppConfig.getInstance().baseUrl;
|
||||||
|
|
||||||
// 设置连接超时时间
|
// 设置连接超时时间
|
||||||
@ -334,7 +334,11 @@ class HttpUtil {
|
|||||||
? err.requestOptions.path
|
? err.requestOptions.path
|
||||||
: err.requestOptions.uri.path;
|
: err.requestOptions.uri.path;
|
||||||
msg = '${msg.isNotEmpty ? msg : 'unknown'} ($_pathOnly)';
|
msg = '${msg.isNotEmpty ? msg : 'unknown'} ($_pathOnly)';
|
||||||
KRCommonUtil.kr_showToast('请求失败($_pathOnly)', timeout: 3500);
|
final _ua =
|
||||||
|
(err.requestOptions.extra['__unknown_attempts'] as int?) ?? 0;
|
||||||
|
if (_ua >= 2) {
|
||||||
|
KRCommonUtil.kr_showToast('请求失败($_pathOnly)', timeout: 3500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BaseResponse<T>.fromJson(
|
return BaseResponse<T>.fromJson(
|
||||||
{'code': code, 'msg': msg, 'data': <String, dynamic>{}});
|
{'code': code, 'msg': msg, 'data': <String, dynamic>{}});
|
||||||
@ -405,6 +409,8 @@ class MyInterceptor extends Interceptor {
|
|||||||
|
|
||||||
/// 自定义简洁 HTTP 拦截器(无边框符号)
|
/// 自定义简洁 HTTP 拦截器(无边框符号)
|
||||||
class _KRSimpleHttpInterceptor extends Interceptor {
|
class _KRSimpleHttpInterceptor extends Interceptor {
|
||||||
|
final Dio _dio;
|
||||||
|
_KRSimpleHttpInterceptor(this._dio);
|
||||||
static String? _lastPath;
|
static String? _lastPath;
|
||||||
static int _lastTsMs = 0;
|
static int _lastTsMs = 0;
|
||||||
@override
|
@override
|
||||||
@ -546,14 +552,38 @@ class _KRSimpleHttpInterceptor extends Interceptor {
|
|||||||
final path = (err.requestOptions.path.isNotEmpty)
|
final path = (err.requestOptions.path.isNotEmpty)
|
||||||
? err.requestOptions.path
|
? err.requestOptions.path
|
||||||
: err.requestOptions.uri.path;
|
: err.requestOptions.uri.path;
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
int unknownAttempts =
|
||||||
if (!(_lastPath == path && (now - _lastTsMs) < 2000)) {
|
(err.requestOptions.extra['__unknown_attempts'] as int?) ?? 0;
|
||||||
_lastPath = path;
|
if (unknownAttempts < 2) {
|
||||||
_lastTsMs = now;
|
err.requestOptions.extra['__unknown_attempts'] = unknownAttempts + 1;
|
||||||
KRCommonUtil.kr_showToast('请求失败($path)', timeout: 3500);
|
final delayMs = unknownAttempts == 0 ? 300 : 700;
|
||||||
|
Future.delayed(Duration(milliseconds: delayMs)).then((_) async {
|
||||||
|
final start = DateTime.now();
|
||||||
|
while (!KRSingBoxImp.instance.kr_isProxyReady &&
|
||||||
|
DateTime.now().difference(start) < const Duration(seconds: 1)) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final Response<dynamic> r =
|
||||||
|
await _dio.fetch<dynamic>(err.requestOptions);
|
||||||
|
handler.resolve(r);
|
||||||
|
} catch (e) {
|
||||||
|
handler.next(e is DioException
|
||||||
|
? e
|
||||||
|
: DioException(requestOptions: err.requestOptions, error: e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
if (!(_lastPath == path && (now - _lastTsMs) < 2000)) {
|
||||||
|
_lastPath = path;
|
||||||
|
_lastTsMs = now;
|
||||||
|
KRCommonUtil.kr_showToast('请求失败($path)', timeout: 3500);
|
||||||
|
}
|
||||||
|
KRLogUtil.kr_e('请求失败($path)',
|
||||||
|
tag: 'HttpUtil', error: err, stackTrace: err.stackTrace);
|
||||||
}
|
}
|
||||||
KRLogUtil.kr_e('请求失败($path)',
|
|
||||||
tag: 'HttpUtil', error: err, stackTrace: err.stackTrace);
|
|
||||||
}
|
}
|
||||||
handler.next(err);
|
handler.next(err);
|
||||||
}
|
}
|
||||||
|
|||||||
140
说明文档.md
140
说明文档.md
@ -1,140 +0,0 @@
|
|||||||
# Windows 构建项目说明文档
|
|
||||||
|
|
||||||
## 🎯 项目概述
|
|
||||||
|
|
||||||
本项目是一个 Flutter Windows 应用程序,已成功配置完整的 Windows 构建流程。
|
|
||||||
|
|
||||||
## ✅ 构建状态
|
|
||||||
|
|
||||||
**当前状态**: ✅ **构建成功**
|
|
||||||
|
|
||||||
- **Debug 构建**: ✓ 成功 (282.5s)
|
|
||||||
- **Release 构建**: ✓ 成功 (27s)
|
|
||||||
- **构建环境**: Windows Server + Visual Studio 2022 Enterprise
|
|
||||||
|
|
||||||
## 🏗️ 构建流程
|
|
||||||
|
|
||||||
### 1. 环境准备
|
|
||||||
- ✅ Flutter 3.24.5 已安装
|
|
||||||
- ✅ Visual Studio 2022 Enterprise 已配置
|
|
||||||
- ✅ NuGet 6.14.0 已安装(通过 Chocolatey)
|
|
||||||
- ✅ Windows 长路径支持已启用
|
|
||||||
|
|
||||||
### 2. 构建步骤
|
|
||||||
1. **代码检出** - 从 Gitea 仓库获取代码
|
|
||||||
2. **依赖安装** - 安装 Flutter 依赖包
|
|
||||||
3. **代码生成** - 运行 build_runner 生成代码
|
|
||||||
4. **Windows 构建** - 构建 Debug 和 Release 版本
|
|
||||||
5. **产物上传** - 上传构建产物到 Gitea Actions
|
|
||||||
|
|
||||||
### 3. 构建输出
|
|
||||||
```
|
|
||||||
Debug: build/windows/x64/runner/Debug/hostexecutor.exe
|
|
||||||
Release: build/windows/x64/runner/Release/hostexecutor.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 关键修复记录
|
|
||||||
|
|
||||||
### 1. NuGet 安装问题
|
|
||||||
**问题**: SSL/TLS 安全通道错误
|
|
||||||
**解决方案**:
|
|
||||||
- 使用 Chocolatey 安装 NuGet
|
|
||||||
- 命令: `choco install nuget.commandline -y`
|
|
||||||
|
|
||||||
### 2. Flutter 路径配置
|
|
||||||
**问题**: Flutter 命令未找到
|
|
||||||
**解决方案**:
|
|
||||||
- 添加 Flutter 到 PATH: `C:\flutter\bin`
|
|
||||||
- 在每个构建步骤中显式设置 PATH
|
|
||||||
|
|
||||||
### 3. 长路径问题
|
|
||||||
**问题**: Windows 路径长度限制
|
|
||||||
**解决方案**:
|
|
||||||
- 启用 Windows 长路径支持
|
|
||||||
- 注册表设置: `LongPathsEnabled = 1`
|
|
||||||
|
|
||||||
### 4. 构建产物路径
|
|
||||||
**问题**: 上传路径配置错误
|
|
||||||
**解决方案**:
|
|
||||||
- 修正路径: `build/windows/x64/runner/Debug/`
|
|
||||||
- 原错误路径: `build/windows/runner/Debug/`
|
|
||||||
|
|
||||||
## 📁 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
/Users/Apple/vpn/hi-client/
|
|
||||||
├── .gitea/workflows/ # Gitea Actions 工作流配置
|
|
||||||
├── lib/ # Flutter 源代码
|
|
||||||
│ ├── app/ # 应用程序代码
|
|
||||||
│ ├── core/ # 核心功能
|
|
||||||
│ └── singbox/ # SingBox 相关
|
|
||||||
├── windows/ # Windows 平台配置
|
|
||||||
├── libcore/ # 核心库(子模块)
|
|
||||||
└── build/ # 构建输出(运行时生成)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 快速开始
|
|
||||||
|
|
||||||
### 本地构建
|
|
||||||
```bash
|
|
||||||
# 安装 Flutter 依赖
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
# 生成代码
|
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
|
||||||
|
|
||||||
# 构建 Windows Debug
|
|
||||||
flutter build windows
|
|
||||||
|
|
||||||
# 构建 Windows Release
|
|
||||||
flutter build windows --release
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用脚本
|
|
||||||
```bash
|
|
||||||
# 运行 Windows 构建修复脚本
|
|
||||||
./fix_windows_build.ps1
|
|
||||||
|
|
||||||
# 安装 NuGet(如果需要)
|
|
||||||
./install_nuget_simple.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📋 注意事项
|
|
||||||
|
|
||||||
### 1. 构建环境要求
|
|
||||||
- Windows 10/11 或 Windows Server
|
|
||||||
- Visual Studio 2022(包含 C++ 开发工具)
|
|
||||||
- Flutter 3.24.5+
|
|
||||||
- NuGet CLI
|
|
||||||
|
|
||||||
### 2. 常见问题
|
|
||||||
- **CMake 警告**: 可忽略,不影响构建
|
|
||||||
- **WebView2 警告**: 类型转换警告,不影响功能
|
|
||||||
- **路径问题**: 确保使用正确的 x64 路径
|
|
||||||
|
|
||||||
### 3. 性能优化
|
|
||||||
- Debug 构建约 4.7 分钟
|
|
||||||
- Release 构建约 27 秒
|
|
||||||
- 建议使用 Release 版本进行分发
|
|
||||||
|
|
||||||
## 🔍 调试工具
|
|
||||||
|
|
||||||
### 构建日志分析
|
|
||||||
查看 `构建日志分析.md` 文件获取详细的构建日志分析和故障排除指南。
|
|
||||||
|
|
||||||
### 连接状态调试
|
|
||||||
使用 `debug_connection_status.dart` 工具检查应用连接状态。
|
|
||||||
|
|
||||||
## 📞 支持
|
|
||||||
|
|
||||||
如遇到构建问题,请检查:
|
|
||||||
1. 环境配置是否正确
|
|
||||||
2. 依赖是否完整安装
|
|
||||||
3. 查看构建日志获取具体错误信息
|
|
||||||
4. 参考本说明文档的修复记录
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**最后更新**: $(date)
|
|
||||||
**构建状态**: ✅ 成功
|
|
||||||
**文档版本**: 1.0
|
|
||||||
Loading…
x
Reference in New Issue
Block a user